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 199AB6094C for ; Thu, 13 Aug 2020 12:59:52 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 16B8E196A3 for ; Thu, 13 Aug 2020 12:59:52 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (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 id 0E26A1969B for ; Thu, 13 Aug 2020 12:59:48 +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 D15F8445E0 for ; Thu, 13 Aug 2020 12:59:47 +0200 (CEST) From: Hannes Laimer To: pbs-devel@lists.proxmox.com Date: Thu, 13 Aug 2020 12:58:53 +0200 Message-Id: <20200813105853.144386-3-h.laimer@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200813105853.144386-1-h.laimer@proxmox.com> References: <20200813105853.144386-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox-backup v3 2/2] ui/cli: added support for the removal of mount-units X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 13 Aug 2020 10:59:52 -0000 Signed-off-by: Hannes Laimer --- src/bin/proxmox_backup_manager/disk.rs | 29 +++- www/DirectoryList.js | 150 ++++++++++-------- www/Makefile | 1 + www/window/SafeRemove.js | 209 +++++++++++++++++++++++++ 4 files changed, 325 insertions(+), 64 deletions(-) create mode 100644 www/window/SafeRemove.js diff --git a/src/bin/proxmox_backup_manager/disk.rs b/src/bin/proxmox_backup_manager/disk.rs index a93a6f6b..b7eec592 100644 --- a/src/bin/proxmox_backup_manager/disk.rs +++ b/src/bin/proxmox_backup_manager/disk.rs @@ -319,6 +319,32 @@ async fn create_datastore_disk( Ok(Value::Null) } +#[api( + input: { + properties: { + name: { + schema: DATASTORE_SCHEMA, + }, + }, + }, +)] +/// Remove a Filesystem mounted under '/mnt/datastore/'.". +async fn delete_datastore_disk( + mut param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + + param["node"] = "localhost".into(); + + let info = &api2::node::disks::directory::API_METHOD_DELETE_DATASTORE_DISK; + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(Value::Null) +} + pub fn filesystem_commands() -> CommandLineInterface { let cmd_def = CliCommandMap::new() @@ -327,7 +353,8 @@ pub fn filesystem_commands() -> CommandLineInterface { CliCommand::new(&API_METHOD_CREATE_DATASTORE_DISK) .arg_param(&["name"]) .completion_cb("disk", complete_disk_name) - ); + ).insert("remove", CliCommand::new(&API_METHOD_DELETE_DATASTORE_DISK) + .arg_param(&["name"])); cmd_def.into() } diff --git a/www/DirectoryList.js b/www/DirectoryList.js index 00531fd0..6ec91e6a 100644 --- a/www/DirectoryList.js +++ b/www/DirectoryList.js @@ -8,83 +8,107 @@ Ext.define('PBS.admin.Directorylist', { emptyText: gettext('No Mount-Units found'), controller: { - xclass: 'Ext.app.ViewController', + xclass: 'Ext.app.ViewController', - createDirectory: function() { - let me = this; - Ext.create('PBS.window.CreateDirectory', { - listeners: { - destroy: function() { - me.reload(); - }, - }, - }).show(); - }, + createDirectory: function () { + console.log(this); + let me = this; + Ext.create('PBS.window.CreateDirectory', { + listeners: { + destroy: function () { + me.reload(); + }, + }, + }).show(); + }, - reload: function() { - let me = this; - let store = me.getView().getStore(); - store.load(); - store.sort(); - }, + removeDir: function () { + let me = this; + let view = me.getView(); + let rec = view.getSelection(); + let dialog = Ext.create('PBS.window.SafeDestroy', { + url: rec[0].data.path.replace( + '/mnt/datastore/', + '/nodes/localhost/disks/directory/'), + item: {type: 'Dir', id: rec[0].data.path.replace('/mnt/datastore/', '')}, + note: 'Data and partitions on the disk will be left untouched.' + }); + dialog.on('destroy', function() { + me.reload(); + }); + dialog.show(); + }, - init: function(view) { - let me = this; - Proxmox.Utils.monStoreErrors(view, view.getStore(), true); - me.reload(); - }, - }, + reload: function () { + let me = this; + let store = me.getView().getStore(); + store.load(); + store.sort(); + }, + init: function (view) { + let me = this; + Proxmox.Utils.monStoreErrors(view, view.getStore(), true); + me.reload(); + }, + }, rootVisible: false, useArrows: true, tbar: [ - { - text: gettext('Reload'), - iconCls: 'fa fa-refresh', - handler: 'reload', - }, - { - text: gettext('Create') + ': Directory', - handler: 'createDirectory', - }, + { + text: gettext('Reload'), + iconCls: 'fa fa-refresh', + handler: 'reload', + }, + { + text: gettext('Create') + ': Directory', + handler: 'createDirectory', + }, + { + xtype: 'proxmoxButton', + text: gettext('Remove'), + handler: 'removeDir', + disabled: true, + iconCls: 'fa fa-trash-o' + } ], columns: [ - { - text: gettext('Path'), - dataIndex: 'path', - flex: 1, - }, - { - header: gettext('Device'), - flex: 1, - dataIndex: 'device', - }, - { - header: gettext('Filesystem'), - width: 100, - dataIndex: 'filesystem', - }, - { - header: gettext('Options'), - width: 100, - dataIndex: 'options', - }, - { - header: gettext('Unit File'), - hidden: true, - dataIndex: 'unitfile', - }, + { + text: gettext('Path'), + dataIndex: 'path', + flex: 1, + }, + { + header: gettext('Device'), + flex: 1, + dataIndex: 'device', + }, + { + header: gettext('Filesystem'), + width: 100, + dataIndex: 'filesystem', + }, + { + header: gettext('Options'), + width: 100, + dataIndex: 'options', + }, + { + header: gettext('Unit File'), + hidden: true, + dataIndex: 'unitfile', + }, ], store: { - fields: ['path', 'device', 'filesystem', 'options', 'unitfile'], - proxy: { - type: 'proxmox', - url: '/api2/json/nodes/localhost/disks/directory', - }, - sorters: 'path', + fields: ['path', 'device', 'filesystem', 'options', 'unitfile'], + proxy: { + type: 'proxmox', + url: '/api2/json/nodes/localhost/disks/directory', + }, + sorters: 'path', }, }); diff --git a/www/Makefile b/www/Makefile index edce8cb3..57db54ee 100644 --- a/www/Makefile +++ b/www/Makefile @@ -23,6 +23,7 @@ JSSRC= \ window/SyncJobEdit.js \ window/ACLEdit.js \ window/DataStoreEdit.js \ + window/SafeRemove.js \ window/CreateDirectory.js \ window/ZFSCreate.js \ window/FileBrowser.js \ diff --git a/www/window/SafeRemove.js b/www/window/SafeRemove.js new file mode 100644 index 00000000..fdbefeae --- /dev/null +++ b/www/window/SafeRemove.js @@ -0,0 +1,209 @@ +/* Popup a message window + * where the user has to manually enter the resource ID + * to enable the destroy button + * (mostly taken from PVE-Manager(git.proxmox.com/git/pve-manager.git)) + */ +Ext.define('PBS.window.SafeDestroy', { + extend: 'Ext.window.Window', + + title: gettext('Confirm'), + modal: true, + buttonAlign: 'center', + bodyPadding: 10, + width: 450, + layout: {type: 'hbox'}, + defaultFocus: 'confirmField', + showProgress: false, + + config: { + item: { + id: undefined, + type: undefined + }, + url: undefined, + note: undefined, + params: {} + }, + + getParams: function () { + var me = this; + var wipeCheckbox = me.lookupReference('wipeCheckbox'); + if (wipeCheckbox.checked) { + me.params.wipe = 1; + } + 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) { + var view = this.getView(); + var removeButton = this.lookupReference('removeButton'); + if (value === view.getItem().id.toString()) { + removeButton.enable(); + } else { + removeButton.disable(); + } + }, + specialkey: function (field, event) { + var removeButton = this.lookupReference('removeButton'); + if (!removeButton.isDisabled() && event.getKey() == event.ENTER) { + removeButton.fireEvent('click', removeButton, event); + } + } + }, + 'button[reference=removeButton]': { + click: function () { + var view = this.getView(); + Proxmox.Utils.API2Request({ + url: view.getUrl() + view.getParams(), + method: 'DELETE', + waitMsgTarget: view, + failure: function (response, opts) { + view.close(); + Ext.Msg.alert('Error', response.htmlStatus); + }, + success: function (response, options) { + var hasProgressBar = view.showProgress && + response.result.data ? true : false; + + if (hasProgressBar) { + // stay around so we can trigger our close events + // when background action is completed + view.hide(); + + var upid = response.result.data; + var win = Ext.create('Proxmox.window.TaskProgress', { + upid: upid, + listeners: { + destroy: function () { + view.close(); + } + } + }); + win.show(); + } else { + view.close(); + } + } + }); + } + } + } + }, + + items: [ + { + xtype: 'component', + cls: [Ext.baseCSSPrefix + 'message-box-icon', + Ext.baseCSSPrefix + 'message-box-warning', + Ext.baseCSSPrefix + 'dlg-icon'] + }, + { + xtype: 'container', + flex: 1, + layout: { + type: 'vbox', + align: 'stretch' + }, + items: [ + { + xtype: 'component', + reference: 'messageCmp' + }, + { + itemId: 'confirmField', + reference: 'confirmField', + xtype: 'textfield', + name: 'confirm', + labelWidth: 300, + hideTrigger: true, + allowBlank: false + }, + { + xtype: 'proxmoxcheckbox', + name: 'wipe', + reference: 'wipeCheckbox', + boxLabel: gettext('Wipe'), + checked: false, + autoEl: { + tag: 'div', + 'data-qtip': gettext('Wipe disk after the removal of mountpoint') + } + }, + { + xtype: 'container', + flex: 1, + layout: { + type: 'vbox', + align: 'middle' + }, + height: 25, + items: [ + { + xtype: 'component', + reference: 'noteCmp' + }, + ] + }, + ] + } + ], + buttons: [ + { + reference: 'removeButton', + text: gettext('Remove'), + disabled: true + } + ], + + initComponent: function () { + var me = this; + + me.callParent(); + + var item = me.getItem(); + + if (!Ext.isDefined(item.id)) { + throw "no ID specified"; + } + + if (!Ext.isDefined(item.type)) { + throw "no Disk type specified"; + } + + var messageCmp = me.lookupReference('messageCmp'); + var noteCmp = me.lookupReference('noteCmp'); + var msg; + + if (item.type === 'Dir') { + msg = `Directory ${item.id} - Remove` + } else { + throw "unknown item type specified"; + } + + if(me.getNote()) { + noteCmp.setHtml(`${me.getNote()}`); + } + + messageCmp.setHtml(msg); + + if (item.type === 'Dir') { + let wipeCheckbox = me.lookupReference('wipeCheckbox'); + wipeCheckbox.setDisabled(true); + wipeCheckbox.setHidden(true); + } + + var confirmField = me.lookupReference('confirmField'); + msg = gettext('Please enter the ID to confirm') + + ' (' + item.id + ')'; + confirmField.setFieldLabel(msg); + } +}); -- 2.20.1