pve-manager/www/manager6/dc/OptionView.js
Dominik Csapak 8d8ba23da9 ui: implement tag ordering as configured in datacenter config
datacenter.cfg returns an 'ordering' option. parse that and
use it to order the tags when viewing. default is alphabetical.

With alphabetical ordering, drag & drop when editing is disabled and the
tags will be inserted at the right place. When saving, the sorted
order will be written into the config

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-17 18:21:23 +01:00

492 lines
13 KiB
JavaScript

Ext.define('PVE.dc.OptionView', {
extend: 'Proxmox.grid.ObjectGrid',
alias: ['widget.pveDcOptionView'],
onlineHelp: 'datacenter_configuration_file',
monStoreErrors: true,
userCls: 'proxmox-tags-full',
add_inputpanel_row: function(name, text, opts) {
var me = this;
opts = opts || {};
me.rows = me.rows || {};
let canEdit = !Object.prototype.hasOwnProperty.call(opts, 'caps') || opts.caps;
me.rows[name] = {
required: true,
defaultValue: opts.defaultValue,
header: text,
renderer: opts.renderer,
editor: canEdit ? {
xtype: 'proxmoxWindowEdit',
width: opts.width || 350,
subject: text,
onlineHelp: opts.onlineHelp,
fieldDefaults: {
labelWidth: opts.labelWidth || 100,
},
setValues: function(values) {
var edit_value = values[name];
if (opts.parseBeforeSet) {
edit_value = PVE.Parser.parsePropertyString(edit_value);
}
Ext.Array.each(this.query('inputpanel'), function(panel) {
panel.setValues(edit_value);
});
},
url: opts.url,
items: [{
xtype: 'inputpanel',
onGetValues: function(values) {
if (values === undefined || Object.keys(values).length === 0) {
return { 'delete': name };
}
var ret_val = {};
ret_val[name] = PVE.Parser.printPropertyString(values);
return ret_val;
},
items: opts.items,
}],
} : undefined,
};
},
render_bwlimits: function(value) {
if (!value) {
return gettext("None");
}
let parsed = PVE.Parser.parsePropertyString(value);
return Object.entries(parsed)
.map(([k, v]) => k + ": " + Proxmox.Utils.format_size(v * 1024) + "/s")
.join(',');
},
initComponent: function() {
var me = this;
me.add_combobox_row('keyboard', gettext('Keyboard Layout'), {
renderer: PVE.Utils.render_kvm_language,
comboItems: Object.entries(PVE.Utils.kvm_keymaps),
defaultValue: '__default__',
deleteEmpty: true,
});
me.add_text_row('http_proxy', gettext('HTTP proxy'), {
defaultValue: Proxmox.Utils.noneText,
vtype: 'HttpProxy',
deleteEmpty: true,
});
me.add_combobox_row('console', gettext('Console Viewer'), {
renderer: PVE.Utils.render_console_viewer,
comboItems: Object.entries(PVE.Utils.console_map),
defaultValue: '__default__',
deleteEmpty: true,
});
me.add_text_row('email_from', gettext('Email from address'), {
deleteEmpty: true,
vtype: 'proxmoxMail',
defaultValue: 'root@$hostname',
});
me.add_text_row('mac_prefix', gettext('MAC address prefix'), {
deleteEmpty: true,
vtype: 'MacPrefix',
defaultValue: Proxmox.Utils.noneText,
});
me.add_inputpanel_row('migration', gettext('Migration Settings'), {
renderer: PVE.Utils.render_as_property_string,
labelWidth: 120,
url: "/api2/extjs/cluster/options",
defaultKey: 'type',
items: [{
xtype: 'displayfield',
name: 'type',
fieldLabel: gettext('Type'),
value: 'secure',
submitValue: true,
}, {
xtype: 'proxmoxNetworkSelector',
name: 'network',
fieldLabel: gettext('Network'),
value: null,
emptyText: Proxmox.Utils.defaultText,
autoSelect: false,
skipEmptyText: true,
}],
});
me.add_inputpanel_row('ha', gettext('HA Settings'), {
renderer: PVE.Utils.render_dc_ha_opts,
labelWidth: 120,
url: "/api2/extjs/cluster/options",
onlineHelp: 'ha_manager_shutdown_policy',
items: [{
xtype: 'proxmoxKVComboBox',
name: 'shutdown_policy',
fieldLabel: gettext('Shutdown Policy'),
deleteEmpty: false,
value: '__default__',
comboItems: [
['__default__', Proxmox.Utils.defaultText + ' (conditional)'],
['freeze', 'freeze'],
['failover', 'failover'],
['migrate', 'migrate'],
['conditional', 'conditional'],
],
defaultValue: '__default__',
}],
});
me.add_inputpanel_row('u2f', gettext('U2F Settings'), {
renderer: v => !v ? Proxmox.Utils.NoneText : PVE.Parser.printPropertyString(v),
width: 450,
url: "/api2/extjs/cluster/options",
onlineHelp: 'pveum_configure_u2f',
items: [{
xtype: 'textfield',
name: 'appid',
fieldLabel: gettext('U2F AppID URL'),
emptyText: gettext('Defaults to origin'),
value: '',
deleteEmpty: true,
skipEmptyText: true,
submitEmptyText: false,
}, {
xtype: 'textfield',
name: 'origin',
fieldLabel: gettext('U2F Origin'),
emptyText: gettext('Defaults to requesting host URI'),
value: '',
deleteEmpty: true,
skipEmptyText: true,
submitEmptyText: false,
},
{
xtype: 'box',
height: 25,
html: `<span class='pmx-hint'>${gettext('Note:')}</span> `
+ Ext.String.format(gettext('{0} is deprecated, use {1}'), 'U2F', 'WebAuthn'),
},
{
xtype: 'displayfield',
userCls: 'pmx-hint',
value: gettext('NOTE: Changing an AppID breaks existing U2F registrations!'),
}],
});
me.add_inputpanel_row('webauthn', gettext('WebAuthn Settings'), {
renderer: v => !v ? Proxmox.Utils.NoneText : PVE.Parser.printPropertyString(v),
width: 450,
url: "/api2/extjs/cluster/options",
onlineHelp: 'pveum_configure_webauthn',
items: [{
xtype: 'textfield',
fieldLabel: gettext('Name'),
name: 'rp', // NOTE: relying party consists of name and id, this is the name
allowBlank: false,
},
{
xtype: 'textfield',
fieldLabel: gettext('Origin'),
emptyText: Ext.String.format(gettext("Domain Lockdown (e.g., {0})"), document.location.origin),
name: 'origin',
allowBlank: true,
},
{
xtype: 'textfield',
fieldLabel: 'ID',
name: 'id',
allowBlank: false,
listeners: {
dirtychange: (f, isDirty) =>
f.up('panel').down('box[id=idChangeWarning]').setHidden(!f.originalValue || !isDirty),
},
},
{
xtype: 'container',
layout: 'hbox',
items: [
{
xtype: 'box',
flex: 1,
},
{
xtype: 'button',
text: gettext('Auto-fill'),
iconCls: 'fa fa-fw fa-pencil-square-o',
handler: function(button, ev) {
let panel = this.up('panel');
let fqdn = document.location.hostname;
panel.down('field[name=rp]').setValue(fqdn);
let idField = panel.down('field[name=id]');
let currentID = idField.getValue();
if (!currentID || currentID.length === 0) {
idField.setValue(fqdn);
}
},
},
],
},
{
xtype: 'box',
height: 25,
html: `<span class='pmx-hint'>${gettext('Note:')}</span> `
+ gettext('WebAuthn requires using a trusted certificate.'),
},
{
xtype: 'box',
id: 'idChangeWarning',
hidden: true,
padding: '5 0 0 0',
html: '<i class="fa fa-exclamation-triangle warning"></i> '
+ gettext('Changing the ID breaks existing WebAuthn TFA entries.'),
}],
});
me.add_inputpanel_row('bwlimit', gettext('Bandwidth Limits'), {
renderer: me.render_bwlimits,
width: 450,
url: "/api2/extjs/cluster/options",
parseBeforeSet: true,
labelWidth: 120,
items: [{
xtype: 'pveBandwidthField',
name: 'default',
fieldLabel: gettext('Default'),
emptyText: gettext('none'),
backendUnit: "KiB",
},
{
xtype: 'pveBandwidthField',
name: 'restore',
fieldLabel: gettext('Backup Restore'),
emptyText: gettext('default'),
backendUnit: "KiB",
},
{
xtype: 'pveBandwidthField',
name: 'migration',
fieldLabel: gettext('Migration'),
emptyText: gettext('default'),
backendUnit: "KiB",
},
{
xtype: 'pveBandwidthField',
name: 'clone',
fieldLabel: gettext('Clone'),
emptyText: gettext('default'),
backendUnit: "KiB",
},
{
xtype: 'pveBandwidthField',
name: 'move',
fieldLabel: gettext('Disk Move'),
emptyText: gettext('default'),
backendUnit: "KiB",
}],
});
me.add_integer_row('max_workers', gettext('Maximal Workers/bulk-action'), {
deleteEmpty: true,
defaultValue: 4,
minValue: 1,
maxValue: 64, // arbitrary but generous limit as limits are good
});
me.add_inputpanel_row('next-id', gettext('Next Free VMID Range'), {
renderer: PVE.Utils.render_as_property_string,
url: "/api2/extjs/cluster/options",
items: [{
xtype: 'proxmoxintegerfield',
name: 'lower',
fieldLabel: gettext('Lower'),
emptyText: '100',
minValue: 100,
maxValue: 1000 * 1000 * 1000 - 1,
submitValue: true,
}, {
xtype: 'proxmoxintegerfield',
name: 'upper',
fieldLabel: gettext('Upper'),
emptyText: '1.000.000',
minValue: 100,
maxValue: 1000 * 1000 * 1000 - 1,
submitValue: true,
}],
});
me.rows['tag-style'] = {
required: true,
renderer: (value) => {
if (value === undefined) {
return gettext('No Overrides');
}
let colors = PVE.Utils.parseTagOverrides(value?.['color-map']);
let shape = value.shape;
let shapeText = PVE.Utils.tagTreeStyles[shape ?? '__default__'];
let txt = Ext.String.format(gettext("Tree Shape: {0}"), shapeText);
let orderText = PVE.Utils.tagOrderOptions[value.ordering ?? '__default__'];
txt += `, ${Ext.String.format(gettext("Ordering: {0}"), orderText)}`;
if (Object.keys(colors).length > 0) {
txt += ', ';
}
for (const tag of Object.keys(colors)) {
txt += Proxmox.Utils.getTagElement(tag, colors);
}
return txt;
},
header: gettext('Tag Style Override'),
editor: {
xtype: 'proxmoxWindowEdit',
width: 800,
subject: gettext('Tag Color Override'),
onlineHelp: 'datacenter_configuration_file',
fieldDefaults: {
labelWidth: 100,
},
url: '/api2/extjs/cluster/options',
items: [
{
xtype: 'inputpanel',
setValues: function(values) {
if (values === undefined) {
return undefined;
}
values = values?.['tag-style'] ?? {};
values.shape = values.shape || '__default__';
values.colors = values['color-map'];
return Proxmox.panel.InputPanel.prototype.setValues.call(this, values);
},
onGetValues: function(values) {
let style = {};
if (values.colors) {
style['color-map'] = values.colors;
}
if (values.shape) {
style.shape = values.shape;
}
if (values.ordering) {
style.ordering = values.ordering;
}
let value = PVE.Parser.printPropertyString(style);
if (value === '') {
return {
'delete': 'tag-style',
};
}
return {
'tag-style': value,
};
},
items: [
{
name: 'shape',
xtype: 'proxmoxKVComboBox',
fieldLabel: gettext('Tree Shape'),
comboItems: Object.entries(PVE.Utils.tagTreeStyles),
defaultValue: '__default__',
deleteEmpty: true,
},
{
name: 'ordering',
xtype: 'proxmoxKVComboBox',
fieldLabel: gettext('Ordering'),
comboItems: Object.entries(PVE.Utils.tagOrderOptions),
defaultValue: '__default__',
value: '__default__',
deleteEmpty: true,
},
{
xtype: 'displayfield',
fieldLabel: gettext('Color Overrides'),
},
{
name: 'colors',
xtype: 'pveTagColorGrid',
deleteEmpty: true,
height: 300,
},
],
},
],
},
};
me.rows['user-tag-access'] = {
required: true,
renderer: (value) => {
if (value === undefined) {
return Ext.String.format(gettext('Mode: {0}'), 'free');
}
let mode = value?.['user-allow'] ?? 'free';
let list = value?.['user-allow-list'].join(',');
let modeTxt = Ext.String.format(gettext('Mode {0}'), mode);
let overrides = PVE.Utils.tagOverrides;
let tags = PVE.Utils.renderTags(list, overrides);
return `${modeTxt}, ${gettext('Pre-defined:')} ${tags}`;
},
header: gettext('User Tag Access'),
editor: {
xtype: 'pveUserTagAccessEdit',
},
};
me.rows['registered-tags'] = {
required: true,
renderer: (value) => {
if (value === undefined) {
return gettext('No Registered Tags');
}
let overrides = PVE.Utils.tagOverrides;
return PVE.Utils.renderTags(value.join(','), overrides);
},
header: gettext('Registered Tags'),
editor: {
xtype: 'pveRegisteredTagEdit',
},
};
me.selModel = Ext.create('Ext.selection.RowModel', {});
Ext.apply(me, {
tbar: [{
text: gettext('Edit'),
xtype: 'proxmoxButton',
disabled: true,
handler: function() { me.run_editor(); },
selModel: me.selModel,
}],
url: "/api2/json/cluster/options",
editorConfig: {
url: "/api2/extjs/cluster/options",
},
interval: 5000,
cwidth1: 200,
listeners: {
itemdblclick: me.run_editor,
},
});
me.callParent();
// set the new value for the default console
me.mon(me.rstore, 'load', function(store, records, success) {
if (!success) {
return;
}
var rec = store.getById('console');
PVE.UIOptions.console = rec.data.value;
if (rec.data.value === '__default__') {
delete PVE.UIOptions.console;
}
PVE.UIOptions['tag-style'] = store.getById('tag-style')?.data?.value;
PVE.Utils.updateTagSettings(PVE.UIOptions['tag-style']);
});
me.on('activate', me.rstore.startUpdate);
me.on('destroy', me.rstore.stopUpdate);
me.on('deactivate', me.rstore.stopUpdate);
},
});