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
next prev 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