Fleecing can be enabled for backup jobs, but not for a manual backup via 'Backup now', where only a default in the node's vzdump.conf applies. So a VM that needs fleecing to not stall during the backup can not be backed up safely from the UI without changing that default. Add the 'Fleecing' and 'Fleecing Storage' fields of the backup job editor, for VMs only, as fleecing is not used for containers. Like there, the storage is required if fleecing is enabled, so the 'Backup' button stays disabled until one is selected. The fields start with the node's defaults. Only send the fleecing option if it was changed. The API checks for 'Datastore.AllocateSpace' on the fleecing storage of the option, but not on the one of the node's default, so always sending it would make manual backups fail for users without that permission on nodes with fleecing enabled by default. Signed-off-by: Michal Fox <me@dualfroz.com>
457 lines
17 KiB
JavaScript
457 lines
17 KiB
JavaScript
Ext.define('PVE.window.Backup', {
|
|
extend: 'Ext.window.Window',
|
|
|
|
resizable: false,
|
|
|
|
initComponent: function () {
|
|
var me = this;
|
|
|
|
if (!me.nodename) {
|
|
throw 'no node name specified';
|
|
}
|
|
|
|
if (!me.vmid) {
|
|
throw 'no VM ID specified';
|
|
}
|
|
|
|
if (!me.vmtype) {
|
|
throw 'no VM type specified';
|
|
}
|
|
|
|
let compressionSelector = Ext.create('PVE.form.BackupCompressionSelector', {
|
|
name: 'compress',
|
|
value: 'zstd',
|
|
fieldLabel: gettext('Compression'),
|
|
});
|
|
|
|
let modeSelector = Ext.create('PVE.form.BackupModeSelector', {
|
|
fieldLabel: gettext('Mode'),
|
|
value: 'snapshot',
|
|
name: 'mode',
|
|
});
|
|
|
|
let mailtoField = Ext.create('Ext.form.field.Text', {
|
|
fieldLabel: gettext('Send email to'),
|
|
name: 'mailto',
|
|
hidden: true,
|
|
emptyText: Proxmox.Utils.noneText,
|
|
});
|
|
|
|
let notificationModeSelector = Ext.create({
|
|
xtype: 'proxmoxKVComboBox',
|
|
comboItems: [
|
|
['notification-system', gettext('Use global settings')],
|
|
// TRANSLATORS: sendmail is a piece of software
|
|
['legacy-sendmail', gettext('Use sendmail (legacy)')],
|
|
],
|
|
fieldLabel: gettext('Notification'),
|
|
name: 'notification-mode',
|
|
value: 'notification-system',
|
|
listeners: {
|
|
change: function (field, value) {
|
|
mailtoField.setHidden(value === 'notification-system');
|
|
},
|
|
},
|
|
});
|
|
|
|
let pbsChangeDetectionModeSelector = Ext.create({
|
|
xtype: 'proxmoxKVComboBox',
|
|
flex: 1,
|
|
disabled: true,
|
|
name: 'pbs-change-detection-mode',
|
|
deleteEmpty: true,
|
|
value: '__default__',
|
|
comboItems: [
|
|
['__default__', 'Default'],
|
|
['data', 'Data'],
|
|
['metadata', 'Metadata'],
|
|
],
|
|
});
|
|
|
|
let pbsChangeDetection = Ext.create('Ext.form.FieldContainer', {
|
|
fieldLabel: gettext('PBS change detection mode'),
|
|
hidden: true,
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'center',
|
|
},
|
|
items: [
|
|
pbsChangeDetectionModeSelector,
|
|
{
|
|
xtype: 'box',
|
|
html: `<i class="fa fa-question-circle" data-qtip="
|
|
${gettext('Mode to detect file changes and switch archive encoding format for container backups to Proxmox Backup Server. Not available for VM backups.')}
|
|
"></i>`,
|
|
padding: 5,
|
|
},
|
|
],
|
|
});
|
|
|
|
const keepNames = [
|
|
['keep-last', gettext('Keep Last')],
|
|
['keep-hourly', gettext('Keep Hourly')],
|
|
['keep-daily', gettext('Keep Daily')],
|
|
['keep-weekly', gettext('Keep Weekly')],
|
|
['keep-monthly', gettext('Keep Monthly')],
|
|
['keep-yearly', gettext('Keep Yearly')],
|
|
];
|
|
|
|
let pruneSettings = keepNames.map((name) =>
|
|
Ext.create('Ext.form.field.Display', {
|
|
name: name[0],
|
|
fieldLabel: name[1],
|
|
hidden: true,
|
|
}),
|
|
);
|
|
|
|
let removeCheckbox = Ext.create('Proxmox.form.Checkbox', {
|
|
name: 'remove',
|
|
checked: false,
|
|
hidden: true,
|
|
uncheckedValue: 0,
|
|
fieldLabel: gettext('Prune'),
|
|
autoEl: {
|
|
tag: 'div',
|
|
'data-qtip': gettext('Prune older backups afterwards'),
|
|
},
|
|
handler: function (checkbox, value) {
|
|
pruneSettings.forEach((field) => field.setHidden(!value));
|
|
me.down('label[name="pruneLabel"]').setHidden(!value);
|
|
},
|
|
});
|
|
|
|
let initialDefaults = false;
|
|
|
|
var storagesel = Ext.create('PVE.form.StorageSelector', {
|
|
nodename: me.nodename,
|
|
name: 'storage',
|
|
fieldLabel: gettext('Storage'),
|
|
storageContent: 'backup',
|
|
allowBlank: false,
|
|
listeners: {
|
|
change: function (f, v) {
|
|
if (!initialDefaults) {
|
|
me.setLoading(false);
|
|
}
|
|
|
|
if (v === null || v === undefined || v === '') {
|
|
return;
|
|
}
|
|
|
|
let store = f.getStore();
|
|
let rec = store.findRecord('storage', v, 0, false, true, true);
|
|
|
|
if (rec && rec.data && rec.data.type === 'pbs') {
|
|
compressionSelector.setValue('zstd');
|
|
compressionSelector.setDisabled(true);
|
|
if (me.vmtype === 'lxc') {
|
|
pbsChangeDetectionModeSelector.setValue('__default__');
|
|
pbsChangeDetectionModeSelector.setDisabled(false);
|
|
pbsChangeDetection.setHidden(false);
|
|
} else {
|
|
pbsChangeDetectionModeSelector.setDisabled(true);
|
|
pbsChangeDetection.setHidden(true);
|
|
}
|
|
} else {
|
|
if (!compressionSelector.getEditable()) {
|
|
compressionSelector.setDisabled(false);
|
|
}
|
|
pbsChangeDetectionModeSelector.setDisabled(true);
|
|
pbsChangeDetection.setHidden(true);
|
|
}
|
|
|
|
Proxmox.Utils.API2Request({
|
|
url: `/nodes/${me.nodename}/vzdump/defaults`,
|
|
method: 'GET',
|
|
params: {
|
|
storage: v,
|
|
},
|
|
waitMsgTarget: me,
|
|
success: function (response, opts) {
|
|
const data = response.result.data;
|
|
|
|
if (!initialDefaults) {
|
|
let notificationMode = data['notification-mode'] ?? 'auto';
|
|
let mailto = data.mailto;
|
|
|
|
if (notificationMode === 'auto' && mailto !== undefined) {
|
|
notificationMode = 'legacy-sendmail';
|
|
}
|
|
if (notificationMode === 'auto' && mailto === undefined) {
|
|
notificationMode = 'notification-system';
|
|
}
|
|
|
|
notificationModeSelector.setValue(notificationMode);
|
|
if (mailto !== undefined) {
|
|
mailtoField.setValue(mailto);
|
|
}
|
|
}
|
|
if (!initialDefaults && data.mode !== undefined) {
|
|
modeSelector.setValue(data.mode);
|
|
}
|
|
if (!initialDefaults && me.vmtype === 'qemu' && data.fleecing) {
|
|
fleecingStorageSelector.setValue(data.fleecing.storage);
|
|
fleecingCheckbox.setValue(data.fleecing.enabled);
|
|
// only changed fleecing settings are sent, so that the node's
|
|
// defaults keep working without permissions on their storage
|
|
fleecingStorageSelector.resetOriginalValue();
|
|
fleecingCheckbox.resetOriginalValue();
|
|
}
|
|
if (!initialDefaults && (data['notes-template'] ?? false)) {
|
|
me.down('field[name=notes-template]').setValue(
|
|
PVE.Utils.unEscapeNotesTemplate(data['notes-template']),
|
|
);
|
|
}
|
|
|
|
initialDefaults = true;
|
|
|
|
// always update storage dependent properties
|
|
if (data['prune-backups'] !== undefined) {
|
|
const keepParams = PVE.Parser.parsePropertyString(
|
|
data['prune-backups'],
|
|
);
|
|
if (!keepParams['keep-all']) {
|
|
removeCheckbox.setHidden(false);
|
|
pruneSettings.forEach(function (field) {
|
|
const keep = keepParams[field.name];
|
|
if (keep) {
|
|
field.setValue(keep);
|
|
} else {
|
|
field.reset();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// no defaults or keep-all=1
|
|
removeCheckbox.setHidden(true);
|
|
removeCheckbox.setValue(false);
|
|
pruneSettings.forEach((field) => field.reset());
|
|
},
|
|
failure: function (response, opts) {
|
|
initialDefaults = true;
|
|
|
|
removeCheckbox.setHidden(true);
|
|
removeCheckbox.setValue(false);
|
|
pruneSettings.forEach((field) => field.reset());
|
|
|
|
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
|
},
|
|
});
|
|
},
|
|
},
|
|
});
|
|
|
|
let protectedCheckbox = Ext.create('Proxmox.form.Checkbox', {
|
|
name: 'protected',
|
|
checked: false,
|
|
uncheckedValue: 0,
|
|
fieldLabel: gettext('Protected'),
|
|
// Tiny amount of padding to stop the UI from shifting
|
|
// when the 'mailto' field is shown.
|
|
padding: '0 0 1 0',
|
|
});
|
|
|
|
let fleecingStorageSelector = Ext.create('PVE.form.StorageSelector', {
|
|
nodename: me.nodename,
|
|
name: 'fleecing-storage',
|
|
fieldLabel: gettext('Fleecing Storage'),
|
|
storageContent: 'images',
|
|
allowBlank: false,
|
|
disabled: true,
|
|
hidden: me.vmtype !== 'qemu',
|
|
});
|
|
|
|
let fleecingCheckbox = Ext.create('Proxmox.form.Checkbox', {
|
|
name: 'fleecing-enabled',
|
|
checked: false,
|
|
hidden: me.vmtype !== 'qemu',
|
|
uncheckedValue: 0,
|
|
fieldLabel: gettext('Fleecing'),
|
|
autoEl: {
|
|
tag: 'div',
|
|
'data-qtip': gettext(
|
|
'Backup write cache that can reduce IO pressure inside guests (VMs only).',
|
|
),
|
|
},
|
|
handler: function (checkbox, value) {
|
|
fleecingStorageSelector.setDisabled(!value);
|
|
fleecingStorageSelector.validate();
|
|
},
|
|
});
|
|
|
|
me.formPanel = Ext.create('Proxmox.panel.InputPanel', {
|
|
bodyPadding: 10,
|
|
border: false,
|
|
column1: [
|
|
storagesel,
|
|
modeSelector,
|
|
protectedCheckbox,
|
|
fleecingCheckbox,
|
|
fleecingStorageSelector,
|
|
pbsChangeDetection,
|
|
],
|
|
column2: [compressionSelector, notificationModeSelector, mailtoField, removeCheckbox],
|
|
columnB: [
|
|
{
|
|
xtype: 'textareafield',
|
|
name: 'notes-template',
|
|
fieldLabel: gettext('Notes'),
|
|
anchor: '100%',
|
|
value: '{{guestname}}',
|
|
},
|
|
{
|
|
xtype: 'box',
|
|
style: {
|
|
margin: '8px 0px',
|
|
'line-height': '1.5em',
|
|
},
|
|
html: Ext.String.format(
|
|
gettext('Possible template variables are: {0}'),
|
|
PVE.Utils.notesTemplateVars.map((v) => `<code>{{${v}}}</code>`).join(', '),
|
|
),
|
|
},
|
|
{
|
|
xtype: 'label',
|
|
name: 'pruneLabel',
|
|
text: gettext('Storage Retention Configuration') + ':',
|
|
hidden: true,
|
|
},
|
|
{
|
|
layout: 'hbox',
|
|
border: false,
|
|
defaults: {
|
|
border: false,
|
|
layout: 'anchor',
|
|
flex: 1,
|
|
},
|
|
items: [
|
|
{
|
|
padding: '0 10 0 0',
|
|
defaults: {
|
|
labelWidth: 110,
|
|
},
|
|
items: [pruneSettings[0], pruneSettings[2], pruneSettings[4]],
|
|
},
|
|
{
|
|
padding: '0 0 0 10',
|
|
defaults: {
|
|
labelWidth: 110,
|
|
},
|
|
items: [pruneSettings[1], pruneSettings[3], pruneSettings[5]],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
var submitBtn = Ext.create('Ext.Button', {
|
|
text: gettext('Backup'),
|
|
handler: function () {
|
|
var storage = storagesel.getValue();
|
|
let values = me.formPanel.getValues();
|
|
var params = {
|
|
storage: storage,
|
|
vmid: me.vmid,
|
|
mode: values.mode,
|
|
remove: values.remove,
|
|
};
|
|
|
|
if (values.mailto) {
|
|
params.mailto = values.mailto;
|
|
}
|
|
|
|
if (values['notification-mode']) {
|
|
params['notification-mode'] = values['notification-mode'];
|
|
}
|
|
|
|
if (values.compress) {
|
|
params.compress = values.compress;
|
|
}
|
|
|
|
if (values.protected) {
|
|
params.protected = values.protected;
|
|
}
|
|
|
|
if (fleecingCheckbox.isDirty() || fleecingStorageSelector.isDirty()) {
|
|
let fleecing = { enabled: values['fleecing-enabled'] };
|
|
if (values['fleecing-enabled']) {
|
|
fleecing.storage = values['fleecing-storage'];
|
|
}
|
|
params.fleecing = PVE.Parser.printPropertyString(fleecing);
|
|
}
|
|
|
|
if (values['pbs-change-detection-mode']) {
|
|
params['pbs-change-detection-mode'] = values['pbs-change-detection-mode'];
|
|
}
|
|
|
|
if (values['notes-template']) {
|
|
params['notes-template'] = PVE.Utils.escapeNotesTemplate(
|
|
values['notes-template'],
|
|
);
|
|
}
|
|
|
|
Proxmox.Utils.API2Request({
|
|
url: '/nodes/' + me.nodename + '/vzdump',
|
|
params: params,
|
|
method: 'POST',
|
|
failure: function (response, opts) {
|
|
Ext.Msg.alert('Error', response.htmlStatus);
|
|
},
|
|
success: function (response, options) {
|
|
// close later so we reload the grid
|
|
// after the task has completed
|
|
me.hide();
|
|
|
|
var upid = response.result.data;
|
|
|
|
var win = Ext.create('Proxmox.window.TaskViewer', {
|
|
upid: upid,
|
|
listeners: {
|
|
close: function () {
|
|
me.close();
|
|
},
|
|
},
|
|
});
|
|
win.show();
|
|
},
|
|
});
|
|
},
|
|
});
|
|
|
|
fleecingStorageSelector.on('validitychange', (field, valid) =>
|
|
submitBtn.setDisabled(!valid),
|
|
);
|
|
|
|
var helpBtn = Ext.create('Proxmox.button.Help', {
|
|
onlineHelp: 'chapter_vzdump',
|
|
listenToGlobalEvent: false,
|
|
hidden: false,
|
|
});
|
|
|
|
let guestTypeStr = me.vmtype === 'lxc' ? 'CT' : 'VM';
|
|
let formattedGuestIdentifier = PVE.Utils.getFormattedGuestIdentifier(me.vmid, me.vmname);
|
|
let title = `${gettext('Backup')} ${guestTypeStr} ${formattedGuestIdentifier}`;
|
|
|
|
Ext.apply(me, {
|
|
title: title,
|
|
modal: true,
|
|
layout: 'auto',
|
|
border: false,
|
|
width: 600,
|
|
items: [me.formPanel],
|
|
buttons: [helpBtn, '->', submitBtn],
|
|
listeners: {
|
|
afterrender: function () {
|
|
/// cleared within the storage selector's change listener
|
|
me.setLoading(gettext('Please wait...'));
|
|
storagesel.setValue(me.storage);
|
|
},
|
|
},
|
|
});
|
|
|
|
me.callParent();
|
|
},
|
|
});
|