]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/dc/OptionView.js
ui: datacenter config: adapt to new default MAC prefix
[pve-manager.git] / www / manager6 / dc / OptionView.js
index 74388475fbdb056c412b0a65fdccc2a2cd02a31b..b200fd12dcc935a0597b82213f744ec5567f4d61 100644 (file)
-Ext.define('PVE.dc.HttpProxyEdit', {
-    extend: 'PVE.window.Edit',
-
-    initComponent : function() {
-       var me = this;
-
-       Ext.applyIf(me, {
-           subject: gettext('HTTP proxy'),
-           items: {
-               xtype: 'proxmoxtextfield',
-               name: 'http_proxy',
-               vtype: 'HttpProxy',
-               emptyText: Proxmox.Utils.noneText,
-               deleteEmpty: true,
-               value: '',
-               fieldLabel: gettext('HTTP proxy')
-           }
-       });
-
-       me.callParent();
+Ext.define('PVE.dc.OptionView', {
+    extend: 'Proxmox.grid.ObjectGrid',
+    alias: ['widget.pveDcOptionView'],
 
-       me.load();
-    }
-});
+    onlineHelp: 'datacenter_configuration_file',
 
-Ext.define('PVE.dc.KeyboardEdit', {
-    extend: 'PVE.window.Edit',
+    monStoreErrors: true,
+    userCls: 'proxmox-tags-full',
 
-    initComponent : function() {
+    add_inputpanel_row: function(name, text, opts) {
        var me = this;
 
-       Ext.applyIf(me, {
-           subject: gettext('Keyboard Layout'),
-           items: {
-               xtype: 'VNCKeyboardSelector',
-               name: 'keyboard',
-               value: '__default__',
-               fieldLabel: gettext('Keyboard Layout')
-           }
-       });
+       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);
+                   }
 
-       me.callParent();
+                   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,
+       };
+    },
 
-       me.load();
-    }
-});
+    render_bwlimits: function(value) {
+       if (!value) {
+           return gettext("None");
+       }
 
-Ext.define('PVE.dc.ConsoleViewerEdit', {
-    extend: 'PVE.window.Edit',
+       let parsed = PVE.Parser.parsePropertyString(value);
+       return Object.entries(parsed)
+           .map(([k, v]) => k + ": " + Proxmox.Utils.format_size(v * 1024) + "/s")
+           .join(',');
+    },
 
-    initComponent : function() {
+    initComponent: function() {
        var me = this;
 
-       var data = [];
-
-       Ext.Array.each(['__default__','vv', 'html5'], function(value) {
-           data.push([value, PVE.Utils.render_console_viewer(value)]);
+       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,
        });
-
-       Ext.applyIf(me, {
-           subject: gettext('Console Viewer'),
-           items: {
+       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: 'BC:24:11',
+       });
+       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: 'console',
+               name: 'shutdown_policy',
+               fieldLabel: gettext('Shutdown Policy'),
+               deleteEmpty: false,
                value: '__default__',
-               fieldLabel: gettext('Console Viewer'),
-               comboItems: data
-           }
+               comboItems: [
+                   ['__default__', Proxmox.Utils.defaultText + ' (conditional)'],
+                   ['freeze', 'freeze'],
+                   ['failover', 'failover'],
+                   ['migrate', 'migrate'],
+                   ['conditional', 'conditional'],
+               ],
+               defaultValue: '__default__',
+           }],
        });
-
-       me.callParent();
-
-       me.load();
-    }
-});
-
-Ext.define('PVE.dc.EmailFromEdit', {
-    extend: 'PVE.window.Edit',
-
-    initComponent : function() {
-       var me = this;
-
-       Ext.applyIf(me, {
-           subject: gettext('Email from address'),
-           items: {
-               xtype: 'proxmoxtextfield',
-               name: 'email_from',
-               vtype: 'pveMail',
-               emptyText: 'root@$hostname',
-               deleteEmpty: true,
-               value: '',
-               fieldLabel: gettext('Email from address')
-           }
+       me.add_inputpanel_row('crs', gettext('Cluster Resource Scheduling'), {
+           renderer: PVE.Utils.render_as_property_string,
+           width: 450,
+           labelWidth: 120,
+           url: "/api2/extjs/cluster/options",
+           onlineHelp: 'ha_manager_crs',
+           items: [{
+               xtype: 'proxmoxKVComboBox',
+               name: 'ha',
+               fieldLabel: gettext('HA Scheduling'),
+               deleteEmpty: false,
+               value: '__default__',
+               comboItems: [
+                   ['__default__', Proxmox.Utils.defaultText + ' (basic)'],
+                   ['basic', 'Basic (Resource Count)'],
+                   ['static', 'Static Load'],
+               ],
+               defaultValue: '__default__',
+           }, {
+               xtype: 'proxmoxcheckbox',
+               name: 'ha-rebalance-on-start',
+               fieldLabel: gettext('Rebalance on Start'),
+               boxLabel: gettext('Use CRS to select the least loaded node when starting an HA service'),
+               value: 0,
+           }],
        });
-
-       me.callParent();
-
-       me.load();
-    }
-});
-
-Ext.define('PVE.dc.MacPrefixEdit', {
-    extend: 'PVE.window.Edit',
-
-    initComponent : function() {
-       var me = this;
-
-       Ext.applyIf(me, {
-           subject: gettext('MAC address prefix'),
-           items: {
-               xtype: 'proxmoxtextfield',
-               name: 'mac_prefix',
-               regex: /^[a-f0-9]{2}(?::[a-f0-9]{2}){0,2}:?$/i,
-               regexText: gettext('Example') + ': 02:8f',
-               emptyText: Proxmox.Utils.noneText,
+       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: '',
-               fieldLabel: gettext('MAC address prefix')
-           }
+               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.callParent();
-
-       me.load();
-    }
-});
-
-Ext.define('PVE.dc.OptionView', {
-    extend: 'Proxmox.grid.ObjectGrid',
-    alias: ['widget.pveDcOptionView'],
-
-    onlineHelp: 'datacenter_configuration_file',
-
-    initComponent : function() {
-       var me = this;
-
-       var reload = function() {
-           me.rstore.load();
-       };
-
-       var rows = {
-           keyboard: { 
-               header: gettext('Keyboard Layout'), 
-               editor: 'PVE.dc.KeyboardEdit',
-               renderer: PVE.Utils.render_kvm_language,
-               required: true 
-           },
-           http_proxy: { 
-               header: gettext('HTTP proxy'),
-               editor: 'PVE.dc.HttpProxyEdit', 
-               required: true,
-               renderer: function(value) {
-                   if (!value) {
-                       return Proxmox.Utils.noneText;
-                   }
-                   return value;
-               }
+       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,
            },
-           console: {
-               header: gettext('Console Viewer'),
-               editor: 'PVE.dc.ConsoleViewerEdit',
-               required: true,
-               renderer: PVE.Utils.render_console_viewer
-           },
-           email_from: { 
-               header: gettext('Email from address'),
-               editor: 'PVE.dc.EmailFromEdit', 
-               required: true,
-               renderer: function(value) {
-                   if (!value) {
-                       return 'root@$hostname';
-                   }
-                   return value;
-               }
+           {
+               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",
            },
-           mac_prefix: {
-               header: gettext('MAC address prefix'),
-               editor: 'PVE.dc.MacPrefixEdit',
-               required: true,
-               renderer: function(value) {
-                   if (!value) {
-                       return Proxmox.Utils.noneText;
+           {
+               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.UIOptions.parseTagOverrides(value?.['color-map']);
+               let shape = value.shape;
+               let shapeText = PVE.UIOptions.tagTreeStyles[shape ?? '__default__'];
+               let txt = Ext.String.format(gettext("Tree Shape: {0}"), shapeText);
+               let orderText = PVE.UIOptions.tagOrderOptions[value.ordering ?? '__default__'];
+               txt += `, ${Ext.String.format(gettext("Ordering: {0}"), orderText)}`;
+               if (value['case-sensitive']) {
+                   txt += `, ${gettext('Case-Sensitive')}`;
+               }
+               if (Object.keys(colors).length > 0) {
+                   txt += `, ${gettext('Color Overrides')}: `;
+                   for (const tag of Object.keys(colors)) {
+                       txt += Proxmox.Utils.getTagElement(tag, colors);
                    }
-                   return value;
                }
-           }
+               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 && values.shape !== '__default__') {
+                               style.shape = values.shape;
+                           }
+                           if (values.ordering) {
+                               style.ordering = values.ordering;
+                           }
+                           if (values['case-sensitive']) {
+                               style['case-sensitive'] = 1;
+                           }
+                           let value = PVE.Parser.printPropertyString(style);
+                           if (value === '') {
+                               return {
+                                   'delete': 'tag-style',
+                               };
+                           }
+                           return {
+                               'tag-style': value,
+                           };
+                       },
+                       items: [
+                           {
+
+                               name: 'shape',
+                               xtype: 'proxmoxComboGrid',
+                               fieldLabel: gettext('Tree Shape'),
+                               valueField: 'value',
+                               displayField: 'display',
+                               allowBlank: false,
+                               listConfig: {
+                                   columns: [
+                                       {
+                                           header: gettext('Option'),
+                                           dataIndex: 'display',
+                                           flex: 1,
+                                       },
+                                       {
+                                           header: gettext('Preview'),
+                                           dataIndex: 'value',
+                                           renderer: function(value) {
+                                               let cls = value ?? '__default__';
+                                               if (value === '__default__') {
+                                                   cls = 'circle';
+                                               }
+                                               let tags = PVE.Utils.renderTags('preview');
+                                               return `<div class="proxmox-tags-${cls}">${tags}</div>`;
+                                           },
+                                           flex: 1,
+                                       },
+                                   ],
+                               },
+                               store: {
+                                   data: Object.entries(PVE.UIOptions.tagTreeStyles).map(v => ({
+                                       value: v[0],
+                                       display: v[1],
+                                   })),
+                               },
+                               deleteDefault: true,
+                               defaultValue: '__default__',
+                               deleteEmpty: true,
+                           },
+                           {
+                               name: 'ordering',
+                               xtype: 'proxmoxKVComboBox',
+                               fieldLabel: gettext('Ordering'),
+                               comboItems: Object.entries(PVE.UIOptions.tagOrderOptions),
+                               defaultValue: '__default__',
+                               value: '__default__',
+                               deleteEmpty: true,
+                           },
+                           {
+                               name: 'case-sensitive',
+                               xtype: 'proxmoxcheckbox',
+                               fieldLabel: gettext('Case-Sensitive'),
+                               boxLabel: gettext('Applies to new edits'),
+                               value: 0,
+                           },
+                           {
+                               xtype: 'displayfield',
+                               fieldLabel: gettext('Color Overrides'),
+                           },
+                           {
+                               name: 'colors',
+                               xtype: 'pveTagColorGrid',
+                               deleteEmpty: true,
+                               height: 300,
+                           },
+                       ],
+                   },
+               ],
+           },
        };
 
-       var sm = Ext.create('Ext.selection.RowModel', {});
-
-       var run_editor = function() {
-           var rec = sm.getSelection()[0];
-           if (!rec) {
-               return;
-           }
+       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.UIOptions.tagOverrides;
+               let tags = PVE.Utils.renderTags(list, overrides);
+               let listTxt = tags !== '' ? `, ${gettext('Pre-defined:')} ${tags}` : '';
+               return `${modeTxt}${listTxt}`;
+           },
+           header: gettext('User Tag Access'),
+           editor: {
+               xtype: 'pveUserTagAccessEdit',
+           },
+       };
 
-           var rowdef = rows[rec.data.key];
-           if (!rowdef.editor) {
-               return;
-           }
-           
-           var win = Ext.create(rowdef.editor, {
-               url: "/api2/extjs/cluster/options",
-               confid: rec.data.key
-           });
-           win.show();
-           win.on('destroy', reload);
+       me.rows['registered-tags'] = {
+           required: true,
+           renderer: (value) => {
+               if (value === undefined) {
+                   return gettext('No Registered Tags');
+               }
+               let overrides = PVE.UIOptions.tagOverrides;
+               return PVE.Utils.renderTags(value.join(','), overrides);
+           },
+           header: gettext('Registered Tags'),
+           editor: {
+               xtype: 'pveRegisteredTagEdit',
+           },
        };
 
-       var edit_btn = new Proxmox.button.Button({
-           text: gettext('Edit'),
-           disabled: true,
-           selModel: sm,
-           handler: run_editor
-       });
+       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",
-           interval: 1000,
-           selModel: sm,
-           tbar: [ edit_btn ],
-           rows: rows,
+           editorConfig: {
+               url: "/api2/extjs/cluster/options",
+           },
+           interval: 5000,
+           cwidth1: 200,
            listeners: {
-               itemdblclick: run_editor,
-               activate: reload
-           }
+               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.options.console = rec.data.value;
+           if (rec.data.value === '__default__') {
+               delete PVE.UIOptions.options.console;
+           }
+
+           PVE.UIOptions.options['tag-style'] = store.getById('tag-style')?.data?.value;
+           PVE.UIOptions.updateTagSettings(PVE.UIOptions.options['tag-style']);
+           PVE.UIOptions.fireUIConfigChanged();
+       });
+
+       me.on('activate', me.rstore.startUpdate);
+       me.on('destroy', me.rstore.stopUpdate);
+       me.on('deactivate', me.rstore.stopUpdate);
+    },
 });