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