Files
dualfroz 7143720659 fix #6516: ui: backup job: limit the job ID length
The API only allows backup job IDs of up to 50 characters, so mark
longer values as invalid in the edit window already, instead of failing
on submit.

Signed-off-by: Michal Fox <me@dualfroz.com>
2026-09-25 11:31:04 +02:00

308 lines
10 KiB
JavaScript

/*
* 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',
maxLength: 50,
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: `<span class="pmx-hint">${gettext('Note')}</span>: ${gettext(
"The node-specific 'vzdump.conf' or, if this is not set, the default from the config schema is used to determine fallback values.",
)}`,
},
],
});