]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/lxc/CreateWizard.js
ui: lxc: dev passthrough: allow translating validation errors
[pve-manager.git] / www / manager6 / lxc / CreateWizard.js
1 Ext.define('PVE.lxc.CreateWizard', {
2 extend: 'PVE.window.Wizard',
3 mixins: ['Proxmox.Mixin.CBind'],
4
5 viewModel: {
6 data: {
7 nodename: '',
8 storage: '',
9 unprivileged: true,
10 },
11 formulas: {
12 cgroupMode: function(get) {
13 const nodeInfo = PVE.data.ResourceStore.getNodes().find(
14 node => node.node === get('nodename'),
15 );
16 return nodeInfo ? nodeInfo['cgroup-mode'] : 2;
17 },
18 },
19 },
20
21 cbindData: {
22 nodename: undefined,
23 },
24
25 subject: gettext('LXC Container'),
26
27 items: [
28 {
29 xtype: 'inputpanel',
30 title: gettext('General'),
31 onlineHelp: 'pct_general',
32 column1: [
33 {
34 xtype: 'pveNodeSelector',
35 name: 'nodename',
36 cbind: {
37 selectCurNode: '{!nodename}',
38 preferredValue: '{nodename}',
39 },
40 bind: {
41 value: '{nodename}',
42 },
43 fieldLabel: gettext('Node'),
44 allowBlank: false,
45 onlineValidator: true,
46 },
47 {
48 xtype: 'pveGuestIDSelector',
49 name: 'vmid', // backend only knows vmid
50 guestType: 'lxc',
51 value: '',
52 loadNextFreeID: true,
53 validateExists: false,
54 },
55 {
56 xtype: 'proxmoxtextfield',
57 name: 'hostname',
58 vtype: 'DnsName',
59 value: '',
60 fieldLabel: gettext('Hostname'),
61 skipEmptyText: true,
62 allowBlank: true,
63 },
64 {
65 xtype: 'proxmoxcheckbox',
66 name: 'unprivileged',
67 value: true,
68 bind: {
69 value: '{unprivileged}',
70 },
71 fieldLabel: gettext('Unprivileged container'),
72 },
73 {
74 xtype: 'proxmoxcheckbox',
75 name: 'features',
76 inputValue: 'nesting=1',
77 value: true,
78 bind: {
79 disabled: '{!unprivileged}',
80 },
81 fieldLabel: gettext('Nesting'),
82 },
83 ],
84 column2: [
85 {
86 xtype: 'pvePoolSelector',
87 fieldLabel: gettext('Resource Pool'),
88 name: 'pool',
89 value: '',
90 allowBlank: true,
91 },
92 {
93 xtype: 'textfield',
94 inputType: 'password',
95 name: 'password',
96 value: '',
97 fieldLabel: gettext('Password'),
98 allowBlank: false,
99 minLength: 5,
100 change: function(f, value) {
101 if (f.rendered) {
102 f.up().down('field[name=confirmpw]').validate();
103 }
104 },
105 },
106 {
107 xtype: 'textfield',
108 inputType: 'password',
109 name: 'confirmpw',
110 value: '',
111 fieldLabel: gettext('Confirm password'),
112 allowBlank: true,
113 submitValue: false,
114 validator: function(value) {
115 var pw = this.up().down('field[name=password]').getValue();
116 if (pw !== value) {
117 return "Passwords do not match!";
118 }
119 return true;
120 },
121 },
122 {
123 xtype: 'textarea',
124 name: 'ssh-public-keys',
125 value: '',
126 fieldLabel: gettext('SSH public key(s)'),
127 allowBlank: true,
128 validator: function(value) {
129 let pwfield = this.up().down('field[name=password]');
130 if (value.length) {
131 let keys = value.indexOf('\n') !== -1 ? value.split('\n') : [value];
132 if (keys.some(key => key !== '' && !PVE.Parser.parseSSHKey(key))) {
133 return "Failed to recognize ssh key";
134 }
135 pwfield.allowBlank = true;
136 } else {
137 pwfield.allowBlank = false;
138 }
139 pwfield.validate();
140 return true;
141 },
142 afterRender: function() {
143 if (!window.FileReader) {
144 return; // No FileReader support in this browser
145 }
146 let cancelEvent = ev => {
147 ev = ev.event;
148 if (ev.preventDefault) {
149 ev.preventDefault();
150 }
151 };
152 this.inputEl.on('dragover', cancelEvent);
153 this.inputEl.on('dragenter', cancelEvent);
154 this.inputEl.on('drop', ev => {
155 cancelEvent(ev);
156 let files = ev.event.dataTransfer.files;
157 PVE.Utils.loadSSHKeyFromFile(files[0], v => this.setValue(v));
158 });
159 },
160 },
161 {
162 xtype: 'pveMultiFileButton',
163 name: 'file',
164 hidden: !window.FileReader,
165 text: gettext('Load SSH Key File'),
166 listeners: {
167 change: function(btn, e, value) {
168 e = e.event;
169 let field = this.up().down('textarea[name=ssh-public-keys]');
170 for (const file of e?.target?.files ?? []) {
171 PVE.Utils.loadSSHKeyFromFile(file, v => {
172 let oldValue = field.getValue();
173 field.setValue(oldValue ? `${oldValue}\n${v.trim()}` : v.trim());
174 });
175 }
176 btn.reset();
177 },
178 },
179 },
180 ],
181 advancedColumnB: [
182 {
183 xtype: 'pveTagFieldSet',
184 name: 'tags',
185 maxHeight: 150,
186 },
187 ],
188 },
189 {
190 xtype: 'inputpanel',
191 title: gettext('Template'),
192 onlineHelp: 'pct_container_images',
193 column1: [
194 {
195 xtype: 'pveStorageSelector',
196 name: 'tmplstorage',
197 fieldLabel: gettext('Storage'),
198 storageContent: 'vztmpl',
199 autoSelect: true,
200 allowBlank: false,
201 bind: {
202 value: '{storage}',
203 nodename: '{nodename}',
204 },
205 },
206 {
207 xtype: 'pveFileSelector',
208 name: 'ostemplate',
209 storageContent: 'vztmpl',
210 fieldLabel: gettext('Template'),
211 bind: {
212 storage: '{storage}',
213 nodename: '{nodename}',
214 },
215 allowBlank: false,
216 },
217 ],
218 },
219 {
220 xtype: 'pveMultiMPPanel',
221 title: gettext('Disks'),
222 insideWizard: true,
223 isCreate: true,
224 unused: false,
225 confid: 'rootfs',
226 },
227 {
228 xtype: 'pveLxcCPUInputPanel',
229 title: gettext('CPU'),
230 insideWizard: true,
231 },
232 {
233 xtype: 'pveLxcMemoryInputPanel',
234 title: gettext('Memory'),
235 insideWizard: true,
236 },
237 {
238 xtype: 'pveLxcNetworkInputPanel',
239 title: gettext('Network'),
240 insideWizard: true,
241 bind: {
242 nodename: '{nodename}',
243 },
244 isCreate: true,
245 },
246 {
247 xtype: 'pveLxcDNSInputPanel',
248 title: gettext('DNS'),
249 insideWizard: true,
250 },
251 {
252 title: gettext('Confirm'),
253 layout: 'fit',
254 items: [
255 {
256 xtype: 'grid',
257 store: {
258 model: 'KeyValue',
259 sorters: [{
260 property: 'key',
261 direction: 'ASC',
262 }],
263 },
264 columns: [
265 { header: 'Key', width: 150, dataIndex: 'key' },
266 { header: 'Value', flex: 1, dataIndex: 'value' },
267 ],
268 },
269 ],
270 dockedItems: [
271 {
272 xtype: 'proxmoxcheckbox',
273 name: 'start',
274 dock: 'bottom',
275 margin: '5 0 0 0',
276 boxLabel: gettext('Start after created'),
277 },
278 ],
279 listeners: {
280 show: function(panel) {
281 let wizard = this.up('window');
282 let kv = wizard.getValues();
283 let data = [];
284 Ext.Object.each(kv, function(key, value) {
285 if (key === 'delete' || key === 'tmplstorage') { // ignore
286 return;
287 }
288 if (key === 'password') { // don't show pw
289 return;
290 }
291 data.push({ key: key, value: value });
292 });
293
294 let summaryStore = panel.down('grid').getStore();
295 summaryStore.suspendEvents();
296 summaryStore.removeAll();
297 summaryStore.add(data);
298 summaryStore.sort();
299 summaryStore.resumeEvents();
300 summaryStore.fireEvent('refresh');
301 },
302 },
303 onSubmit: function() {
304 let wizard = this.up('window');
305 let kv = wizard.getValues();
306 delete kv.delete;
307
308 let nodename = kv.nodename;
309 delete kv.nodename;
310 delete kv.tmplstorage;
311
312 if (!kv.pool.length) {
313 delete kv.pool;
314 }
315 if (!kv.password.length && kv['ssh-public-keys']) {
316 delete kv.password;
317 }
318
319 Proxmox.Utils.API2Request({
320 url: `/nodes/${nodename}/lxc`,
321 waitMsgTarget: wizard,
322 method: 'POST',
323 params: kv,
324 success: function(response, opts) {
325 Ext.create('Proxmox.window.TaskViewer', {
326 autoShow: true,
327 upid: response.result.data,
328 });
329 wizard.close();
330 },
331 failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
332 });
333 },
334 },
335 ],
336 });