From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 88517F0BB for ; Thu, 20 Jul 2023 16:33:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7616014D89 for ; Thu, 20 Jul 2023 16:33:29 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Thu, 20 Jul 2023 16:33:26 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 8303741E7F for ; Thu, 20 Jul 2023 16:33:15 +0200 (CEST) From: Lukas Wagner To: pve-devel@lists.proxmox.com Date: Thu, 20 Jul 2023 16:32:26 +0200 Message-Id: <20230720143236.652292-60-l.wagner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230720143236.652292-1-l.wagner@proxmox.com> References: <20230720143236.652292-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.072 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pve-devel] [PATCH v4 pve-manager 59/69] ui: allow to configure notification event -> target mapping X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 20 Jul 2023 14:33:42 -0000 Signed-off-by: Lukas Wagner --- Notes: Changes since v3: - Show warnings only if 'never' is selected - Also show a warning for disabled package update notifications - Some code style touch ups - Added some comments www/manager6/Makefile | 1 + www/manager6/dc/Config.js | 12 ++ www/manager6/dc/NotificationEvents.js | 277 ++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 www/manager6/dc/NotificationEvents.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 140b20f0..452abbd4 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -158,6 +158,7 @@ JSSRC= \ dc/Health.js \ dc/Log.js \ dc/NodeView.js \ + dc/NotificationEvents.js \ dc/OptionView.js \ dc/PermissionView.js \ dc/PoolEdit.js \ diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js index 04ed04f0..aa025c8d 100644 --- a/www/manager6/dc/Config.js +++ b/www/manager6/dc/Config.js @@ -317,6 +317,18 @@ Ext.define('PVE.dc.Config', { ); } + if (caps.dc['Sys.Audit']) { + me.items.push( + { + xtype: 'pveNotificationEvents', + title: gettext('Notifications'), + onlineHelp: 'notification_events', + iconCls: 'fa fa-bell-o', + itemId: 'notifications', + }, + ); + } + if (caps.dc['Sys.Audit']) { me.items.push({ xtype: 'pveDcSupport', diff --git a/www/manager6/dc/NotificationEvents.js b/www/manager6/dc/NotificationEvents.js new file mode 100644 index 00000000..d233c70b --- /dev/null +++ b/www/manager6/dc/NotificationEvents.js @@ -0,0 +1,277 @@ +Ext.define('PVE.dc.NotificationEventsPolicySelector', { + alias: ['widget.pveNotificationEventsPolicySelector'], + extend: 'Proxmox.form.KVComboBox', + deleteEmpty: false, + value: '__default__', + + config: { + warningRef: null, + warnIfValIs: null, + }, + + listeners: { + change: function(field, newValue) { + let me = this; + if (!me.warningRef && !me.warnIfValIs) { + return; + } + + let warningField = field.nextSibling( + `displayfield[reference=${me.warningRef}]` + ); + warningField.setVisible(newValue === me.warnIfValIs); + }, + }, +}); + +Ext.define('PVE.dc.NotificationEventDisabledWarning', { + alias: ['widget.pveNotificationEventDisabledWarning'], + extend: 'Ext.form.field.Display', + userCls: 'pmx-hint', + hidden: true, + value: gettext('Disabling notifications is not ' + + 'recommended for production systems!'), +}); + +Ext.define('PVE.dc.NotificationEventsTargetSelector', { + alias: ['widget.pveNotificationEventsTargetSelector'], + extend: 'PVE.form.NotificationTargetSelector', + fieldLabel: gettext('Notification Target'), + allowBlank: true, + editable: true, + autoSelect: false, + deleteEmpty: false, + emptyText: `${Proxmox.Utils.defaultText} (${gettext("mail-to-root")})`, +}); + +Ext.define('PVE.dc.NotificationEvents', { + extend: 'Proxmox.grid.ObjectGrid', + alias: ['widget.pveNotificationEvents'], + + // Taken from OptionView.js, but adapted slightly. + // The modified version allows us to have multiple rows in the ObjectGrid + // for the same underlying property (notify). + // Every setting is eventually stored as a property string in the + // notify key of datacenter.cfg. + // When updating 'notify', all properties that were already set + // also have to be submitted, even if they were not modified. + // This means that we need to save the old value somewhere. + addInputPanelRow: function(name, propertyName, text, opts) { + let me = this; + + opts = opts || {}; + me.rows = me.rows || {}; + + me.rows[name] = { + required: true, + defaultValue: opts.defaultValue, + header: text, + renderer: opts.renderer, + name: propertyName, + editor: { + xtype: 'proxmoxWindowEdit', + width: opts.width || 400, + subject: text, + onlineHelp: opts.onlineHelp, + fieldDefaults: { + labelWidth: opts.labelWidth || 150, + }, + setValues: function(values) { + let value = values[propertyName]; + + if (opts.parseBeforeSet) { + value = PVE.Parser.parsePropertyString(value); + } + + Ext.Array.each(this.query('inputpanel'), function(panel) { + panel.setValues(value); + + // Save the original value + panel.originalValue = { + ...value, + }; + }); + }, + url: opts.url, + items: [{ + xtype: 'inputpanel', + onGetValues: function(values) { + let fields = this.config.items.map(field => field.name).filter(n => n); + + // Restore old, unchanged values + for (const [key, value] of Object.entries(this.originalValue)) { + if (!fields.includes(key)) { + values[key] = value; + } + } + + let value = {}; + if (Object.keys(values).length > 0) { + value[propertyName] = PVE.Parser.printPropertyString(values); + } else { + Proxmox.Utils.assemble_field_data(value, { 'delete': propertyName }); + } + + return value; + }, + items: opts.items, + }], + }, + }; + }, + + initComponent: function() { + let me = this; + + // Helper function for rendering the property + // Needed since the actual value is always stored in the 'notify' property + let render_value = (store, target_key, mode_key, default_val) => { + let value = store.getById('notify')?.get('value') ?? {}; + let target = value[target_key] ?? gettext('mail-to-root'); + let template; + + switch (value[mode_key]) { + case 'always': + template = gettext('Always, notify via target \'{0}\''); + break; + case 'never': + template = gettext('Never'); + break; + case 'auto': + template = gettext('Automatically, notify via target \'{0}\''); + break; + default: + template = gettext('{1} ({2}), notify via target \'{0}\''); + break; + } + + return Ext.String.format(template, target, Proxmox.Utils.defaultText, default_val); + }; + + me.addInputPanelRow('fencing', 'notify', gettext('Node Fencing'), { + renderer: (value, metaData, record, rowIndex, colIndex, store) => + render_value(store, 'target-fencing', 'fencing', gettext('Always')), + url: "/api2/extjs/cluster/options", + items: [ + { + xtype: 'pveNotificationEventsPolicySelector', + name: 'fencing', + fieldLabel: gettext('Notify'), + comboItems: [ + ['__default__', `${Proxmox.Utils.defaultText} (${gettext('Always')})`], + ['always', gettext('Always')], + ['never', gettext('Never')], + ], + warningRef: 'warning', + warnIfValIs: 'never', + }, + { + xtype: 'pveNotificationEventsTargetSelector', + name: 'target-fencing', + }, + { + xtype: 'pveNotificationEventDisabledWarning', + reference: 'warning' + }, + ], + }); + + me.addInputPanelRow('replication', 'notify', gettext('Replication'), { + renderer: (value, metaData, record, rowIndex, colIndex, store) => + render_value(store, 'target-replication', 'replication', gettext('Always')), + url: "/api2/extjs/cluster/options", + items: [ + { + xtype: 'pveNotificationEventsPolicySelector', + name: 'replication', + fieldLabel: gettext('Notify'), + comboItems: [ + ['__default__', `${Proxmox.Utils.defaultText} (${gettext('Always')})`], + ['always', gettext('Always')], + ['never', gettext('Never')], + ], + warningRef: 'warning', + warnIfValIs: 'never', + }, + { + xtype: 'pveNotificationEventsTargetSelector', + name: 'target-replication', + }, + { + xtype: 'pveNotificationEventDisabledWarning', + reference: 'warning' + }, + ], + }); + + me.addInputPanelRow('updates', 'notify', gettext('Package Updates'), { + renderer: (value, metaData, record, rowIndex, colIndex, store) => + render_value( + store, + 'target-package-updates', + 'package-updates', + gettext('Automatically') + ), + url: "/api2/extjs/cluster/options", + items: [ + { + xtype: 'pveNotificationEventsPolicySelector', + name: 'package-updates', + fieldLabel: gettext('Notify'), + comboItems: [ + [ + '__default__', + `${Proxmox.Utils.defaultText} (${gettext('Automatically')})` + ], + ['auto', gettext('Automatically')], + ['always', gettext('Always')], + ['never', gettext('Never')], + ], + warningRef: 'warning', + warnIfValIs: 'never', + }, + { + xtype: 'pveNotificationEventsTargetSelector', + name: 'target-package-updates', + }, + { + xtype: 'pveNotificationEventDisabledWarning', + reference: 'warning' + }, + ], + }); + + // Hack: Also load the notify property to make it accessible + // for our render functions. + me.rows.notify = { + visible: false, + }; + + me.selModel = Ext.create('Ext.selection.RowModel', {}); + + Ext.apply(me, { + tbar: [{ + text: gettext('Edit'), + xtype: 'proxmoxButton', + disabled: true, + handler: () => 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(); + + me.on('activate', me.rstore.startUpdate); + me.on('destroy', me.rstore.stopUpdate); + me.on('deactivate', me.rstore.stopUpdate); + }, +}); -- 2.39.2