Line data Source code
1 43 : import React, { useState } from 'react';
2 :
3 : import { Button } from "@patternfly/react-core/dist/esm/components/Button";
4 : import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox";
5 : import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form";
6 : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput";
7 : import {
8 : Modal
9 : } from '@patternfly/react-core/dist/esm/deprecated/components/Modal';
10 : import { FormHelper } from 'cockpit-components-form-helper.jsx';
11 : import { useDialogs } from "dialogs.jsx";
12 : import { fmt_to_fragments } from 'utils.jsx';
13 :
14 : import cockpit from 'cockpit';
15 :
16 : import { ErrorNotification } from './Notification.jsx';
17 : import * as client from './client.js';
18 : import * as utils from './util.js';
19 :
20 43 : const _ = cockpit.gettext;
21 :
22 2 : const ContainerCommitModal = ({ con, container, localImages }) => {
23 2 : const Dialogs = useDialogs();
24 :
25 2 : const [imageName, setImageName] = useState("");
26 2 : const [tag, setTag] = useState("");
27 2 : const [author, setAuthor] = useState("");
28 2 : const [command, setCommand] = useState(utils.quote_cmdline(container.Config.Cmd));
29 2 : const [pause, setPause] = useState(false);
30 2 : const [useDocker, setUseDocker] = useState(false);
31 :
32 2 : const [dialogError, setDialogError] = useState("");
33 2 : const [dialogErrorDetail, setDialogErrorDetail] = useState("");
34 2 : const [commitInProgress, setCommitInProgress] = useState(false);
35 2 : const [nameError, setNameError] = useState("");
36 :
37 2 : const handleCommit = (force) => {
38 2 : if (!force && !imageName) {
39 2 : setNameError(_("Image name is required"));
40 2 : return;
41 2 : }
42 :
43 2 : let full_name = imageName + ":" + (tag !== "" ? tag : "latest");
44 2 : if (full_name.indexOf("/") < 0)
45 2 : full_name = "localhost/" + full_name;
46 :
47 2 : if (!force && localImages?.some(image => image.uid === container.uid && image.Name === full_name)) {
48 2 : setNameError(_("Image name is not unique"));
49 2 : return;
50 2 : }
51 :
52 2 : function quote(word) {
53 2 : word = word.replace(/"/g, '\\"');
54 2 : return '"' + word + '"';
55 2 : }
56 :
57 2 : const commitData = {};
58 2 : commitData.container = container.Id;
59 2 : commitData.repo = imageName;
60 2 : commitData.author = author;
61 2 : commitData.pause = pause;
62 :
63 2 : if (useDocker)
64 2 : commitData.format = 'docker';
65 :
66 2 : if (tag)
67 2 : commitData.tag = tag;
68 :
69 2 : commitData.changes = [];
70 2 : if (command.trim() !== "") {
71 2 : let cmdData = "";
72 2 : const words = utils.unquote_cmdline(command.trim());
73 2 : const cmdStr = words.map(quote).join(", ");
74 2 : cmdData = "CMD [" + cmdStr + "]";
75 2 : commitData.changes.push(cmdData);
76 2 : }
77 :
78 2 : setCommitInProgress(true);
79 2 : setNameError("");
80 2 : setDialogError("");
81 2 : setDialogErrorDetail("");
82 2 : client.commitContainer(con, commitData)
83 2 : .then(() => Dialogs.close())
84 2 : .catch(ex => {
85 2 : setDialogError(cockpit.format(_("Failed to commit container $0"), container.Name));
86 2 : setDialogErrorDetail(cockpit.format("$0: $1", ex.message, ex.reason));
87 2 : setCommitInProgress(false);
88 2 : });
89 2 : };
90 :
91 2 : const commitContent = (
92 2 : <Form isHorizontal>
93 0 : {dialogError && <ErrorNotification errorMessage={dialogError} errorDetail={dialogErrorDetail} onDismiss={() => setDialogError("")} />}
94 2 : <FormGroup fieldId="commit-dialog-image-name" label={_("New image name")}>
95 2 : <TextInput id="commit-dialog-image-name"
96 2 : value={imageName}
97 2 : validated={nameError ? "error" : "default"}
98 2 : onChange={(_, value) => { setNameError(""); setImageName(value) }} />
99 2 : <FormHelper fieldId="commit-dialog-image-name" helperTextInvalid={nameError} />
100 2 : </FormGroup>
101 :
102 2 : <FormGroup fieldId="commit-dialog-image-tag" label={_("Tag")}>
103 2 : <TextInput id="commit-dialog-image-tag"
104 2 : placeholder="latest" // Do not translate
105 2 : value={tag}
106 2 : onChange={(_, value) => { setNameError(""); setTag(value) }} />
107 2 : </FormGroup>
108 :
109 2 : <FormGroup fieldId="commit-dialog-author" label={_("Author")}>
110 2 : <TextInput id="commit-dialog-author"
111 2 : placeholder={_("Example, Your Name <yourname@example.com>")}
112 2 : value={author}
113 2 : onChange={(_, value) => setAuthor(value)} />
114 2 : </FormGroup>
115 :
116 2 : <FormGroup fieldId="commit-dialog-command" label={_("Command")}>
117 2 : <TextInput id="commit-dialog-command"
118 2 : value={command}
119 2 : onChange={(_, value) => setCommand(value)} />
120 2 : </FormGroup>
121 :
122 2 : <FormGroup fieldId="commit-dialog-pause" label={_("Options")} isStack hasNoPaddingTop>
123 2 : <Checkbox id="commit-dialog-pause"
124 2 : isChecked={pause}
125 2 : onChange={(_, val) => setPause(val)}
126 2 : label={_("Pause container when creating image")} />
127 2 : <Checkbox id="commit-dialog-docker"
128 2 : isChecked={useDocker}
129 2 : onChange={(_, val) => setUseDocker(val)}
130 2 : description={_("Docker format is useful when sharing the image with Docker or Moby Engine")}
131 2 : label={_("Use legacy Docker format")} />
132 2 : </FormGroup>
133 2 : </Form>
134 : );
135 :
136 2 : return (
137 2 : <Modal isOpen
138 2 : showClose={false}
139 2 : position="top" variant="medium"
140 2 : title={_("Commit container")}
141 2 : description={fmt_to_fragments(_("Create a new image based on the current state of the $0 container."), <b>{container.Name}</b>)}
142 2 : footer={<>
143 2 : <Button variant="primary"
144 2 : className="btn-ctr-commit"
145 2 : isLoading={commitInProgress && !nameError}
146 2 : isDisabled={commitInProgress || nameError}
147 2 : onClick={() => handleCommit(false)}>
148 2 : {_("Commit")}
149 2 : </Button>
150 2 : {nameError && <Button variant="warning"
151 2 : className="btn-ctr-commit-force"
152 2 : isLoading={commitInProgress}
153 2 : isDisabled={commitInProgress}
154 2 : onClick={() => handleCommit(true)}>
155 2 : {_("Force commit")}
156 2 : </Button>}
157 2 : <Button variant="link"
158 2 : className="btn-ctr-cancel-commit"
159 2 : isDisabled={commitInProgress}
160 2 : onClick={Dialogs.close}>
161 2 : {_("Cancel")}
162 2 : </Button>
163 2 : </>}
164 : >
165 2 : {commitContent}
166 2 : </Modal>
167 : );
168 2 : };
169 :
170 43 : export default ContainerCommitModal;
|