diff --git a/www/manager6/panel/BackupAdvancedOptions.js b/www/manager6/panel/BackupAdvancedOptions.js new file mode 100644 index 0000000..fe8cc3b --- /dev/null +++ b/www/manager6/panel/BackupAdvancedOptions.js @@ -0,0 +1,306 @@ +/* + * Input panel for advanced backup options intended to be used as part of an edit/create window. + */ +Ext.define('PVE.panel.BackupAdvancedOptions', { + extend: 'Proxmox.panel.InputPanel', + xtype: 'pveBackupAdvancedOptionsPanel', + mixins: ['Proxmox.Mixin.CBind'], + + cbindData: function () { + let me = this; + me.isCreate = !!me.isCreate; + return {}; + }, + + viewModel: { + data: {}, + }, + + controller: { + xclass: 'Ext.app.ViewController', + + toggleFleecing: function (cb, value) { + let me = this; + me.lookup('fleecingStorage').setDisabled(!value); + }, + + control: { + 'proxmoxcheckbox[reference=fleecingEnabled]': { + change: 'toggleFleecing', + }, + }, + }, + + onGetValues: function (formValues) { + let me = this; + if (me.needMask) { + // isMasked() may not yet be true if not rendered once + return {}; + } + + if (!formValues.id && me.isCreate) { + formValues.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13); + } + + let options = {}; + + if (!me.isCreate) { + options.delete = []; // to avoid having to check this all the time + } + const deletePropertyOnEdit = me.isCreate + ? () => { + /* no-op on create */ + } + : (key) => options.delete.push(key); + + let fleecing = {}, + fleecingOptions = ['fleecing-enabled', 'fleecing-storage']; + let performance = {}, + performanceOptions = ['max-workers', 'pbs-entries-max']; + + for (const [key, value] of Object.entries(formValues)) { + if (performanceOptions.includes(key)) { + performance[key] = value; + // deleteEmpty is not currently implemented for pveBandwidthField + } else if (key === 'bwlimit' && value === '') { + deletePropertyOnEdit('bwlimit'); + } else if (key === 'delete') { + if (Array.isArray(value)) { + value + .filter((opt) => !performanceOptions.includes(opt)) + .forEach((opt) => deletePropertyOnEdit(opt)); + } else if (!performanceOptions.includes(formValues.delete)) { + deletePropertyOnEdit(value); + } + } else if (fleecingOptions.includes(key)) { + let fleecingKey = key.slice('fleecing-'.length); + fleecing[fleecingKey] = value; + } else { + options[key] = value; + } + } + + if (Object.keys(performance).length > 0) { + options.performance = PVE.Parser.printPropertyString(performance); + } else { + deletePropertyOnEdit('performance'); + } + + if (Object.keys(fleecing).length > 0) { + options.fleecing = PVE.Parser.printPropertyString(fleecing); + } else { + deletePropertyOnEdit('fleecing'); + } + + if (me.isCreate) { + delete options.delete; + } + + return options; + }, + + onSetValues: function (values) { + if (values.fleecing) { + for (const [key, value] of Object.entries(values.fleecing)) { + values[`fleecing-${key}`] = value; + } + delete values.fleecing; + } + if (values['pbs-change-detection-mode'] === '__default__') { + delete values['pbs-change-detection-mode']; + } + return values; + }, + + updateCompression: function (value, disabled) { + this.lookup('zstdThreadCount').setDisabled(disabled || value !== 'zstd'); + }, + + items: [ + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'pmxDisplayEditField', + vtype: 'ConfigId', + fieldLabel: gettext('Job ID'), + emptyText: gettext('Autogenerate'), + name: 'id', + allowBlank: true, + cbind: { + editable: '{isCreate}', + }, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: gettext('Can be used in notification matchers to match this job.'), + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'pveBandwidthField', + name: 'bwlimit', + fieldLabel: gettext('Bandwidth Limit'), + emptyText: gettext('Fallback'), + backendUnit: 'KiB', + allowZero: true, + emptyValue: '', + autoEl: { + tag: 'div', + 'data-qtip': Ext.String.format(gettext('Use {0} for unlimited'), 0), + }, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: `${gettext('Limit I/O bandwidth.')} ${Ext.String.format(gettext('Schema default: {0}'), 0)}`, + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'proxmoxintegerfield', + name: 'zstd', + reference: 'zstdThreadCount', + fieldLabel: gettext('Zstd Threads'), + fieldStyle: 'text-align: right', + emptyText: gettext('Fallback'), + minValue: 0, + cbind: { + deleteEmpty: '{!isCreate}', + }, + autoEl: { + tag: 'div', + 'data-qtip': gettext('With 0, half of the available cores are used'), + }, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: `${gettext('Threads used for zstd compression (non-PBS).')} ${Ext.String.format(gettext('Schema default: {0}'), 1)}`, + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'proxmoxintegerfield', + name: 'max-workers', + minValue: 1, + maxValue: 256, + fieldLabel: gettext('IO-Workers'), + fieldStyle: 'text-align: right', + emptyText: gettext('Fallback'), + cbind: { + deleteEmpty: '{!isCreate}', + }, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: `${gettext('I/O workers in the QEMU process (VMs only).')} ${Ext.String.format(gettext('Schema default: {0}'), 16)}`, + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'proxmoxcheckbox', + name: 'fleecing-enabled', + reference: 'fleecingEnabled', + fieldLabel: gettext('Fleecing'), + uncheckedValue: 0, + value: 0, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: gettext( + 'Backup write cache that can reduce IO pressure inside guests (VMs only).', + ), + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'pveStorageSelector', + name: 'fleecing-storage', + fieldLabel: gettext('Fleecing Storage'), + reference: 'fleecingStorage', + clusterView: true, + storageContent: 'images', + allowBlank: false, + disabled: true, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: gettext( + 'Prefer a fast and local storage, ideally with support for discard and thin-provisioning or sparse files.', + ), + }, + }, + { + // It's part of the 'performance' property string, so have a field to preserve the + // value, but don't expose it. It's a rather niche setting and difficult to + // convey/understand what it does. + xtype: 'proxmoxintegerfield', + name: 'pbs-entries-max', + hidden: true, + fieldLabel: 'TODO', + fieldStyle: 'text-align: right', + emptyText: 'TODO', + cbind: { + deleteEmpty: '{!isCreate}', + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'proxmoxcheckbox', + fieldLabel: gettext('Repeat missed'), + name: 'repeat-missed', + uncheckedValue: 0, + defaultValue: 0, + cbind: { + deleteDefaultValue: '{!isCreate}', + }, + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: gettext( + "Run jobs as soon as possible if they couldn't start on schedule, for example, due to the node being offline.", + ), + }, + }, + { + xtype: 'pveTwoColumnContainer', + startColumn: { + xtype: 'proxmoxKVComboBox', + fieldLabel: gettext('PBS change detection mode'), + name: 'pbs-change-detection-mode', + deleteEmpty: true, + value: '__default__', + comboItems: [ + ['__default__', 'Default'], + ['data', 'Data'], + ['metadata', 'Metadata'], + ], + }, + endFlex: 2, + endColumn: { + xtype: 'displayfield', + value: gettext( + 'Mode to detect file changes and switch archive encoding format for container backups.', + ), + }, + }, + { + xtype: 'component', + padding: '5 1', + html: `${gettext('Note')}: ${gettext( + "The node-specific 'vzdump.conf' or, if this is not set, the default from the config schema is used to determine fallback values.", + )}`, + }, + ], +});