public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Michael Köppl" <m.koeppl@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH widget-toolkit v3 4/4] window: add more general base dialog, make SafeDestroy concrete
Date: Tue, 30 Sep 2025 16:58:45 +0200	[thread overview]
Message-ID: <20250930145848.263162-9-m.koeppl@proxmox.com> (raw)
In-Reply-To: <20250930145848.263162-1-m.koeppl@proxmox.com>

Move the functionality of SafeDestroy to ConfirmRemoveDialog and use it
as a base for SafeDestroy. ConfirmRemoveDialog will serve as the general
base implementation of a dialog, offering some customization for remove
dialogs while covering the functionality of the original SafeDestroy
dialog. SafeDestroy is now a special case of ConfirmRemoveDialog. This
also avoids changes for dialog windows extending SafeDestroy.

Also makes ConfirmRemoveDialog a non-dangerous yes/no dialog by default,
setting dangerous = true and the "Remove" in SafeDestroy explicitly.

Signed-off-by: Michael Köppl <m.koeppl@proxmox.com>
---
 src/Makefile                      |   1 +
 src/window/ConfirmRemoveDialog.js | 247 ++++++++++++++++++++++++++++++
 src/window/SafeDestroy.js         | 240 +----------------------------
 3 files changed, 255 insertions(+), 233 deletions(-)
 create mode 100644 src/window/ConfirmRemoveDialog.js

diff --git a/src/Makefile b/src/Makefile
index 0e2b329..05c7b90 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -83,6 +83,7 @@ JSSRC=					\
 	panel/WebhookEditPanel.js	\
 	window/Edit.js			\
 	window/PasswordEdit.js		\
+	window/ConfirmRemoveDialog.js	\
 	window/SafeDestroy.js		\
 	window/PackageVersions.js	\
 	window/TaskViewer.js		\
diff --git a/src/window/ConfirmRemoveDialog.js b/src/window/ConfirmRemoveDialog.js
new file mode 100644
index 0000000..94f1e84
--- /dev/null
+++ b/src/window/ConfirmRemoveDialog.js
@@ -0,0 +1,247 @@
+// Pop-up a message window where the user has to confirm their intent to delete an item.
+// Optionally, an additional textfield can be added, requiring the user to enter the ID
+// of the given item again to confirm their intent.
+Ext.define('Proxmox.window.ConfirmRemoveDialog', {
+    extend: 'Ext.window.Window',
+    alias: 'widget.proxmoxConfirmRemoveDialog',
+
+    title: gettext('Confirm'),
+    modal: true,
+    buttonAlign: 'center',
+    bodyPadding: 10,
+    width: 450,
+    layout: { type: 'hbox' },
+    defaultFocus: 'confirmField',
+    showProgress: false,
+
+    // if set to true, a warning sign will be displayed and entering the ID will
+    // be required before removal is possible. If set to false, a question mark
+    // will be displayed.
+    dangerous: false,
+
+    confirmButtonText: gettext('Yes'),
+    // second button will only be displayed if a text is given
+    declineButtonText: gettext('No'),
+
+    additionalItems: [],
+
+    // gets called if we have a progress bar or taskview and it detected that
+    // the task finished. function(success)
+    taskDone: Ext.emptyFn,
+
+    // gets called when the api call is finished, right at the beginning
+    // function(success, response, options)
+    apiCallDone: Ext.emptyFn,
+
+    config: {
+        item: {
+            id: undefined,
+        },
+        text: undefined,
+        url: undefined,
+        note: undefined,
+        params: {},
+    },
+
+    getParams: function () {
+        let me = this;
+
+        if (Ext.Object.isEmpty(me.params)) {
+            return '';
+        }
+        return '?' + Ext.Object.toQueryString(me.params);
+    },
+
+    controller: {
+        xclass: 'Ext.app.ViewController',
+
+        control: {
+            'field[name=confirm]': {
+                change: function (f, value) {
+                    const view = this.getView();
+                    const confirmButton = this.lookupReference('confirmButton');
+                    if (value === view.getItem().id.toString()) {
+                        confirmButton.enable();
+                    } else {
+                        confirmButton.disable();
+                    }
+                },
+                specialkey: function (field, event) {
+                    const confirmButton = this.lookupReference('confirmButton');
+                    if (!confirmButton.isDisabled() && event.getKey() === event.ENTER) {
+                        confirmButton.fireEvent('click', confirmButton, event);
+                    }
+                },
+            },
+            'button[reference=confirmButton]': {
+                click: function () {
+                    const view = this.getView();
+                    Proxmox.Utils.API2Request({
+                        url: view.getUrl() + view.getParams(),
+                        method: 'DELETE',
+                        waitMsgTarget: view,
+                        failure: function (response, opts) {
+                            view.apiCallDone(false, response, opts);
+                            view.close();
+                            Ext.Msg.alert('Error', response.htmlStatus);
+                        },
+                        success: function (response, options) {
+                            const hasProgressBar = !!(view.showProgress && response.result.data);
+
+                            view.apiCallDone(true, response, options);
+
+                            if (hasProgressBar) {
+                                // stay around so we can trigger our close events
+                                // when background action is completed
+                                view.hide();
+
+                                const upid = response.result.data;
+                                const win = Ext.create('Proxmox.window.TaskProgress', {
+                                    upid: upid,
+                                    taskDone: view.taskDone,
+                                    listeners: {
+                                        destroy: function () {
+                                            view.close();
+                                        },
+                                    },
+                                });
+                                win.show();
+                            } else {
+                                view.close();
+                            }
+                        },
+                    });
+                },
+            },
+            'button[reference=declineButton]': {
+                click: function () {
+                    const view = this.getView();
+                    view.close();
+                },
+            },
+        },
+    },
+
+    initComponent: function () {
+        let me = this;
+
+        let cls = [Ext.baseCSSPrefix + 'message-box-icon', Ext.baseCSSPrefix + 'dlg-icon'];
+        if (me.dangerous) {
+            cls.push(Ext.baseCSSPrefix + 'message-box-warning');
+        } else {
+            cls.push(Ext.baseCSSPrefix + 'message-box-question');
+        }
+
+        let body = {
+            xtype: 'container',
+            layout: 'hbox',
+            items: [
+                {
+                    xtype: 'component',
+                    cls: cls,
+                },
+            ],
+        };
+
+        const itemId = me.getItem().id;
+        if (!Ext.isDefined(itemId)) {
+            throw 'no ID specified';
+        }
+
+        let content = {
+            xtype: 'container',
+            layout: 'vbox',
+            items: [
+                {
+                    xtype: 'component',
+                    reference: 'messageCmp',
+                    html: Ext.htmlEncode(me.getText()),
+                },
+            ],
+        };
+
+        if (me.dangerous) {
+            let label = `${gettext('Please enter the ID to confirm')} (${itemId})`;
+            content.items.push({
+                itemId: 'confirmField',
+                reference: 'confirmField',
+                xtype: 'textfield',
+                name: 'confirm',
+                padding: '5 0 0 0',
+                width: 340,
+                labelWidth: 240,
+                fieldLabel: label,
+                hideTrigger: true,
+                allowBlank: false,
+            });
+        }
+
+        if (me.additionalItems && me.additionalItems.length > 0) {
+            content.items.push({
+                xtype: 'container',
+                height: 5,
+            });
+            for (const item of me.additionalItems) {
+                content.items.push(item);
+            }
+        }
+
+        if (Ext.isDefined(me.getNote())) {
+            content.items.push({
+                xtype: 'container',
+                reference: 'noteContainer',
+                flex: 1,
+                layout: {
+                    type: 'vbox',
+                },
+                items: [
+                    {
+                        xtype: 'component',
+                        reference: 'noteCmp',
+                        userCls: 'pmx-hint',
+                        html: `<span title="${me.getNote()}">${me.getNote()}</span>`,
+                    },
+                ],
+            });
+        }
+
+        body.items.push(content);
+
+        me.items = [body];
+
+        let buttons = [
+            {
+                xtype: 'button',
+                reference: 'confirmButton',
+                text: me.confirmButtonText,
+                disabled: me.dangerous,
+                width: 75,
+                margin: '0 5 0 0',
+            },
+        ];
+
+        if (me.declineButtonText) {
+            buttons.push({
+                xtype: 'button',
+                reference: 'declineButton',
+                text: me.declineButtonText,
+                width: 75,
+            });
+        }
+
+        me.dockedItems = [
+            {
+                xtype: 'container',
+                dock: 'bottom',
+                cls: ['x-toolbar', 'x-toolbar-footer'],
+                layout: {
+                    type: 'hbox',
+                    pack: 'center',
+                },
+                items: buttons,
+            },
+        ];
+
+        me.callParent();
+    },
+});
diff --git a/src/window/SafeDestroy.js b/src/window/SafeDestroy.js
index 4193db1..4ef1389 100644
--- a/src/window/SafeDestroy.js
+++ b/src/window/SafeDestroy.js
@@ -1,255 +1,29 @@
-// Pop-up a message window where the user has to confirm their intent to delete an item.
-// Optionally, an additional textfield can be added, requiring the user to enter the ID
-// of the given item again to confirm their intent.
+// Pop-up a message window where the user has to manually enter the resource ID to enable the
+// destroy confirmation button to ensure that they got the correct resource selected for.
 Ext.define('Proxmox.window.SafeDestroy', {
-    extend: 'Ext.window.Window',
+    extend: 'Proxmox.window.ConfirmRemoveDialog',
     alias: 'widget.proxmoxSafeDestroy',
 
-    title: gettext('Confirm'),
-    modal: true,
-    buttonAlign: 'center',
-    bodyPadding: 10,
-    width: 450,
-    layout: { type: 'hbox' },
-    defaultFocus: 'confirmField',
-    showProgress: false,
-
-    // if set to true, a warning sign will be displayed and entering the ID will
-    // be required before removal is possible. If set to false, a question mark
-    // will be displayed.
     dangerous: true,
 
     confirmButtonText: gettext('Remove'),
     // second button will only be displayed if a text is given
     declineButtonText: undefined,
 
-    additionalItems: [],
-
-    // gets called if we have a progress bar or taskview and it detected that
-    // the task finished. function(success)
-    taskDone: Ext.emptyFn,
-
-    // gets called when the api call is finished, right at the beginning
-    // function(success, response, options)
-    apiCallDone: Ext.emptyFn,
-
     config: {
         item: {
             id: undefined,
             formattedIdentifier: undefined,
         },
-        url: undefined,
-        note: undefined,
         taskName: undefined,
-        params: {},
     },
 
-    getParams: function () {
+    getText: function () {
         let me = this;
 
-        if (Ext.Object.isEmpty(me.params)) {
-            return '';
-        }
-        return '?' + Ext.Object.toQueryString(me.params);
-    },
-
-    controller: {
-        xclass: 'Ext.app.ViewController',
-
-        control: {
-            'field[name=confirm]': {
-                change: function (f, value) {
-                    const view = this.getView();
-                    const confirmButton = this.lookupReference('confirmButton');
-                    if (value === view.getItem().id.toString()) {
-                        confirmButton.enable();
-                    } else {
-                        confirmButton.disable();
-                    }
-                },
-                specialkey: function (field, event) {
-                    const confirmButton = this.lookupReference('confirmButton');
-                    if (!confirmButton.isDisabled() && event.getKey() === event.ENTER) {
-                        confirmButton.fireEvent('click', confirmButton, event);
-                    }
-                },
-            },
-            'button[reference=confirmButton]': {
-                click: function () {
-                    const view = this.getView();
-                    Proxmox.Utils.API2Request({
-                        url: view.getUrl() + view.getParams(),
-                        method: 'DELETE',
-                        waitMsgTarget: view,
-                        failure: function (response, opts) {
-                            view.apiCallDone(false, response, opts);
-                            view.close();
-                            Ext.Msg.alert('Error', response.htmlStatus);
-                        },
-                        success: function (response, options) {
-                            const hasProgressBar = !!(view.showProgress && response.result.data);
-
-                            view.apiCallDone(true, response, options);
-
-                            if (hasProgressBar) {
-                                // stay around so we can trigger our close events
-                                // when background action is completed
-                                view.hide();
-
-                                const upid = response.result.data;
-                                const win = Ext.create('Proxmox.window.TaskProgress', {
-                                    upid: upid,
-                                    taskDone: view.taskDone,
-                                    listeners: {
-                                        destroy: function () {
-                                            view.close();
-                                        },
-                                    },
-                                });
-                                win.show();
-                            } else {
-                                view.close();
-                            }
-                        },
-                    });
-                },
-            },
-            'button[reference=declineButton]': {
-                click: function () {
-                    const view = this.getView();
-                    view.close();
-                },
-            },
-        },
-    },
-
-    initComponent: function () {
-        let me = this;
-
-        let cls = [Ext.baseCSSPrefix + 'message-box-icon', Ext.baseCSSPrefix + 'dlg-icon'];
-        if (me.dangerous) {
-            cls.push(Ext.baseCSSPrefix + 'message-box-warning');
-        } else {
-            cls.push(Ext.baseCSSPrefix + 'message-box-question');
-        }
-
-        let body = {
-            xtype: 'container',
-            layout: 'hbox',
-            items: [
-                {
-                    xtype: 'component',
-                    cls: cls,
-                },
-            ],
-        };
-
-        const itemId = me.getItem().id;
-        if (!Ext.isDefined(itemId)) {
-            throw 'no ID specified';
-        }
-
-        const taskName = me.getTaskName();
-
-        let content = {
-            xtype: 'container',
-            layout: 'vbox',
-            items: [
-                {
-                    xtype: 'component',
-                    reference: 'messageCmp',
-                    html: Ext.htmlEncode(
-                        Proxmox.Utils.format_task_description(
-                            taskName,
-                            me.getItem().formattedIdentifier ?? itemId,
-                        ),
-                    ),
-                },
-            ],
-        };
-
-        if (me.dangerous) {
-            let label = `${gettext('Please enter the ID to confirm')} (${itemId})`;
-            content.items.push({
-                itemId: 'confirmField',
-                reference: 'confirmField',
-                xtype: 'textfield',
-                name: 'confirm',
-                padding: '5 0 0 0',
-                width: 340,
-                labelWidth: 240,
-                fieldLabel: label,
-                hideTrigger: true,
-                allowBlank: false,
-            });
-        }
-
-        if (me.additionalItems && me.additionalItems.length > 0) {
-            content.items.push({
-                xtype: 'container',
-                height: 5,
-            });
-            for (const item of me.additionalItems) {
-                content.items.push(item);
-            }
-        }
-
-        if (Ext.isDefined(me.getNote())) {
-            content.items.push({
-                xtype: 'container',
-                reference: 'noteContainer',
-                flex: 1,
-                layout: {
-                    type: 'vbox',
-                },
-                items: [
-                    {
-                        xtype: 'component',
-                        reference: 'noteCmp',
-                        userCls: 'pmx-hint',
-                        html: `<span title="${me.getNote()}">${me.getNote()}</span>`,
-                    },
-                ],
-            });
-        }
-
-        body.items.push(content);
-
-        me.items = [body];
-
-        let buttons = [
-            {
-                xtype: 'button',
-                reference: 'confirmButton',
-                text: me.confirmButtonText,
-                disabled: me.dangerous,
-                width: 75,
-                margin: '0 5 0 0',
-            },
-        ];
-
-        if (me.declineButtonText) {
-            buttons.push({
-                xtype: 'button',
-                reference: 'declineButton',
-                text: me.declineButtonText,
-                width: 75,
-            });
-        }
-
-        me.dockedItems = [
-            {
-                xtype: 'container',
-                dock: 'bottom',
-                cls: ['x-toolbar', 'x-toolbar-footer'],
-                layout: {
-                    type: 'hbox',
-                    pack: 'center',
-                },
-                items: buttons,
-            },
-        ];
+        let identifier = me.getItem().formattedIdentifier ?? me.getItem().id;
+        me.text = `${Proxmox.Utils.format_task_description(me.getTaskName(), identifier)}`;
 
-        me.callParent();
+        return me.callParent();
     },
 });
-- 
2.47.3



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

  parent reply	other threads:[~2025-09-30 15:00 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-30 14:58 [pve-devel] [PATCH container/docs/ha-manager/manager/widget-toolkit/qemu-server v3 00/11] fix #6613: update HA rules upon resource deletion Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH ha-manager v3 1/2] fix #6613: update rules containing the resource to be deleted Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH ha-manager v3 2/2] api: add purge parameter for resource deletion Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH qemu-server v3 1/1] fix #6613: pass purge param to delete_service_from_config Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH container " Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH widget-toolkit v3 1/4] window: refactor construction of SafeDestroy items Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH widget-toolkit v3 2/4] window: introduce dangerous parameter to SafeDestroy Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH widget-toolkit v3 3/4] window: make buttons in SafeDestroy configurable Michael Köppl
2025-09-30 14:58 ` Michael Köppl [this message]
2025-09-30 14:58 ` [pve-devel] [PATCH manager v3 1/2] ui: add ConfirmRemoveResource window Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH manager v3 2/2] ui: use ConfirmRemoveResource window for removing resources Michael Köppl
2025-09-30 14:58 ` [pve-devel] [PATCH docs v3 1/1] add notes about effects of purge flag for resource and guest removal Michael Köppl

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250930145848.263162-9-m.koeppl@proxmox.com \
    --to=m.koeppl@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal