[pve-devel] [PATCH manager 2/2] add UI for node maintenance enable/disable
Thomas Skinner
thomas at atskinner.net
Tue Aug 26 19:34:36 CEST 2025
On Tue, Aug 26, 2025 at 5:00 AM Daniel Kral <d.kral at proxmox.com> wrote:
>
> Hm, the buttons might be a little ambiguous that these are only for LRM
> entries... But I think it's a good start as there isn't a dedicated list
> for the LRMs which gives more room for action buttons that can be done
> on all items. But let's wait for other feedback.
I wasn't sure where the best place for buttons to go would be, but
Thomas Lamprecht suggested in [1] to place in the HA overview, so
that's where I figured it would fit best. I agree with his logic on
needing to check HA status before enabling/disabling the maintenance.
Selectable grid was an easy implementation there and the status was
already being polled.
[1]: https://lore.proxmox.com/pve-devel/90de8fa6-0e31-4273-adf3-ad337ec39446@proxmox.com/
> On Mon Aug 25, 2025 at 6:11 AM CEST, Thomas Skinner wrote:
> > Signed-off-by: Thomas Skinner <thomas at atskinner.net>
> > ---
> > www/manager6/ha/StatusView.js | 85 +++++++++++++++++++++++++++++++++++
> > 1 file changed, 85 insertions(+)
> >
> > diff --git a/www/manager6/ha/StatusView.js b/www/manager6/ha/StatusView.js
> > index 50ad8e84..79e12df5 100644
> > --- a/www/manager6/ha/StatusView.js
> > +++ b/www/manager6/ha/StatusView.js
> > @@ -41,12 +41,58 @@ Ext.define(
> > },
> > });
> >
> > + let sm = Ext.create('Ext.selection.RowModel', {});
> > +
> > + let caps = Ext.state.Manager.get('GuiCap');
> > +
> > + var node_maintenance_disable = function (disable) {
>
> var mustn't be used for new code anymore [0], and new variable names
> should be in camelCase [1].
>
> [0] https://pve.proxmox.com/wiki/Javascript_Style_Guide#Variables
> [1] https://pve.proxmox.com/wiki/Javascript_Style_Guide#Casing
Some copypasta got the best of me here. Will update for v2.
> this could be an arrow function and the function's variable name is
> rather fragile as 'disable' can be set and then does a rather different
> action to the node maintenance.. Maybe just "setNodeMaintenance"?
>
> > + let rec = sm.getSelection()[0];
> > + if (!rec || rec.data.type !== "lrm") {
> > + return;
> > + }
> > + let nodename = rec.get('node');
> > + let enableText = disable ? 'Disable' : 'Enable';
> > + let msg = Ext.String.format(gettext("{0} maintenance mode on node '{1}'?"), enableText, nodename);
> > + Ext.Msg.confirm(gettext('Confirm'), msg, (btn) => {
> > + if (btn === 'yes') {
> > + Proxmox.Utils.API2Request({
> > + params: { disable: disable ? 1 : 0 },
> > + url: '/cluster/ha/nodes/' + nodename + '/maintenance',
> > + method: 'POST',
> > + waitMsgTarget: me,
> > + failure: function (response, opts) {
> > + Ext.Msg.alert(gettext('Error'), response.htmlStatus);
> > + },
> > + });
> > + }
> > + });
> > + };
> > +
> > Ext.apply(me, {
> > store: store,
> > + selModel: sm,
> > stateful: false,
> > viewConfig: {
> > trackOver: false,
> > },
> > + tbar: [
> > + {
> > + text: gettext('Enable Maintenance Mode'),
> > + itemId: 'enableMaintBtn',
> > + disabled: true,
> > + handler: function () {
> > + node_maintenance_disable(false);
> > + },
>
> nit: use an arrow function instead
>
> handler: () => node_maintenance_disable(false),
Oh, I didn't know that was possible. Will fix up this one and the next.
> > + },
> > + {
> > + text: gettext('Disable Maintenance Mode'),
> > + itemId: 'disableMaintBtn',
> > + disabled: true,
> > + handler: function () {
> > + node_maintenance_disable(true);
>
> nit: same here
>
> > + },
> > + },
> > + ],
> > columns: [
> > {
> > header: gettext('Type'),
> > @@ -60,12 +106,50 @@ Ext.define(
> > dataIndex: 'status',
> > },
> > ],
> > + listeners: {
> > + beforeselect: function (tree, record, index, eopts) {
> > + if (!caps.nodes['Sys.Console']) {
> > + return;
> > + }
> > + let enableMaintBtnDisable = true;
> > + let disableMaintBtnDisable = true;
> > + if (record && record.data.type === "lrm") {
> > + if (record.data.lrm_mode && record.data.lrm_mode === 'maintenance') {
> > + disableMaintBtnDisable = false;
> > + } else {
> > + enableMaintBtnDisable = false;
> > + }
> > + }
> > + me.down('#enableMaintBtn').setDisabled(enableMaintBtnDisable);
> > + me.down('#disableMaintBtn').setDisabled(disableMaintBtnDisable);
> > + },
> > + }
> > });
> >
> > me.callParent();
> >
> > me.on('activate', me.rstore.startUpdate);
> > me.on('destroy', me.rstore.stopUpdate);
> > +
> > + me.mon(me.rstore, 'load', function (curstore, results) {
> > + let rec = sm.getSelection()[0];
> > + if (!rec || rec.data.type !== "lrm") {
> > + return;
> > + }
> > + for (const { data } of results) {
> > + switch (data.type) {
> > + case 'lrm':
> > + if (rec.data.node === data.node) {
> > + let inMaint = rec.data.lrm_mode === 'maintenance';
> > + me.down('#enableMaintBtn').setDisabled(inMaint);
> > + me.down('#disableMaintBtn').setDisabled(!inMaint);
> > + }
> > + break;
> > + default:
> > + break;
> > + }
> > + }
> > + });
> > },
> > },
> > function () {
> > @@ -88,6 +172,7 @@ Ext.define(
> > 'type',
> > 'crm_state',
> > 'request_state',
> > + 'lrm_mode',
> > {
> > name: 'vname',
> > convert: function (value, record) {
>
>
More information about the pve-devel
mailing list