]>
Commit | Line | Data |
---|---|---|
d7683c68 | 1 | Ext.define('PVE.window.Restore', { |
9fccc702 | 2 | extend: 'Ext.window.Window', // fixme: Proxmox.window.Edit? |
d7683c68 DM |
3 | |
4 | resizable: false, | |
d3de9b55 TL |
5 | width: 500, |
6 | modal: true, | |
7 | layout: 'auto', | |
8 | border: false, | |
d7683c68 | 9 | |
878bbf0b SR |
10 | controller: { |
11 | xclass: 'Ext.app.ViewController', | |
12 | control: { | |
13 | '#liveRestore': { | |
14 | change: function(el, newVal) { | |
15 | let liveWarning = this.lookupReference('liveWarning'); | |
16 | liveWarning.setHidden(!newVal); | |
17 | let start = this.lookupReference('start'); | |
18 | start.setDisabled(newVal); | |
19 | }, | |
20 | }, | |
d3de9b55 TL |
21 | 'form': { |
22 | validitychange: function(f, valid) { | |
23 | this.lookupReference('doRestoreBtn').setDisabled(!valid); | |
24 | }, | |
25 | }, | |
26 | }, | |
27 | ||
28 | doRestore: function() { | |
29 | let me = this; | |
30 | let view = me.getView(); | |
31 | ||
32 | let values = view.down('form').getForm().getValues(); | |
33 | ||
34 | let params = { | |
35 | vmid: view.vmid || values.vmid, | |
36 | force: view.vmid ? 1 : 0, | |
37 | }; | |
38 | if (values.unique) { | |
39 | params.unique = 1; | |
40 | } | |
41 | if (values.start && !values['live-restore']) { | |
42 | params.start = 1; | |
43 | } | |
44 | if (values['live-restore']) { | |
45 | params['live-restore'] = 1; | |
46 | } | |
47 | if (values.storage) { | |
48 | params.storage = values.storage; | |
49 | } | |
1ad26512 FE |
50 | |
51 | ['bwlimit', 'cores', 'name', 'memory', 'sockets'].forEach(opt => { | |
52 | if ((values[opt] ?? '') !== '') { | |
53 | params[opt] = values[opt]; | |
54 | } | |
55 | }); | |
56 | ||
57 | if (params.name && view.vmtype === 'lxc') { | |
58 | params.hostname = params.name; | |
59 | delete params.name; | |
d3de9b55 TL |
60 | } |
61 | ||
62 | let confirmMsg; | |
63 | if (view.vmtype === 'lxc') { | |
64 | params.ostemplate = view.volid; | |
65 | params.restore = 1; | |
66 | if (values.unprivileged !== 'keep') { | |
67 | params.unprivileged = values.unprivileged; | |
68 | } | |
69 | confirmMsg = Proxmox.Utils.format_task_description('vzrestore', params.vmid); | |
70 | } else if (view.vmtype === 'qemu') { | |
71 | params.archive = view.volid; | |
72 | confirmMsg = Proxmox.Utils.format_task_description('qmrestore', params.vmid); | |
73 | } else { | |
74 | throw 'unknown VM type'; | |
75 | } | |
76 | ||
77 | let executeRestore = () => { | |
78 | Proxmox.Utils.API2Request({ | |
79 | url: `/nodes/${view.nodename}/${view.vmtype}`, | |
80 | params: params, | |
81 | method: 'POST', | |
82 | waitMsgTarget: view, | |
83 | failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus), | |
84 | success: function(response, options) { | |
85 | Ext.create('Proxmox.window.TaskViewer', { | |
86 | autoShow: true, | |
87 | upid: response.result.data, | |
88 | }); | |
89 | view.close(); | |
90 | }, | |
91 | }); | |
92 | }; | |
93 | ||
94 | if (view.vmid) { | |
5f855ccf FE |
95 | confirmMsg += `. ${Ext.String.format( |
96 | gettext('This will permanently erase current {0} data.'), | |
97 | view.vmtype === 'lxc' ? 'CT' : 'VM', | |
98 | )}`; | |
99 | if (view.vmtype === 'lxc') { | |
a707bed6 | 100 | confirmMsg += `<br>${gettext('Mount point volumes are also erased.')}`; |
5f855ccf | 101 | } |
d3de9b55 TL |
102 | Ext.Msg.confirm(gettext('Confirm'), confirmMsg, function(btn) { |
103 | if (btn === 'yes') { | |
104 | executeRestore(); | |
105 | } | |
106 | }); | |
107 | } else { | |
108 | executeRestore(); | |
109 | } | |
878bbf0b | 110 | }, |
47520da3 FE |
111 | |
112 | afterRender: function() { | |
113 | let view = this.getView(); | |
114 | ||
115 | Proxmox.Utils.API2Request({ | |
116 | url: `/nodes/${view.nodename}/vzdump/extractconfig`, | |
117 | method: 'GET', | |
118 | waitMsgTarget: view, | |
119 | params: { | |
120 | volume: view.volid, | |
121 | }, | |
122 | failure: response => Ext.Msg.alert('Error', response.htmlStatus), | |
123 | success: function(response, options) { | |
1ad26512 FE |
124 | let allStoragesAvailable = true; |
125 | ||
126 | response.result.data.split('\n').forEach(line => { | |
127 | let [_, key, value] = line.match(/^([^:]+):\s*(\S+)\s*$/) ?? []; | |
128 | ||
129 | if (!key) { | |
130 | return; | |
131 | } | |
132 | ||
133 | if (key === '#qmdump#map') { | |
134 | let match = value.match(/^(\S+):(\S+):(\S*):(\S*):$/) ?? []; | |
135 | // if a /dev/XYZ disk was backed up, ther is no storage hint | |
136 | allStoragesAvailable &&= !!match[3] && !!PVE.data.ResourceStore.getById( | |
137 | `storage/${view.nodename}/${match[3]}`); | |
138 | } else if (key === 'name' || key === 'hostname') { | |
139 | view.lookupReference('nameField').setEmptyText(value); | |
140 | } else if (key === 'memory' || key === 'cores' || key === 'sockets') { | |
141 | view.lookupReference(`${key}Field`).setEmptyText(value); | |
47520da3 | 142 | } |
47520da3 FE |
143 | }); |
144 | ||
145 | if (!allStoragesAvailable) { | |
146 | let storagesel = view.down('pveStorageSelector[name=storage]'); | |
147 | storagesel.allowBlank = false; | |
148 | storagesel.setEmptyText(''); | |
149 | } | |
150 | }, | |
151 | }); | |
152 | }, | |
878bbf0b SR |
153 | }, |
154 | ||
8058410f | 155 | initComponent: function() { |
d3de9b55 | 156 | let me = this; |
d7683c68 DM |
157 | |
158 | if (!me.nodename) { | |
159 | throw "no node name specified"; | |
160 | } | |
d7683c68 DM |
161 | if (!me.volid) { |
162 | throw "no volume ID specified"; | |
163 | } | |
d7683c68 DM |
164 | if (!me.vmtype) { |
165 | throw "no vmtype specified"; | |
166 | } | |
167 | ||
d3de9b55 | 168 | let storagesel = Ext.create('PVE.form.StorageSelector', { |
d7683c68 DM |
169 | nodename: me.nodename, |
170 | name: 'storage', | |
171 | value: '', | |
172 | fieldLabel: gettext('Storage'), | |
53e3ea84 | 173 | storageContent: me.vmtype === 'lxc' ? 'rootdir' : 'images', |
6fe863ba FE |
174 | // when restoring a container without specifying a storage, the backend defaults |
175 | // to 'local', which is unintuitive and 'rootdir' might not even be allowed on it | |
176 | allowBlank: me.vmtype !== 'lxc', | |
53e3ea84 | 177 | emptyText: me.vmtype === 'lxc' ? '' : gettext('From backup configuration'), |
6fe863ba | 178 | autoSelect: me.vmtype === 'lxc', |
d7683c68 DM |
179 | }); |
180 | ||
d3de9b55 | 181 | let items = [ |
9cf2e84b DM |
182 | { |
183 | xtype: 'displayfield', | |
184 | value: me.volidText || me.volid, | |
f6710aac | 185 | fieldLabel: gettext('Source'), |
9cf2e84b DM |
186 | }, |
187 | storagesel, | |
ce60366f TL |
188 | { |
189 | xtype: 'pmxDisplayEditField', | |
190 | name: 'vmid', | |
191 | fieldLabel: me.vmtype === 'lxc' ? 'CT' : 'VM', | |
192 | value: me.vmid, | |
193 | editable: !me.vmid, | |
194 | editConfig: { | |
195 | xtype: 'pveGuestIDSelector', | |
196 | guestType: me.vmtype, | |
197 | loadNextFreeID: true, | |
198 | validateExists: false, | |
199 | }, | |
200 | }, | |
9fa4ce6d | 201 | { |
a331258a | 202 | xtype: 'pveBandwidthField', |
9fa4ce6d | 203 | name: 'bwlimit', |
0545b2a5 | 204 | backendUnit: 'KiB', |
9ae4a024 FE |
205 | allowZero: true, |
206 | fieldLabel: gettext('Bandwidth Limit'), | |
9fa4ce6d TL |
207 | emptyText: gettext('Defaults to target storage restore limit'), |
208 | autoEl: { | |
209 | tag: 'div', | |
f6710aac TL |
210 | 'data-qtip': gettext("Use '0' to disable all bandwidth limits."), |
211 | }, | |
c517e212 | 212 | }, |
8b030d08 | 213 | { |
24c55f41 TL |
214 | xtype: 'fieldcontainer', |
215 | layout: 'hbox', | |
216 | items: [{ | |
217 | xtype: 'proxmoxcheckbox', | |
218 | name: 'unique', | |
219 | fieldLabel: gettext('Unique'), | |
24c55f41 TL |
220 | flex: 1, |
221 | autoEl: { | |
222 | tag: 'div', | |
f6710aac | 223 | 'data-qtip': gettext('Autogenerate unique properties, e.g., MAC addresses'), |
24c55f41 | 224 | }, |
f6710aac | 225 | checked: false, |
8b030d08 | 226 | }, |
24c55f41 TL |
227 | { |
228 | xtype: 'proxmoxcheckbox', | |
229 | name: 'start', | |
878bbf0b | 230 | reference: 'start', |
24c55f41 TL |
231 | flex: 1, |
232 | fieldLabel: gettext('Start after restore'), | |
233 | labelWidth: 105, | |
f6710aac | 234 | checked: false, |
24c55f41 TL |
235 | }], |
236 | }, | |
9cf2e84b DM |
237 | ]; |
238 | ||
239 | if (me.vmtype === 'lxc') { | |
6da2926c TL |
240 | items.push( |
241 | { | |
242 | xtype: 'radiogroup', | |
243 | fieldLabel: gettext('Privilege Level'), | |
244 | reference: 'noVNCScalingGroup', | |
245 | height: '15px', // renders faster with value assigned | |
246 | layout: { | |
247 | type: 'hbox', | |
248 | algin: 'stretch', | |
249 | }, | |
250 | autoEl: { | |
251 | tag: 'div', | |
252 | 'data-qtip': | |
253 | gettext('Choose if you want to keep or override the privilege level of the restored Container.'), | |
254 | }, | |
255 | items: [ | |
256 | { | |
257 | xtype: 'radiofield', | |
258 | name: 'unprivileged', | |
259 | inputValue: 'keep', | |
260 | boxLabel: gettext('From Backup'), | |
261 | flex: 1, | |
262 | checked: true, | |
263 | }, | |
264 | { | |
265 | xtype: 'radiofield', | |
266 | name: 'unprivileged', | |
267 | inputValue: '1', | |
268 | boxLabel: gettext('Unprivileged'), | |
269 | flex: 1, | |
270 | }, | |
271 | { | |
272 | xtype: 'radiofield', | |
273 | name: 'unprivileged', | |
274 | inputValue: '0', | |
275 | boxLabel: gettext('Privileged'), | |
276 | flex: 1, | |
277 | //margin: '0 0 0 10', | |
278 | }, | |
279 | ], | |
280 | }, | |
281 | ); | |
878bbf0b SR |
282 | } else if (me.vmtype === 'qemu') { |
283 | items.push({ | |
284 | xtype: 'proxmoxcheckbox', | |
285 | name: 'live-restore', | |
286 | itemId: 'liveRestore', | |
287 | flex: 1, | |
288 | fieldLabel: gettext('Live restore'), | |
289 | checked: false, | |
290 | hidden: !me.isPBS, | |
d3de9b55 TL |
291 | }, |
292 | { | |
878bbf0b SR |
293 | xtype: 'displayfield', |
294 | reference: 'liveWarning', | |
295 | // TODO: Remove once more tested/stable? | |
d290acc8 | 296 | value: gettext('Note: If anything goes wrong during the live-restore, new data written by the VM may be lost.'), |
878bbf0b SR |
297 | userCls: 'pmx-hint', |
298 | hidden: true, | |
299 | }); | |
9cf2e84b DM |
300 | } |
301 | ||
c7f66bd0 FE |
302 | items.push({ |
303 | xtype: 'fieldset', | |
304 | title: `${gettext('Override Settings')}:`, | |
8ce4c524 | 305 | layout: 'hbox', |
c7f66bd0 | 306 | defaults: { |
8ce4c524 TL |
307 | border: false, |
308 | layout: 'anchor', | |
309 | flex: 1, | |
1ad26512 | 310 | }, |
c7f66bd0 FE |
311 | items: [ |
312 | { | |
8ce4c524 TL |
313 | padding: '0 10 0 0', |
314 | items: [{ | |
315 | xtype: 'textfield', | |
316 | fieldLabel: me.vmtype === 'lxc' ? gettext('Hostname') : gettext('Name'), | |
317 | name: 'name', | |
a2b8ac2e | 318 | vtype: 'DnsName', |
8ce4c524 TL |
319 | reference: 'nameField', |
320 | allowBlank: true, | |
321 | }, { | |
322 | xtype: 'proxmoxintegerfield', | |
323 | fieldLabel: gettext('Cores'), | |
324 | name: 'cores', | |
325 | reference: 'coresField', | |
326 | minValue: 1, | |
327 | maxValue: 128, | |
328 | allowBlank: true, | |
329 | }], | |
c7f66bd0 FE |
330 | }, |
331 | { | |
8ce4c524 TL |
332 | padding: '0 0 0 10', |
333 | items: [ | |
334 | { | |
335 | xtype: 'pveMemoryField', | |
336 | fieldLabel: gettext('Memory'), | |
337 | name: 'memory', | |
338 | reference: 'memoryField', | |
339 | value: '', | |
340 | allowBlank: true, | |
341 | }, | |
342 | { | |
343 | xtype: 'proxmoxintegerfield', | |
344 | fieldLabel: gettext('Sockets'), | |
345 | name: 'sockets', | |
346 | reference: 'socketsField', | |
347 | minValue: 1, | |
348 | maxValue: 4, | |
349 | allowBlank: true, | |
350 | hidden: me.vmtype !== 'qemu', | |
351 | disabled: me.vmtype !== 'qemu', | |
352 | }], | |
c7f66bd0 FE |
353 | }, |
354 | ], | |
b61d079a | 355 | }); |
1ad26512 | 356 | |
d3de9b55 | 357 | let title = gettext('Restore') + ": " + (me.vmtype === 'lxc' ? 'CT' : 'VM'); |
185a77e5 | 358 | if (me.vmid) { |
70aaf893 | 359 | title = `${gettext('Overwrite')} ${title} ${me.vmid}`; |
185a77e5 | 360 | } |
d7683c68 DM |
361 | |
362 | Ext.apply(me, { | |
363 | title: title, | |
d3de9b55 TL |
364 | items: [ |
365 | { | |
366 | xtype: 'form', | |
367 | bodyPadding: 10, | |
368 | border: false, | |
369 | fieldDefaults: { | |
370 | labelWidth: 100, | |
371 | anchor: '100%', | |
372 | }, | |
373 | items: items, | |
374 | }, | |
375 | ], | |
376 | buttons: [ | |
377 | { | |
378 | text: gettext('Restore'), | |
379 | reference: 'doRestoreBtn', | |
380 | handler: 'doRestore', | |
381 | }, | |
382 | ], | |
d7683c68 DM |
383 | }); |
384 | ||
385 | me.callParent(); | |
f6710aac | 386 | }, |
d7683c68 | 387 | }); |