extend: 'Ext.window.Window', // fixme: Proxmox.window.Edit?
resizable: false,
+ width: 500,
+ modal: true,
+ layout: 'auto',
+ border: false,
- initComponent : function() {
- var me = this;
+ controller: {
+ xclass: 'Ext.app.ViewController',
+ control: {
+ '#liveRestore': {
+ change: function(el, newVal) {
+ let liveWarning = this.lookupReference('liveWarning');
+ liveWarning.setHidden(!newVal);
+ let start = this.lookupReference('start');
+ start.setDisabled(newVal);
+ },
+ },
+ 'form': {
+ validitychange: function(f, valid) {
+ this.lookupReference('doRestoreBtn').setDisabled(!valid);
+ },
+ },
+ },
+
+ doRestore: function() {
+ let me = this;
+ let view = me.getView();
+
+ let values = view.down('form').getForm().getValues();
+
+ let params = {
+ vmid: view.vmid || values.vmid,
+ force: view.vmid ? 1 : 0,
+ };
+ if (values.unique) {
+ params.unique = 1;
+ }
+ if (values.start && !values['live-restore']) {
+ params.start = 1;
+ }
+ if (values['live-restore']) {
+ params['live-restore'] = 1;
+ }
+ if (values.storage) {
+ params.storage = values.storage;
+ }
+
+ ['bwlimit', 'cores', 'name', 'memory', 'sockets'].forEach(opt => {
+ if ((values[opt] ?? '') !== '') {
+ params[opt] = values[opt];
+ }
+ });
+
+ if (params.name && view.vmtype === 'lxc') {
+ params.hostname = params.name;
+ delete params.name;
+ }
+
+ let confirmMsg;
+ if (view.vmtype === 'lxc') {
+ params.ostemplate = view.volid;
+ params.restore = 1;
+ if (values.unprivileged !== 'keep') {
+ params.unprivileged = values.unprivileged;
+ }
+ confirmMsg = Proxmox.Utils.format_task_description('vzrestore', params.vmid);
+ } else if (view.vmtype === 'qemu') {
+ params.archive = view.volid;
+ confirmMsg = Proxmox.Utils.format_task_description('qmrestore', params.vmid);
+ } else {
+ throw 'unknown VM type';
+ }
+
+ let executeRestore = () => {
+ Proxmox.Utils.API2Request({
+ url: `/nodes/${view.nodename}/${view.vmtype}`,
+ params: params,
+ method: 'POST',
+ waitMsgTarget: view,
+ failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
+ success: function(response, options) {
+ Ext.create('Proxmox.window.TaskViewer', {
+ autoShow: true,
+ upid: response.result.data,
+ });
+ view.close();
+ },
+ });
+ };
+
+ if (view.vmid) {
+ confirmMsg += `. ${Ext.String.format(
+ gettext('This will permanently erase current {0} data.'),
+ view.vmtype === 'lxc' ? 'CT' : 'VM',
+ )}`;
+ if (view.vmtype === 'lxc') {
+ confirmMsg += `<br>${gettext('Mount point volumes are also erased.')}`;
+ }
+ Ext.Msg.confirm(gettext('Confirm'), confirmMsg, function(btn) {
+ if (btn === 'yes') {
+ executeRestore();
+ }
+ });
+ } else {
+ executeRestore();
+ }
+ },
+
+ afterRender: function() {
+ let view = this.getView();
+
+ Proxmox.Utils.API2Request({
+ url: `/nodes/${view.nodename}/vzdump/extractconfig`,
+ method: 'GET',
+ waitMsgTarget: view,
+ params: {
+ volume: view.volid,
+ },
+ failure: response => Ext.Msg.alert('Error', response.htmlStatus),
+ success: function(response, options) {
+ let allStoragesAvailable = true;
+
+ response.result.data.split('\n').forEach(line => {
+ let [_, key, value] = line.match(/^([^:]+):\s*(\S+)\s*$/) ?? [];
+
+ if (!key) {
+ return;
+ }
+
+ if (key === '#qmdump#map') {
+ let match = value.match(/^(\S+):(\S+):(\S*):(\S*):$/) ?? [];
+ // if a /dev/XYZ disk was backed up, ther is no storage hint
+ allStoragesAvailable &&= !!match[3] && !!PVE.data.ResourceStore.getById(
+ `storage/${view.nodename}/${match[3]}`);
+ } else if (key === 'name' || key === 'hostname') {
+ view.lookupReference('nameField').setEmptyText(value);
+ } else if (key === 'memory' || key === 'cores' || key === 'sockets') {
+ view.lookupReference(`${key}Field`).setEmptyText(value);
+ }
+ });
+
+ if (!allStoragesAvailable) {
+ let storagesel = view.down('pveStorageSelector[name=storage]');
+ storagesel.allowBlank = false;
+ storagesel.setEmptyText('');
+ }
+ },
+ });
+ },
+ },
+
+ initComponent: function() {
+ let me = this;
if (!me.nodename) {
throw "no node name specified";
}
-
if (!me.volid) {
throw "no volume ID specified";
}
-
if (!me.vmtype) {
throw "no vmtype specified";
}
- var storagesel = Ext.create('PVE.form.StorageSelector', {
+ let storagesel = Ext.create('PVE.form.StorageSelector', {
nodename: me.nodename,
name: 'storage',
value: '',
fieldLabel: gettext('Storage'),
- storageContent: (me.vmtype === 'lxc') ? 'rootdir' : 'images',
- allowBlank: true
+ storageContent: me.vmtype === 'lxc' ? 'rootdir' : 'images',
+ // when restoring a container without specifying a storage, the backend defaults
+ // to 'local', which is unintuitive and 'rootdir' might not even be allowed on it
+ allowBlank: me.vmtype !== 'lxc',
+ emptyText: me.vmtype === 'lxc' ? '' : gettext('From backup configuration'),
+ autoSelect: me.vmtype === 'lxc',
});
- var IDfield;
- if (me.vmid) {
- IDfield = Ext.create('Ext.form.field.Display', {
- name: 'vmid',
- value: me.vmid,
- fieldLabel: (me.vmtype === 'lxc') ? 'CT' : 'VM'
- });
- } else {
- IDfield = Ext.create('PVE.form.GuestIDSelector', {
- name: 'vmid',
- guestType: me.vmtype,
- loadNextGuestID: true,
- validateExists: false
- });
- }
-
- var items = [
+ let items = [
{
xtype: 'displayfield',
value: me.volidText || me.volid,
- fieldLabel: gettext('Source')
+ fieldLabel: gettext('Source'),
},
storagesel,
- IDfield,
{
- xtype: 'proxmoxintegerfield',
+ xtype: 'pmxDisplayEditField',
+ name: 'vmid',
+ fieldLabel: me.vmtype === 'lxc' ? 'CT' : 'VM',
+ value: me.vmid,
+ editable: !me.vmid,
+ editConfig: {
+ xtype: 'pveGuestIDSelector',
+ guestType: me.vmtype,
+ loadNextFreeID: true,
+ validateExists: false,
+ },
+ },
+ {
+ xtype: 'pveBandwidthField',
name: 'bwlimit',
- fieldLabel: gettext('Read Limit (MiB/s)'),
- minValue: 0,
+ backendUnit: 'KiB',
+ allowZero: true,
+ fieldLabel: gettext('Bandwidth Limit'),
emptyText: gettext('Defaults to target storage restore limit'),
autoEl: {
tag: 'div',
- 'data-qtip': gettext("Use '0' to disable all bandwidth limits.")
- }
- }
+ 'data-qtip': gettext("Use '0' to disable all bandwidth limits."),
+ },
+ },
+ {
+ xtype: 'fieldcontainer',
+ layout: 'hbox',
+ items: [{
+ xtype: 'proxmoxcheckbox',
+ name: 'unique',
+ fieldLabel: gettext('Unique'),
+ flex: 1,
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Autogenerate unique properties, e.g., MAC addresses'),
+ },
+ checked: false,
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'start',
+ reference: 'start',
+ flex: 1,
+ fieldLabel: gettext('Start after restore'),
+ labelWidth: 105,
+ checked: false,
+ }],
+ },
];
if (me.vmtype === 'lxc') {
+ items.push(
+ {
+ xtype: 'radiogroup',
+ fieldLabel: gettext('Privilege Level'),
+ reference: 'noVNCScalingGroup',
+ height: '15px', // renders faster with value assigned
+ layout: {
+ type: 'hbox',
+ algin: 'stretch',
+ },
+ autoEl: {
+ tag: 'div',
+ 'data-qtip':
+ gettext('Choose if you want to keep or override the privilege level of the restored Container.'),
+ },
+ items: [
+ {
+ xtype: 'radiofield',
+ name: 'unprivileged',
+ inputValue: 'keep',
+ boxLabel: gettext('From Backup'),
+ flex: 1,
+ checked: true,
+ },
+ {
+ xtype: 'radiofield',
+ name: 'unprivileged',
+ inputValue: '1',
+ boxLabel: gettext('Unprivileged'),
+ flex: 1,
+ },
+ {
+ xtype: 'radiofield',
+ name: 'unprivileged',
+ inputValue: '0',
+ boxLabel: gettext('Privileged'),
+ flex: 1,
+ //margin: '0 0 0 10',
+ },
+ ],
+ },
+ );
+ } else if (me.vmtype === 'qemu') {
items.push({
xtype: 'proxmoxcheckbox',
- name: 'unprivileged',
- value: true,
- fieldLabel: gettext('Unprivileged container')
+ name: 'live-restore',
+ itemId: 'liveRestore',
+ flex: 1,
+ fieldLabel: gettext('Live restore'),
+ checked: false,
+ hidden: !me.isPBS,
+ },
+ {
+ xtype: 'displayfield',
+ reference: 'liveWarning',
+ // TODO: Remove once more tested/stable?
+ value: gettext('Note: If anything goes wrong during the live-restore, new data written by the VM may be lost.'),
+ userCls: 'pmx-hint',
+ hidden: true,
});
}
- me.formPanel = Ext.create('Ext.form.Panel', {
- bodyPadding: 10,
- border: false,
- fieldDefaults: {
- labelWidth: 100,
- anchor: '100%'
+ items.push({
+ xtype: 'fieldset',
+ title: `${gettext('Override Settings')}:`,
+ layout: 'hbox',
+ defaults: {
+ border: false,
+ layout: 'anchor',
+ flex: 1,
},
- items: items
- });
-
- var form = me.formPanel.getForm();
-
- var doRestore = function(url, params) {
- Proxmox.Utils.API2Request({
- url: url,
- params: params,
- method: 'POST',
- waitMsgTarget: me,
- failure: function (response, opts) {
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+ items: [
+ {
+ padding: '0 10 0 0',
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: me.vmtype === 'lxc' ? gettext('Hostname') : gettext('Name'),
+ name: 'name',
+ vtype: 'DnsName',
+ reference: 'nameField',
+ allowBlank: true,
+ }, {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Cores'),
+ name: 'cores',
+ reference: 'coresField',
+ minValue: 1,
+ maxValue: 128,
+ allowBlank: true,
+ }],
},
- success: function(response, options) {
- var upid = response.result.data;
-
- var win = Ext.create('Proxmox.window.TaskViewer', {
- upid: upid
- });
- win.show();
- me.close();
- }
- });
- };
-
- var submitBtn = Ext.create('Ext.Button', {
- text: gettext('Restore'),
- handler: function(){
- var storage = storagesel.getValue();
- var values = form.getValues();
-
- var params = {
- storage: storage,
- vmid: me.vmid || values.vmid,
- force: me.vmid ? 1 : 0
- };
-
- if (values.bwlimit !== undefined) {
- params.bwlimit = values.bwlimit * 1024;
- }
-
- var url;
- var msg;
- if (me.vmtype === 'lxc') {
- url = '/nodes/' + me.nodename + '/lxc';
- params.ostemplate = me.volid;
- params.restore = 1;
- if (values.unprivileged) { params.unprivileged = 1; }
- msg = Proxmox.Utils.format_task_description('vzrestore', params.vmid);
- } else if (me.vmtype === 'qemu') {
- url = '/nodes/' + me.nodename + '/qemu';
- params.archive = me.volid;
- msg = Proxmox.Utils.format_task_description('qmrestore', params.vmid);
- } else {
- throw 'unknown VM type';
- }
-
- if (me.vmid) {
- msg += '. ' + gettext('This will permanently erase current VM data.');
- Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
- if (btn !== 'yes') {
- return;
- }
- doRestore(url, params);
- });
- } else {
- doRestore(url, params);
- }
- }
- });
-
- form.on('validitychange', function(f, valid) {
- submitBtn.setDisabled(!valid);
+ {
+ padding: '0 0 0 10',
+ items: [
+ {
+ xtype: 'pveMemoryField',
+ fieldLabel: gettext('Memory'),
+ name: 'memory',
+ reference: 'memoryField',
+ value: '',
+ allowBlank: true,
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Sockets'),
+ name: 'sockets',
+ reference: 'socketsField',
+ minValue: 1,
+ maxValue: 4,
+ allowBlank: true,
+ hidden: me.vmtype !== 'qemu',
+ disabled: me.vmtype !== 'qemu',
+ }],
+ },
+ ],
});
- var title = gettext('Restore') + ": " + (
- (me.vmtype === 'lxc') ? 'CT' : 'VM');
-
+ let title = gettext('Restore') + ": " + (me.vmtype === 'lxc' ? 'CT' : 'VM');
if (me.vmid) {
- title += " " + me.vmid;
+ title = `${gettext('Overwrite')} ${title} ${me.vmid}`;
}
Ext.apply(me, {
title: title,
- width: 500,
- modal: true,
- layout: 'auto',
- border: false,
- items: [ me.formPanel ],
- buttons: [ submitBtn ]
+ items: [
+ {
+ xtype: 'form',
+ bodyPadding: 10,
+ border: false,
+ fieldDefaults: {
+ labelWidth: 100,
+ anchor: '100%',
+ },
+ items: items,
+ },
+ ],
+ buttons: [
+ {
+ text: gettext('Restore'),
+ reference: 'doRestoreBtn',
+ handler: 'doRestore',
+ },
+ ],
});
me.callParent();
- }
+ },
});