mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-08-17 04:18:45 +00:00

The confirmation dialogs of the following actions are affected by this change: * Remove * Clone * Migrate * Snapshot * Snapshot restore * Backup VM/CT from config view * Restore VM/CT from config view A combination of VM/CT id and name is added to each confirmation dialog. The order of id and name depends on the sort field selected in the tree settings. If "Name" is selected, the confirmation dialogs will show "VM name (VMID)". In any other case, "VMID (VM name)" will be used. The VM/CT name is considered optional in all handled cases. If it is undefined, only the VMID will be displayed in the dialog window. No exceptions are thrown in case of an undefined guest name because it only extends the information displayed to the user and is not essential for performing any of the actions above. Signed-off-by: Michael Köppl <m.koeppl@proxmox.com>
390 lines
9.3 KiB
JavaScript
390 lines
9.3 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 taskDescription;
|
|
if (view.vmtype === 'lxc') {
|
|
params.ostemplate = view.volid;
|
|
params.restore = 1;
|
|
if (values.unprivileged !== 'keep') {
|
|
params.unprivileged = values.unprivileged;
|
|
}
|
|
taskDescription = Proxmox.Utils.format_task_description('vzrestore', params.vmid);
|
|
} else if (view.vmtype === 'qemu') {
|
|
params.archive = view.volid;
|
|
taskDescription = Proxmox.Utils.format_task_description('qmrestore', params.vmid);
|
|
} else {
|
|
throw 'unknown VM type';
|
|
}
|
|
let confirmMsg = Ext.htmlEncode(taskDescription);
|
|
|
|
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, there 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) {
|
|
let formattedGuestIdentifier = PVE.Utils.getFormattedGuestIdentifier(me.vmid, me.vmname);
|
|
title = `${gettext('Overwrite')} ${title} ${formattedGuestIdentifier}`;
|
|
}
|
|
|
|
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();
|
|
},
|
|
});
|