pve-manager/www/manager6/window/Restore.js
Fiona Ebner a2b8ac2e21 ui: backup restore: validate name
Use the same validation as the guest creation wizards do to catch
errors early, before sending the API request.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-04-24 12:09:52 +02:00

388 lines
9.1 KiB
JavaScript

Ext.define('PVE.window.Restore', {
extend: 'Ext.window.Window', // fixme: Proxmox.window.Edit?
resizable: false,
width: 500,
modal: true,
layout: 'auto',
border: false,
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";
}
let storagesel = Ext.create('PVE.form.StorageSelector', {
nodename: me.nodename,
name: 'storage',
value: '',
fieldLabel: gettext('Storage'),
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',
});
let items = [
{
xtype: 'displayfield',
value: me.volidText || me.volid,
fieldLabel: gettext('Source'),
},
storagesel,
{
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',
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."),
},
},
{
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: '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,
});
}
items.push({
xtype: 'fieldset',
title: `${gettext('Override Settings')}:`,
layout: 'hbox',
defaults: {
border: false,
layout: 'anchor',
flex: 1,
},
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,
}],
},
{
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',
}],
},
],
});
let title = gettext('Restore') + ": " + (me.vmtype === 'lxc' ? 'CT' : 'VM');
if (me.vmid) {
title = `${gettext('Overwrite')} ${title} ${me.vmid}`;
}
Ext.apply(me, {
title: title,
items: [
{
xtype: 'form',
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%',
},
items: items,
},
],
buttons: [
{
text: gettext('Restore'),
reference: 'doRestoreBtn',
handler: 'doRestore',
},
],
});
me.callParent();
},
});