]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/window/Restore.js
afac38bafa0f9e712c8f6c5b9689c46bab560b4f
[pve-manager.git] / www / manager6 / window / Restore.js
1 Ext.define('PVE.window.Restore', {
2 extend: 'Ext.window.Window', // fixme: Proxmox.window.Edit?
3
4 resizable: false,
5 width: 500,
6 modal: true,
7 layout: 'auto',
8 border: false,
9
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 },
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 }
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;
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 }
104 },
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) {
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);
136 }
137 });
138
139 if (!allStoragesAvailable) {
140 let storagesel = view.down('pveStorageSelector[name=storage]');
141 storagesel.allowBlank = false;
142 storagesel.setEmptyText('');
143 }
144 },
145 });
146 },
147 },
148
149 initComponent: function() {
150 let me = this;
151
152 if (!me.nodename) {
153 throw "no node name specified";
154 }
155 if (!me.volid) {
156 throw "no volume ID specified";
157 }
158 if (!me.vmtype) {
159 throw "no vmtype specified";
160 }
161
162 let storagesel = Ext.create('PVE.form.StorageSelector', {
163 nodename: me.nodename,
164 name: 'storage',
165 value: '',
166 fieldLabel: gettext('Storage'),
167 storageContent: me.vmtype === 'lxc' ? 'rootdir' : 'images',
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',
171 emptyText: me.vmtype === 'lxc' ? '' : gettext('From backup configuration'),
172 autoSelect: me.vmtype === 'lxc',
173 });
174
175 let items = [
176 {
177 xtype: 'displayfield',
178 value: me.volidText || me.volid,
179 fieldLabel: gettext('Source'),
180 },
181 storagesel,
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 },
195 {
196 xtype: 'pveBandwidthField',
197 name: 'bwlimit',
198 backendUnit: 'KiB',
199 allowZero: true,
200 fieldLabel: gettext('Bandwidth Limit'),
201 emptyText: gettext('Defaults to target storage restore limit'),
202 autoEl: {
203 tag: 'div',
204 'data-qtip': gettext("Use '0' to disable all bandwidth limits."),
205 },
206 },
207 {
208 xtype: 'fieldcontainer',
209 layout: 'hbox',
210 items: [{
211 xtype: 'proxmoxcheckbox',
212 name: 'unique',
213 fieldLabel: gettext('Unique'),
214 flex: 1,
215 autoEl: {
216 tag: 'div',
217 'data-qtip': gettext('Autogenerate unique properties, e.g., MAC addresses'),
218 },
219 checked: false,
220 },
221 {
222 xtype: 'proxmoxcheckbox',
223 name: 'start',
224 reference: 'start',
225 flex: 1,
226 fieldLabel: gettext('Start after restore'),
227 labelWidth: 105,
228 checked: false,
229 }],
230 },
231 ];
232
233 if (me.vmtype === 'lxc') {
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 );
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,
285 },
286 {
287 xtype: 'displayfield',
288 reference: 'liveWarning',
289 // TODO: Remove once more tested/stable?
290 value: gettext('Note: If anything goes wrong during the live-restore, new data written by the VM may be lost.'),
291 userCls: 'pmx-hint',
292 hidden: true,
293 });
294 }
295
296 items.push({
297 xtype: 'fieldset',
298 title: `${gettext('Override Settings')}:`,
299 layout: 'column',
300 defaults: {
301 width: '45%',
302 margin: '0 0 5 10',
303 },
304 items: [
305 {
306 xtype: 'textfield',
307 fieldLabel: me.vmtype === 'lxc' ? gettext('Hostname') : gettext('Name'),
308 name: 'name',
309 reference: 'nameField',
310 allowBlank: true,
311 },
312 {
313 xtype: 'pveMemoryField',
314 fieldLabel: gettext('Memory'),
315 name: 'memory',
316 reference: 'memoryField',
317 value: '',
318 allowBlank: true,
319 },
320 {
321 xtype: 'proxmoxintegerfield',
322 fieldLabel: gettext('Cores'),
323 name: 'cores',
324 reference: 'coresField',
325 minValue: 1,
326 maxValue: 128,
327 allowBlank: true,
328 },
329 {
330 xtype: 'proxmoxintegerfield',
331 fieldLabel: gettext('Sockets'),
332 name: 'sockets',
333 reference: 'socketsField',
334 minValue: 1,
335 maxValue: 4,
336 allowBlank: true,
337 hidden: me.vmtype !== 'qemu',
338 disabled: me.vmtype !== 'qemu',
339 },
340 ],
341 });
342
343 let title = gettext('Restore') + ": " + (me.vmtype === 'lxc' ? 'CT' : 'VM');
344 if (me.vmid) {
345 title = `${gettext('Overwrite')} ${title} ${me.vmid}`;
346 }
347
348 Ext.apply(me, {
349 title: title,
350 items: [
351 {
352 xtype: 'form',
353 bodyPadding: 10,
354 border: false,
355 fieldDefaults: {
356 labelWidth: 100,
357 anchor: '100%',
358 },
359 items: items,
360 },
361 ],
362 buttons: [
363 {
364 text: gettext('Restore'),
365 reference: 'doRestoreBtn',
366 handler: 'doRestore',
367 },
368 ],
369 });
370
371 me.callParent();
372 },
373 });