public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui
@ 2021-01-28 11:59 Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 01/15] api2/tape/changer: add changer filter to list_drives api call Dominik Csapak
                   ` (15 more replies)
  0 siblings, 16 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

it's a very rough beginning, mostly so that we can test the features
on the webui

its still not baked in by default, and there are things missing
(e.g. restore, encryption, etc)

also the ux is not representable of what i have in mind for the
finished gui, e.g.
* a first setup wizard
* a wizard for creating/extending media pools
etc.

i'd love some feedback though

changes from v1:
* rebase on master (e.g. changer-drive-id -> changer-drivenum)
* add filter to list_drives instead of having an extra api call
  get_drives (and adapt gui)
* omit patch for changing protected flags (most things should work
  without it, i'll test and send a separate patch for where it's needed)
* add patch to add vendor/model to drivestatus
* add patch to map import/export slots
* add patch for fixing missing changes from changer-drive-id to changer-drivenum

Dominik Csapak (15):
  api2/tape/changer: add changer filter to list_drives api call
  api2/tape/drive: add load_media as api call
  api2/tape/drive: change methods of some api calls from put to get
  api2/config/{drive,changer}: prevent adding same device multiple times
  ui: tape: add form fields
  ui: tape: add Edit Windows
  ui: tape: add BackupOverview Panel
  ui: tape: add ChangerStatus panel
  ui: tape: add DriveConfig panel
  ui: tape: add PoolConfig
  ui: tape: move TapeManagement.js to tape dir
  ui: tape: use panels in tape interface
  tape/changer: add vendor/model to DriveStatus
  tape/changer: refactor marking of import/export slots from config
  tape: change changer-drive-id to changer-drivenum

 docs/tape-backup.rst                     |   8 +-
 src/api2/config/changer.rs               |  12 +-
 src/api2/config/drive.rs                 |  13 +-
 src/api2/tape/changer.rs                 |   4 +-
 src/api2/tape/drive.rs                   |  22 +-
 src/bin/proxmox_tape/drive.rs            |   2 +-
 src/tape/changer/mod.rs                  |  36 +-
 src/tape/changer/mtx/mtx_wrapper.rs      |  41 +-
 src/tape/changer/mtx/parse_mtx_status.rs |   6 +
 src/tape/changer/sg_pt_changer.rs        |  32 +-
 src/tape/drive/virtual_tape.rs           |   2 +
 www/Makefile                             |  18 +-
 www/TapeManagement.js                    |  11 -
 www/tape/BackupOverview.js               | 150 ++++++
 www/tape/ChangerStatus.js                | 631 +++++++++++++++++++++++
 www/tape/DriveConfig.js                  | 316 ++++++++++++
 www/tape/PoolConfig.js                   | 119 +++++
 www/tape/TapeManagement.js               |  35 ++
 www/tape/form/AllocationSelector.js      |  31 ++
 www/tape/form/ChangerSelector.js         |  60 +++
 www/tape/form/DriveSelector.js           |  66 +++
 www/tape/form/PoolSelector.js            |  44 ++
 www/tape/form/RetentionSelector.js       |  26 +
 www/tape/form/TapeDevicePathSelector.js  |  62 +++
 www/tape/window/ChangerEdit.js           |  50 ++
 www/tape/window/DriveEdit.js             |  77 +++
 www/tape/window/LabelMedia.js            |  47 ++
 www/tape/window/PoolEdit.js              |  64 +++
 www/tape/window/TapeBackup.js            |  43 ++
 29 files changed, 1957 insertions(+), 71 deletions(-)
 delete mode 100644 www/TapeManagement.js
 create mode 100644 www/tape/BackupOverview.js
 create mode 100644 www/tape/ChangerStatus.js
 create mode 100644 www/tape/DriveConfig.js
 create mode 100644 www/tape/PoolConfig.js
 create mode 100644 www/tape/TapeManagement.js
 create mode 100644 www/tape/form/AllocationSelector.js
 create mode 100644 www/tape/form/ChangerSelector.js
 create mode 100644 www/tape/form/DriveSelector.js
 create mode 100644 www/tape/form/PoolSelector.js
 create mode 100644 www/tape/form/RetentionSelector.js
 create mode 100644 www/tape/form/TapeDevicePathSelector.js
 create mode 100644 www/tape/window/ChangerEdit.js
 create mode 100644 www/tape/window/DriveEdit.js
 create mode 100644 www/tape/window/LabelMedia.js
 create mode 100644 www/tape/window/PoolEdit.js
 create mode 100644 www/tape/window/TapeBackup.js

-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 01/15] api2/tape/changer: add changer filter to list_drives api call
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 02/15] api2/tape/drive: add load_media as " Dominik Csapak
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

so that an api user can get the drives belonging to a changer
without having to parse the config listing themselves

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/tape/changer.rs |  4 ++--
 src/api2/tape/drive.rs   | 13 ++++++++++++-
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs
index 87737d32..84030f8e 100644
--- a/src/api2/tape/changer.rs
+++ b/src/api2/tape/changer.rs
@@ -11,9 +11,9 @@ use crate::{
     api2::types::{
         CHANGER_NAME_SCHEMA,
         DriveListEntry,
-        ScsiTapeChanger,
-        MtxStatusEntry,
         MtxEntryKind,
+        MtxStatusEntry,
+        ScsiTapeChanger,
     },
     tape::{
         TAPE_STATUS_DIR,
diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index c86c666e..df7752df 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -27,6 +27,7 @@ use crate::{
     api2::{
         types::{
             UPID_SCHEMA,
+            CHANGER_NAME_SCHEMA,
             DRIVE_NAME_SCHEMA,
             MEDIA_LABEL_SCHEMA,
             MEDIA_POOL_NAME_SCHEMA,
@@ -1100,7 +1101,12 @@ pub fn catalog_media(
 
 #[api(
     input: {
-        properties: {},
+        properties: {
+            changer: {
+                schema: CHANGER_NAME_SCHEMA,
+                optional: true,
+            },
+        },
     },
     returns: {
         description: "The list of configured drives with model information.",
@@ -1112,6 +1118,7 @@ pub fn catalog_media(
 )]
 /// List drives
 pub fn list_drives(
+    changer: Option<String>,
     _param: Value,
 ) -> Result<Vec<DriveListEntry>, Error> {
 
@@ -1124,6 +1131,10 @@ pub fn list_drives(
     let mut list = Vec::new();
 
     for drive in drive_list {
+        if changer.is_some() && drive.changer != changer {
+            continue;
+        }
+
         let mut entry = DriveListEntry {
             name: drive.name,
             path: drive.path.clone(),
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 02/15] api2/tape/drive: add load_media as api call
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 01/15] api2/tape/changer: add changer filter to list_drives api call Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 03/15] api2/tape/drive: change methods of some api calls from put to get Dominik Csapak
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

code was already there, just add it as api call

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/tape/drive.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index df7752df..f405208d 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -1194,6 +1194,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([
         &Router::new()
             .put(&API_METHOD_LABEL_MEDIA)
     ),
+    (
+        "load-media",
+        &Router::new()
+            .put(&API_METHOD_LOAD_MEDIA)
+    ),
     (
         "load-slot",
         &Router::new()
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 03/15] api2/tape/drive: change methods of some api calls from put to get
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 01/15] api2/tape/changer: add changer filter to list_drives api call Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 02/15] api2/tape/drive: add load_media as " Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 04/15] api2/config/{drive, changer}: prevent adding same device multiple times Dominik Csapak
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

makes more sense to have retrieving api calls as get instead of put

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/tape/drive.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index f405208d..8e224d28 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -1207,12 +1207,12 @@ pub const SUBDIRS: SubdirMap = &sorted!([
     (
         "cartridge-memory",
         &Router::new()
-            .put(&API_METHOD_CARTRIDGE_MEMORY)
+            .get(&API_METHOD_CARTRIDGE_MEMORY)
     ),
     (
         "volume-statistics",
         &Router::new()
-            .put(&API_METHOD_VOLUME_STATISTICS)
+            .get(&API_METHOD_VOLUME_STATISTICS)
     ),
     (
         "read-label",
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 04/15] api2/config/{drive, changer}: prevent adding same device multiple times
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (2 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 03/15] api2/tape/drive: change methods of some api calls from put to get Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 05/15] ui: tape: add form fields Dominik Csapak
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

this check is not perfect since there are often multiple device
nodes per drive/changer, but from the scan api we should return always
the same, so for an api user this should be enough

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/config/changer.rs | 12 ++++++++++--
 src/api2/config/drive.rs   | 11 +++++++++--
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/api2/config/changer.rs b/src/api2/config/changer.rs
index 1b429659..8404d20b 100644
--- a/src/api2/config/changer.rs
+++ b/src/api2/config/changer.rs
@@ -59,8 +59,16 @@ pub fn create_changer(
 
     check_drive_path(&linux_changers, &path)?;
 
-    if config.sections.get(&name).is_some() {
-        bail!("Entry '{}' already exists", name);
+    let existing: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
+
+    for changer in existing {
+        if changer.name == name {
+            bail!("Entry '{}' already exists", name);
+        }
+
+        if changer.path == path {
+            bail!("Path '{}' already in use by '{}'", path, changer.name);
+        }
     }
 
     let item = ScsiTapeChanger {
diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs
index 78ebecb3..7d7803d3 100644
--- a/src/api2/config/drive.rs
+++ b/src/api2/config/drive.rs
@@ -56,8 +56,15 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
 
     check_drive_path(&linux_drives, &item.path)?;
 
-    if config.sections.get(&item.name).is_some() {
-        bail!("Entry '{}' already exists", item.name);
+    let existing: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+
+    for drive in existing {
+        if drive.name == item.name {
+            bail!("Entry '{}' already exists", item.name);
+        }
+        if drive.path == item.path {
+            bail!("Path '{}' already used in drive '{}'", item.path, drive.name);
+        }
     }
 
     config.set_data(&item.name, "linux", &item)?;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 05/15] ui: tape: add form fields
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (3 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 04/15] api2/config/{drive, changer}: prevent adding same device multiple times Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 06/15] ui: tape: add Edit Windows Dominik Csapak
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

this includes selectors for
* Allocation Policy
* Retention Policy
* Drives
* Changers
* Tape Device Paths
* Pools

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile                            |  6 +++
 www/tape/form/AllocationSelector.js     | 31 ++++++++++++
 www/tape/form/ChangerSelector.js        | 60 ++++++++++++++++++++++
 www/tape/form/DriveSelector.js          | 66 +++++++++++++++++++++++++
 www/tape/form/PoolSelector.js           | 44 +++++++++++++++++
 www/tape/form/RetentionSelector.js      | 26 ++++++++++
 www/tape/form/TapeDevicePathSelector.js | 62 +++++++++++++++++++++++
 7 files changed, 295 insertions(+)
 create mode 100644 www/tape/form/AllocationSelector.js
 create mode 100644 www/tape/form/ChangerSelector.js
 create mode 100644 www/tape/form/DriveSelector.js
 create mode 100644 www/tape/form/PoolSelector.js
 create mode 100644 www/tape/form/RetentionSelector.js
 create mode 100644 www/tape/form/TapeDevicePathSelector.js

diff --git a/www/Makefile b/www/Makefile
index c2d80c74..beea80bf 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -9,6 +9,12 @@ TAPE_UI_FILES=
 
 ifdef TEST_TAPE_GUI
 TAPE_UI_FILES=						\
+	tape/form/AllocationSelector.js			\
+	tape/form/ChangerSelector.js			\
+	tape/form/DriveSelector.js			\
+	tape/form/PoolSelector.js			\
+	tape/form/RetentionSelector.js			\
+	tape/form/TapeDevicePathSelector.js		\
 	TapeManagement.js
 endif
 
diff --git a/www/tape/form/AllocationSelector.js b/www/tape/form/AllocationSelector.js
new file mode 100644
index 00000000..ab56c00c
--- /dev/null
+++ b/www/tape/form/AllocationSelector.js
@@ -0,0 +1,31 @@
+Ext.define('PBS.TapeManagement.AllocationStore', {
+    extend: 'Ext.data.Store',
+    alias: 'store.allocationCalendarEventStore',
+
+    field: ['value', 'text'],
+    data: [
+	{ value: 'continue', text: gettext('Continue') },
+	{ value: 'always', text: gettext('Always') },
+	{ value: '*:0/30', text: Ext.String.format(gettext("Every {0} minutes"), 30) },
+	{ value: 'hourly', text: gettext("Every hour") },
+	{ value: '0/2:00', text: gettext("Every two hours") },
+	{ value: '2,22:30', text: gettext("Every day") + " 02:30, 22:30" },
+	{ value: 'daily', text: gettext("Every day") + " 00:00" },
+	{ value: 'mon..fri', text: gettext("Monday to Friday") + " 00:00" },
+	{ value: 'mon..fri *:00', text: gettext("Monday to Friday") + ', ' + gettext("hourly") },
+	{ value: 'sat 18:15', text: gettext("Every Saturday") + " 18:15" },
+	{ value: 'monthly', text: gettext("Every first day of the Month") + " 00:00" },
+	{ value: 'sat *-1..7 02:00', text: gettext("Every first Saturday of the month") + " 02:00" },
+	{ value: 'yearly', text: gettext("First day of the year") + " 00:00" },
+    ],
+});
+
+Ext.define('PBS.TapeManagement.AllocationSelector', {
+    extend: 'PBS.form.CalendarEvent',
+    alias: 'widget.pbsAllocationSelector',
+
+    store: {
+	type: 'allocationCalendarEventStore',
+    },
+});
+
diff --git a/www/tape/form/ChangerSelector.js b/www/tape/form/ChangerSelector.js
new file mode 100644
index 00000000..cc07580c
--- /dev/null
+++ b/www/tape/form/ChangerSelector.js
@@ -0,0 +1,60 @@
+Ext.define('PBS.form.ChangerSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: 'widget.pbsChangerSelector',
+
+    allowBlank: false,
+    displayField: 'name',
+    valueField: 'name',
+    value: null,
+    multiSelect: false,
+
+
+    store: {
+	proxy: {
+	    type: 'proxmox',
+	    url: '/api2/json/tape/changer',
+	},
+	autoLoad: true,
+	sorter: 'name',
+    },
+
+    listConfig: {
+	columns: [
+	    {
+		text: gettext('Name'),
+		dataIndex: 'name',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Path'),
+		sortable: true,
+		dataIndex: 'path',
+		hidden: true,
+		flex: 1,
+	    },
+	    {
+		text: gettext('Vendor'),
+		dataIndex: 'vendor',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Model'),
+		dataIndex: 'model',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Serial'),
+		dataIndex: 'serial',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	],
+    },
+});
diff --git a/www/tape/form/DriveSelector.js b/www/tape/form/DriveSelector.js
new file mode 100644
index 00000000..333989a9
--- /dev/null
+++ b/www/tape/form/DriveSelector.js
@@ -0,0 +1,66 @@
+Ext.define('PBS.form.DriveSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: 'widget.pbsDriveSelector',
+
+    allowBlank: false,
+    displayField: 'name',
+    valueField: 'name',
+    value: null,
+
+    store: {
+	proxy: {
+	    type: 'proxmox',
+	    url: '/api2/json/tape/drive',
+	},
+	autoLoad: true,
+	sorters: 'name',
+    },
+
+    listConfig: {
+	columns: [
+	    {
+		text: gettext('Name'),
+		dataIndex: 'name',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Vendor'),
+		dataIndex: 'vendor',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Model'),
+		dataIndex: 'model',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Serial'),
+		dataIndex: 'serial',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	],
+    },
+
+    initComponent: function() {
+	let me = this;
+
+	if (me.changer) {
+	    me.store.proxy.extraParams = {
+		changer: me.changer,
+	    };
+	} else {
+	    me.store.proxy.extraParams = {};
+	}
+
+	me.callParent();
+    },
+});
+
diff --git a/www/tape/form/PoolSelector.js b/www/tape/form/PoolSelector.js
new file mode 100644
index 00000000..2a0f0bbb
--- /dev/null
+++ b/www/tape/form/PoolSelector.js
@@ -0,0 +1,44 @@
+Ext.define('PBS.TapeManagement.PoolSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: 'widget.pbsMediaPoolSelector',
+
+    allowBlank: false,
+    displayField: 'name',
+    valueField: 'name',
+    autoSelect: false,
+
+    store: {
+	proxy: {
+	    type: 'proxmox',
+	    url: '/api2/json/config/media-pool',
+	},
+	autoLoad: true,
+	sorters: 'name',
+    },
+
+    listConfig: {
+	columns: [
+	    {
+		text: gettext('Name'),
+		dataIndex: 'name',
+	    },
+	    {
+		text: gettext('Drive'),
+		dataIndex: 'drive',
+	    },
+	    {
+		text: gettext('Allocation'),
+		dataIndex: 'allocation',
+	    },
+	    {
+		text: gettext('Retention'),
+		dataIndex: 'retention',
+	    },
+	    {
+		text: gettext('Encryption Fingerprint'),
+		dataIndex: 'encryption',
+	    },
+	],
+    },
+});
+
diff --git a/www/tape/form/RetentionSelector.js b/www/tape/form/RetentionSelector.js
new file mode 100644
index 00000000..4c77d39e
--- /dev/null
+++ b/www/tape/form/RetentionSelector.js
@@ -0,0 +1,26 @@
+Ext.define('PBS.TapeManagement.RetentionStore', {
+    extend: 'Ext.data.Store',
+    alias: 'store.retentionCalendarEventStore',
+
+    field: ['value', 'text'],
+    data: [
+	{ value: 'overwrite', text: gettext('Overwrite') },
+	{ value: 'keep', text: gettext('Keep') },
+	{ value: '120 minutes', text: Ext.String.format(gettext("{0} minutes"), 120) },
+	{ value: '12 hours', text: Ext.String.format(gettext("{0} hours"), 12) },
+	{ value: '7 days', text: Ext.String.format(gettext("{0} days"), 7) },
+	{ value: '4 weeks', text: Ext.String.format(gettext("{0} weeks"), 4) },
+	{ value: '6 months', text: Ext.String.format(gettext("{0} months"), 6) },
+	{ value: '2 years', text: Ext.String.format(gettext("{0} years"), 2) },
+    ],
+});
+
+Ext.define('PBS.TapeManagement.RetentionSelector', {
+    extend: 'PBS.form.CalendarEvent',
+    alias: 'widget.pbsRetentionSelector',
+
+    store: {
+	type: 'retentionCalendarEventStore',
+    },
+});
+
diff --git a/www/tape/form/TapeDevicePathSelector.js b/www/tape/form/TapeDevicePathSelector.js
new file mode 100644
index 00000000..e458454d
--- /dev/null
+++ b/www/tape/form/TapeDevicePathSelector.js
@@ -0,0 +1,62 @@
+Ext.define('PBS.form.TapeDevicePathSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: 'widget.pbsTapeDevicePathSelector',
+
+    allowBlank: false,
+    displayField: 'path',
+    valueField: 'path',
+
+    // type can be 'drives' or 'changers'
+    type: 'drives',
+
+    listConfig: {
+	columns: [
+	    {
+		text: gettext('Path'),
+		dataIndex: 'path',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Vendor'),
+		dataIndex: 'vendor',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Model'),
+		dataIndex: 'model',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	    {
+		text: gettext('Serial'),
+		dataIndex: 'serial',
+		sortable: true,
+		flex: 1,
+		renderer: Ext.String.htmlEncode,
+	    },
+	],
+    },
+
+    initComponent: function() {
+	let me = this;
+	if (me.type !== 'drives' && me.type !== 'changers') {
+	    throw `invalid type '${me.type}'`;
+	}
+
+	let url = `/api2/json/tape/scan-${me.type}`;
+	me.store = {
+	    proxy: {
+		type: 'proxmox',
+		url,
+	    },
+	    autoLoad: true,
+	};
+
+	me.callParent();
+    },
+});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 06/15] ui: tape: add Edit Windows
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (4 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 05/15] ui: tape: add form fields Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 07/15] ui: tape: add BackupOverview Panel Dominik Csapak
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

includes edit windows for
* Drives
* Changers
* Media Pools
* Labeling Media
* Making new Tape Backups

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile                   |  5 +++
 www/tape/window/ChangerEdit.js | 50 ++++++++++++++++++++++
 www/tape/window/DriveEdit.js   | 77 ++++++++++++++++++++++++++++++++++
 www/tape/window/LabelMedia.js  | 47 +++++++++++++++++++++
 www/tape/window/PoolEdit.js    | 64 ++++++++++++++++++++++++++++
 www/tape/window/TapeBackup.js  | 43 +++++++++++++++++++
 6 files changed, 286 insertions(+)
 create mode 100644 www/tape/window/ChangerEdit.js
 create mode 100644 www/tape/window/DriveEdit.js
 create mode 100644 www/tape/window/LabelMedia.js
 create mode 100644 www/tape/window/PoolEdit.js
 create mode 100644 www/tape/window/TapeBackup.js

diff --git a/www/Makefile b/www/Makefile
index beea80bf..827633a3 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -15,6 +15,11 @@ TAPE_UI_FILES=						\
 	tape/form/PoolSelector.js			\
 	tape/form/RetentionSelector.js			\
 	tape/form/TapeDevicePathSelector.js		\
+	tape/window/ChangerEdit.js			\
+	tape/window/DriveEdit.js			\
+	tape/window/LabelMedia.js			\
+	tape/window/PoolEdit.js				\
+	tape/window/TapeBackup.js			\
 	TapeManagement.js
 endif
 
diff --git a/www/tape/window/ChangerEdit.js b/www/tape/window/ChangerEdit.js
new file mode 100644
index 00000000..bb166e50
--- /dev/null
+++ b/www/tape/window/ChangerEdit.js
@@ -0,0 +1,50 @@
+Ext.define('PBS.TapeManagement.ChangerEditWindow', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pbsChangerEditWindow',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    isCreate: true,
+    isAdd: true,
+    subject: gettext('Changer'),
+    cbindData: function(initialConfig) {
+	let me = this;
+
+	let changerid = initialConfig.changerid;
+	let baseurl = '/api2/extjs/config/changer';
+
+	me.isCreate = !changerid;
+	me.url = changerid ? `${baseurl}/${encodeURIComponent(changerid)}` : baseurl;
+	me.method = changerid ? 'PUT' : 'POST';
+
+	return { };
+    },
+
+    items: [
+	{
+	    fieldLabel: gettext('Name'),
+	    name: 'name',
+	    xtype: 'pmxDisplayEditField',
+	    renderer: Ext.htmlEncode,
+	    allowBlank: false,
+	    cbind: {
+		editable: '{isCreate}',
+	    },
+	},
+	{
+	    fieldLabel: gettext('Path'),
+	    xtype: 'pbsTapeDevicePathSelector',
+	    type: 'changers',
+	    name: 'path',
+	    allowBlank: false,
+	},
+	{
+	    fieldLabel: gettext('Import-Export Slots'),
+	    xtype: 'proxmoxtextfield',
+	    name: 'export-slots',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
+    ],
+});
+
diff --git a/www/tape/window/DriveEdit.js b/www/tape/window/DriveEdit.js
new file mode 100644
index 00000000..5c3c8728
--- /dev/null
+++ b/www/tape/window/DriveEdit.js
@@ -0,0 +1,77 @@
+Ext.define('PBS.TapeManagement.DriveEditWindow', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pbsDriveEditWindow',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    isCreate: true,
+    isAdd: true,
+    subject: gettext('Drive'),
+    cbindData: function(initialConfig) {
+	let me = this;
+
+	let driveid = initialConfig.driveid;
+	let baseurl = '/api2/extjs/config/drive';
+
+	me.isCreate = !driveid;
+	me.url = driveid ? `${baseurl}/${encodeURIComponent(driveid)}` : baseurl;
+	me.method = driveid ? 'PUT' : 'POST';
+
+	return { };
+    },
+
+    items: [
+	{
+	    fieldLabel: gettext('Name'),
+	    name: 'name',
+	    xtype: 'pmxDisplayEditField',
+	    renderer: Ext.htmlEncode,
+	    allowBlank: false,
+	    cbind: {
+		editable: '{isCreate}',
+	    },
+	},
+	{
+	    fieldLabel: gettext('Changer'),
+	    xtype: 'pbsChangerSelector',
+	    name: 'changer',
+	    skipEmptyText: true,
+	    allowBlank: true,
+	    autoSelect: false,
+	    emptyText: gettext('No Changer'),
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    listeners: {
+		change: function(field, value) {
+		    let disableSlotField = !value || value === '';
+		    console.log(value);
+		    field
+			.up('window')
+			.down('field[name=changer-drivenum]')
+			.setDisabled(disableSlotField);
+		},
+	    },
+	},
+	{
+	    fieldLabel: gettext('Changer Slot'),
+	    xtype: 'proxmoxintegerfield',
+	    name: 'changer-drivenum',
+	    disabled: true,
+	    allowBlank: true,
+	    emptyText: '0',
+	    minValue: 0,
+	    maxValue: 8,
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
+	{
+	    fieldLabel: gettext('Path'),
+	    xtype: 'pbsTapeDevicePathSelector',
+	    type: 'drives',
+	    name: 'path',
+	    allowBlank: false,
+	},
+    ],
+});
+
diff --git a/www/tape/window/LabelMedia.js b/www/tape/window/LabelMedia.js
new file mode 100644
index 00000000..ec7fca5d
--- /dev/null
+++ b/www/tape/window/LabelMedia.js
@@ -0,0 +1,47 @@
+Ext.define('PBS.TapeManagement.LabelMediaWindow', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pbsLabelMediaWindow',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    isCreate: true,
+    isAdd: true,
+    title: gettext('Label Media'),
+    submitText: gettext('OK'),
+
+    showProgress: true,
+
+    items: [
+	{
+	    xtype: 'displayfield',
+	    fieldLabel: gettext('Drive'),
+	    cbind: {
+		value: '{driveid}',
+	    },
+	},
+	{
+	    fieldLabel: gettext('Label'),
+	    name: 'label-text',
+	    xtype: 'proxmoxtextfield',
+	    allowBlank: false,
+	},
+	{
+	    xtype: 'pbsMediaPoolSelector',
+	    fieldLabel: gettext('Media Pool'),
+	    name: 'pool',
+	    allowBlank: true,
+	    skipEmptyText: true,
+	},
+    ],
+
+    initComponent: function() {
+	let me = this;
+	if (!me.driveid) {
+	    throw "no driveid given";
+	}
+
+	let driveid = encodeURIComponent(me.driveid);
+	me.url = `/api2/extjs/tape/drive/${driveid}/label-media`;
+	me.callParent();
+    },
+});
+
diff --git a/www/tape/window/PoolEdit.js b/www/tape/window/PoolEdit.js
new file mode 100644
index 00000000..c5148c9d
--- /dev/null
+++ b/www/tape/window/PoolEdit.js
@@ -0,0 +1,64 @@
+Ext.define('PBS.TapeManagement.PoolEditWindow', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pbsPoolEditWindow',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    isCreate: true,
+    isAdd: true,
+    subject: gettext('Media Pool'),
+    cbindData: function(initialConfig) {
+	let me = this;
+
+	let poolid = initialConfig.poolid;
+	let baseurl = '/api2/extjs/config/media-pool';
+
+	me.isCreate = !poolid;
+	me.url = poolid ? `${baseurl}/${encodeURIComponent(poolid)}` : baseurl;
+	me.method = poolid ? 'PUT' : 'POST';
+
+	return { };
+    },
+
+    items: [
+	{
+	    fieldLabel: gettext('Name'),
+	    name: 'name',
+	    xtype: 'pmxDisplayEditField',
+	    renderer: Ext.htmlEncode,
+	    allowBlank: false,
+	    cbind: {
+		editable: '{isCreate}',
+	    },
+	},
+	{
+	    fieldLabel: gettext('Drive'),
+	    xtype: 'pbsDriveSelector',
+	    name: 'drive',
+	    allowBlank: false,
+	    autoSelect: false,
+	},
+	{
+	    fieldLabel: gettext('Allocation'),
+	    xtype: 'pbsAllocationSelector',
+	    name: 'allocation',
+	    skipEmptyText: true,
+	    allowBlank: true,
+	    autoSelect: false,
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
+	{
+	    fieldLabel: gettext('Retention'),
+	    xtype: 'pbsRetentionSelector',
+	    name: 'retention',
+	    skipEmptyText: true,
+	    allowBlank: true,
+	    autoSelect: false,
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
+    ],
+});
+
diff --git a/www/tape/window/TapeBackup.js b/www/tape/window/TapeBackup.js
new file mode 100644
index 00000000..a3ceacdc
--- /dev/null
+++ b/www/tape/window/TapeBackup.js
@@ -0,0 +1,43 @@
+Ext.define('PBS.TapeManagement.TapeBackupWindow', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'pbsTapeBackupWindow',
+
+    subject: gettext('Backup'),
+    url: '/api2/extjs/tape/backup',
+    method: 'POST',
+    showTaskViewer: true,
+    isCreate: true,
+
+    items: [
+	{
+	    xtype: 'pbsDataStoreSelector',
+	    fieldLabel: gettext('Datastore'),
+	    name: 'store',
+	},
+	{
+	    xtype: 'pbsMediaPoolSelector',
+	    fieldLabel: gettext('Media Pool'),
+	    name: 'pool',
+	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    name: 'export-media-set',
+	    fieldLabel: gettext('Export Media Set'),
+	    listeners: {
+		change: function(cb, value) {
+		    let me = this;
+		    let eject = me.up('window').down('proxmoxcheckbox[name=eject-media]');
+		    if (value) {
+			eject.setValue(false);
+		    }
+		    eject.setDisabled(!!value);
+		},
+	    },
+	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    name: 'eject-media',
+	    fieldLabel: gettext('Eject Media'),
+	},
+    ],
+});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 07/15] ui: tape: add BackupOverview Panel
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (5 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 06/15] ui: tape: add Edit Windows Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 08/15] ui: tape: add ChangerStatus panel Dominik Csapak
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

shows all tapes with the relevant info
* which pool it belongs to
* what backups are on it
* which media-set
* location
* etc.

This is very rough, and maybe not the best way to display this information.
It may make sense to reverse the tree, i.e. having pools at top-level,
then media-sets, then tapes, then snapshots..

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile               |   1 +
 www/tape/BackupOverview.js | 150 +++++++++++++++++++++++++++++++++++++
 2 files changed, 151 insertions(+)
 create mode 100644 www/tape/BackupOverview.js

diff --git a/www/Makefile b/www/Makefile
index 827633a3..aad5f7ce 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -20,6 +20,7 @@ TAPE_UI_FILES=						\
 	tape/window/LabelMedia.js			\
 	tape/window/PoolEdit.js				\
 	tape/window/TapeBackup.js			\
+	tape/BackupOverview.js				\
 	TapeManagement.js
 endif
 
diff --git a/www/tape/BackupOverview.js b/www/tape/BackupOverview.js
new file mode 100644
index 00000000..4743dcc0
--- /dev/null
+++ b/www/tape/BackupOverview.js
@@ -0,0 +1,150 @@
+Ext.define('PBS.TapeManagement.BackupOverview', {
+    extend: 'Ext.tree.Panel',
+    alias: 'widget.pbsBackupOverview',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	backup: function() {
+	    let me = this;
+	    Ext.create('PBS.TapeManagement.TapeBackupWindow', {
+		listeners: {
+		    destroy: function() {
+			me.reload();
+		    },
+		},
+	    }).show();
+	},
+
+	reload: async function() {
+	    let me = this;
+	    let view = me.getView();
+
+	    Proxmox.Utils.setErrorMask(view, true);
+
+	    try {
+		let list_response = await PBS.Async.api2({
+		    url: '/api2/extjs/tape/media/list',
+		});
+		let list = list_response.result.data.sort(
+		    (a, b) => a['label-text'].localeCompare(b['label-text']),
+		);
+
+		let content = {};
+
+		let content_response = await PBS.Async.api2({
+		    url: '/api2/extjs/tape/media/content',
+		});
+
+		let content_list = content_response.result.data.sort(
+		    (a, b) => a.snapshot.localeCompare(b.snapshot),
+		);
+
+		for (let entry of content_list) {
+		    let tape = entry['label-text'];
+		    entry['label-text'] = entry.snapshot;
+		    entry.leaf = true;
+		    if (content[tape] === undefined) {
+			content[tape] = [entry];
+		    } else {
+			content[tape].push(entry);
+		    }
+		}
+
+		for (let child of list) {
+		    let tape = child['label-text'];
+		    if (content[tape]) {
+			child.children = content[tape];
+			child.leaf = false;
+		    } else {
+			child.leaf = true;
+		    }
+		}
+
+		view.setRootNode({
+		    expanded: true,
+		    children: list,
+		});
+
+		Proxmox.Utils.setErrorMask(view, false);
+	    } catch (error) {
+		Proxmox.Utils.setErrorMask(view, error.toString());
+	    }
+	},
+    },
+
+    listeners: {
+	activate: 'reload',
+    },
+
+    store: {
+	sorters: 'label-text',
+	data: [],
+    },
+
+    rootVisible: false,
+
+    tbar: [
+	{
+	    text: gettext('Reload'),
+	    handler: 'reload',
+	},
+	'-',
+	{
+	    text: gettext('New Backup'),
+	    handler: 'backup',
+	},
+    ],
+
+    columns: [
+	{
+	    xtype: 'treecolumn',
+	    text: gettext('Tape/Backup'),
+	    dataIndex: 'label-text',
+	    flex: 3,
+	},
+	{
+	    text: gettext('Location'),
+	    dataIndex: 'location',
+	    flex: 1,
+	    renderer: function(value) {
+		if (!value) {
+		    return "";
+		}
+		let result;
+		if ((result = /^online-(.+)$/.exec(value)) !== null) {
+		    return Ext.htmlEncode(result[1]);
+		}
+
+		return value;
+	    },
+	},
+	{
+	    text: gettext('Status'),
+	    dataIndex: 'status',
+	    flex: 1,
+	},
+	{
+	    text: gettext('Media Set'),
+	    dataIndex: 'media-set-name',
+	    flex: 2,
+	},
+	{
+	    text: gettext('Pool'),
+	    dataIndex: 'pool',
+	    flex: 1,
+	},
+	{
+	    text: gettext('Sequence Nr.'),
+	    dataIndex: 'seq-nr',
+	    flex: 0.5,
+	},
+	{
+	    text: gettext('Backup Time'),
+	    dataIndex: 'backup-time',
+	    renderer: (time) => time !== undefined ? new Date(time*1000) : "",
+	    flex: 1,
+	},
+    ],
+});
+
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 08/15] ui: tape: add ChangerStatus panel
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (6 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 07/15] ui: tape: add BackupOverview Panel Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 09/15] ui: tape: add DriveConfig panel Dominik Csapak
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

this lets the users manage changers and lets them view the status of one
by having an overview of:
* slots for tapes
* import/export slots
* drives

lets the user:
* barcode-label all the tapes in the library
* move tapes between slots, into/out of drives
* show some basic info when a tape is loaded into a drive
* show the status of a drive
* clean a drive

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile              |   1 +
 www/tape/ChangerStatus.js | 631 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 632 insertions(+)
 create mode 100644 www/tape/ChangerStatus.js

diff --git a/www/Makefile b/www/Makefile
index aad5f7ce..db486f71 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -21,6 +21,7 @@ TAPE_UI_FILES=						\
 	tape/window/PoolEdit.js				\
 	tape/window/TapeBackup.js			\
 	tape/BackupOverview.js				\
+	tape/ChangerStatus.js				\
 	TapeManagement.js
 endif
 
diff --git a/www/tape/ChangerStatus.js b/www/tape/ChangerStatus.js
new file mode 100644
index 00000000..75af75ce
--- /dev/null
+++ b/www/tape/ChangerStatus.js
@@ -0,0 +1,631 @@
+Ext.define('PBS.TapeManagement.ChangerStatus', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pbsChangerStatus',
+
+    viewModel: {
+	data: {
+	    changer: '',
+	},
+
+	formulas: {
+	    changerSelected: (get) => get('changer') !== '',
+	},
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	changerChange: function(field, value) {
+	    let me = this;
+	    let view = me.getView();
+	    let vm = me.getViewModel();
+	    vm.set('changer', value);
+	    if (view.rendered) {
+		me.reload();
+	    }
+	},
+
+	onAdd: function() {
+	    let me = this;
+	    Ext.create('PBS.TapeManagement.ChangerEditWindow', {
+		listeners: {
+		    destroy: function() {
+			me.reloadList();
+		    },
+		},
+	    }).show();
+	},
+
+	onEdit: function() {
+	    let me = this;
+	    let vm = me.getViewModel();
+	    let changerid = vm.get('changer');
+	    Ext.create('PBS.TapeManagement.ChangerEditWindow', {
+		changerid,
+		autoLoad: true,
+		listeners: {
+		    destroy: () => me.reload(),
+		},
+	    }).show();
+	},
+
+	slotTransfer: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let vm = me.getViewModel();
+	    let from = record.data['entry-id'];
+	    let changer = encodeURIComponent(vm.get('changer'));
+	    Ext.create('Proxmox.window.Edit', {
+		title: gettext('Transfer'),
+		isCreate: true,
+		submitText: gettext('OK'),
+		method: 'POST',
+		url: `/api2/extjs/tape/changer/${changer}/transfer`,
+		items: [
+		    {
+			xtype: 'displayfield',
+			name: 'from',
+			value: from,
+			submitValue: true,
+			fieldLabel: gettext('From Slot'),
+		    },
+		    {
+			xtype: 'proxmoxintegerfield',
+			name: 'to',
+			fieldLabel: gettext('To Slot'),
+		    },
+		],
+		listeners: {
+		    destroy: function() {
+			me.reload();
+		    },
+		},
+	    }).show();
+	},
+
+	load: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let vm = me.getViewModel();
+	    let label = record.data['label-text'];
+
+	    let changer = vm.get('changer');
+
+	    Ext.create('Proxmox.window.Edit', {
+		isCreate: true,
+		submitText: gettext('OK'),
+		title: gettext('Load Media into Drive'),
+		url: `/api2/extjs/tape/drive`,
+		submitUrl: function(url, values) {
+		    let drive = values.drive;
+		    delete values.drive;
+		    return `${url}/${encodeURIComponent(drive)}/load-media`;
+		},
+		items: [
+		    {
+			xtype: 'displayfield',
+			name: 'label-text',
+			value: label,
+			submitValue: true,
+			fieldLabel: gettext('Media'),
+		    },
+		    {
+			xtype: 'pbsDriveSelector',
+			fieldLabel: gettext('Drive'),
+			changer: changer,
+			name: 'drive',
+		    },
+		],
+		listeners: {
+		    destroy: function() {
+			me.reload();
+		    },
+		},
+	    }).show();
+	},
+
+	unload: async function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    Proxmox.Utils.setErrorMask(view, true);
+	    try {
+		await PBS.Async.api2({
+		    method: 'PUT',
+		    url: `/api2/extjs/tape/drive/${encodeURIComponent(drive)}/unload`,
+		});
+		Proxmox.Utils.setErrorMask(view);
+		me.reload();
+	    } catch (error) {
+		Ext.Msg.alert(gettext('Error'), error);
+		Proxmox.Utils.setErrorMask(view);
+		me.reload();
+	    }
+	},
+
+	driveCommand: function(driveid, command, callback, params, method) {
+	    let me = this;
+	    let view = me.getView();
+	    params = params || {};
+	    method = method || 'GET';
+	    Proxmox.Utils.API2Request({
+		url: `/api2/extjs/tape/drive/${driveid}/${command}`,
+		method,
+		waitMsgTarget: view,
+		params,
+		success: function(response) {
+		    callback(response);
+		},
+		failure: function(response) {
+		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		},
+	    });
+	},
+
+	cartridgeMemory: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'cartridge-memory', function(response) {
+		Ext.create('Ext.window.Window', {
+		    title: gettext('Cartridge Memory'),
+		    modal: true,
+		    width: 600,
+		    height: 450,
+		    layout: 'fit',
+		    scrollable: true,
+		    items: [
+			{
+			    xtype: 'grid',
+			    store: {
+				data: response.result.data,
+			    },
+			    columns: [
+				{
+				    text: gettext('ID'),
+				    dataIndex: 'id',
+				    width: 60,
+				},
+				{
+				    text: gettext('Name'),
+				    dataIndex: 'name',
+				    flex: 2,
+				},
+				{
+				    text: gettext('Value'),
+				    dataIndex: 'value',
+				    flex: 1,
+				},
+			    ],
+			},
+		    ],
+		}).show();
+	    });
+	},
+
+	cleanDrive: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'clean', function(response) {
+		Ext.create('Proxmox.window.TaskProgress', {
+		    upid: response.result.data,
+		    taskDone: function() {
+			me.reload();
+		    },
+		}).show();
+	    }, {}, 'PUT');
+	},
+
+	volumeStatistics: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'volume-statistics', function(response) {
+		Ext.create('Ext.window.Window', {
+		    title: gettext('Volume Statistics'),
+		    modal: true,
+		    width: 600,
+		    height: 450,
+		    layout: 'fit',
+		    scrollable: true,
+		    items: [
+			{
+			    xtype: 'grid',
+			    store: {
+				data: response.result.data,
+			    },
+			    columns: [
+				{
+				    text: gettext('ID'),
+				    dataIndex: 'id',
+				    width: 60,
+				},
+				{
+				    text: gettext('Name'),
+				    dataIndex: 'name',
+				    flex: 2,
+				},
+				{
+				    text: gettext('Value'),
+				    dataIndex: 'value',
+				    flex: 1,
+				},
+			    ],
+			},
+		    ],
+		}).show();
+	    });
+	},
+
+	readLabel: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'read-label', function(response) {
+		let lines = [];
+		for (const [key, val] of Object.entries(response.result.data)) {
+		    lines.push(`${key}: ${val}`);
+		}
+
+		let txt = lines.join('<br>');
+
+		Ext.Msg.show({
+		    title: gettext('Label Information'),
+		    message: txt,
+		    icon: undefined,
+		});
+	    });
+	},
+
+	status: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'status', function(response) {
+		let lines = [];
+		for (const [key, val] of Object.entries(response.result.data)) {
+		    lines.push(`${key}: ${val}`);
+		}
+
+		let txt = lines.join('<br>');
+
+		Ext.Msg.show({
+		    title: gettext('Label Information'),
+		    message: txt,
+		    icon: undefined,
+		});
+	    });
+	},
+
+	reloadList: function() {
+	    let me = this;
+	    me.lookup('changerselector').getStore().load();
+	},
+
+	barcodeLabel: function() {
+	    let me = this;
+	    let vm = me.getViewModel();
+	    let changer = vm.get('changer');
+	    if (changer === '') {
+		return;
+	    }
+
+	    Ext.create('Proxmox.window.Edit', {
+		title: gettext('Barcode Label'),
+		showTaskViewer: true,
+		url: '/api2/extjs/tape/drive',
+		submitUrl: function(url, values) {
+		    let drive = values.drive;
+		    delete values.drive;
+		    return `${url}/${encodeURIComponent(drive)}/barcode-label-media`;
+		},
+
+		items: [
+		    {
+			xtype: 'pbsDriveSelector',
+			fieldLabel: gettext('Drive'),
+			name: 'drive',
+			changer: changer,
+		    },
+		    {
+			xtype: 'pbsMediaPoolSelector',
+			fieldLabel: gettext('Pool'),
+			name: 'pool',
+			skipEmptyText: true,
+			allowBlank: true,
+		    },
+		],
+	    }).show();
+	},
+
+	reload: async function() {
+	    let me = this;
+	    let view = me.getView();
+	    let vm = me.getViewModel();
+	    let changer = vm.get('changer');
+	    if (changer === '') {
+		return;
+	    }
+
+	    try {
+		Proxmox.Utils.setErrorMask(view, true);
+		Proxmox.Utils.setErrorMask(me.lookup('content'));
+		let status = await PBS.Async.api2({
+		    url: `/api2/extjs/tape/changer/${encodeURIComponent(changer)}/status`,
+		});
+		let drives = await PBS.Async.api2({
+		    url: `/api2/extjs/tape/drive?changer=${encodeURIComponent(changer)}`,
+		});
+
+		let data = {
+		    slot: [],
+		    'import-export': [],
+		    drive: [],
+		};
+
+		let drive_entries = {};
+
+		for (const entry of drives.result.data) {
+		    drive_entries[entry['changer-drivenum'] || 0] = entry;
+		}
+
+		for (let entry of status.result.data) {
+		    let type = entry['entry-kind'];
+
+		    if (type === 'drive' && drive_entries[entry['entry-id']] !== undefined) {
+			entry = Ext.applyIf(entry, drive_entries[entry['entry-id']]);
+		    }
+
+		    data[type].push(entry);
+		}
+
+
+		me.lookup('slots').getStore().setData(data.slot);
+		me.lookup('import_export').getStore().setData(data['import-export']);
+		me.lookup('drives').getStore().setData(data.drive);
+
+		Proxmox.Utils.setErrorMask(view);
+	    } catch (err) {
+		Proxmox.Utils.setErrorMask(view);
+		Proxmox.Utils.setErrorMask(me.lookup('content'), err);
+	    }
+	},
+    },
+
+    listeners: {
+	activate: 'reload',
+    },
+
+    tbar: [
+	{
+	    fieldLabel: gettext('Changer'),
+	    xtype: 'pbsChangerSelector',
+	    reference: 'changerselector',
+	    autoSelect: true,
+	    listeners: {
+		change: 'changerChange',
+	    },
+	},
+	'-',
+	{
+	    text: gettext('Reload'),
+	    xtype: 'proxmoxButton',
+	    handler: 'reload',
+	    selModel: false,
+	},
+	'-',
+	{
+	    text: gettext('Add'),
+	    xtype: 'proxmoxButton',
+	    handler: 'onAdd',
+	    selModel: false,
+	},
+	{
+	    text: gettext('Edit'),
+	    xtype: 'proxmoxButton',
+	    handler: 'onEdit',
+	    bind: {
+		disabled: '{!changerSelected}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxStdRemoveButton',
+	    baseurl: '/api2/extjs/config/changer',
+	    callback: 'reloadList',
+	    selModel: false,
+	    getRecordName: function() {
+		let me = this;
+		let vm = me.up('panel').getViewModel();
+		return vm.get('changer');
+	    },
+	    getUrl: function() {
+		let me = this;
+		let vm = me.up('panel').getViewModel();
+		return `/api2/extjs/config/changer/${vm.get('changer')}`;
+	    },
+	    bind: {
+		disabled: '{!changerSelected}',
+	    },
+	},
+	'-',
+	{
+	    text: gettext('Barcode Label'),
+	    xtype: 'proxmoxButton',
+	    handler: 'barcodeLabel',
+	    iconCls: 'fa fa-barcode',
+	    bind: {
+		disabled: '{!changerSelected}',
+	    },
+	},
+    ],
+
+    layout: 'auto',
+    bodyPadding: 5,
+    scrollable: true,
+
+    items: [
+	{
+	    xtype: 'container',
+	    reference: 'content',
+	    layout: {
+		type: 'hbox',
+		aling: 'stretch',
+	    },
+	    items: [
+		{
+		    xtype: 'grid',
+		    reference: 'slots',
+		    title: gettext('Slots'),
+		    padding: 5,
+		    flex: 1,
+		    store: {
+			data: [],
+		    },
+		    columns: [
+			{
+			    text: gettext('Slot'),
+			    dataIndex: 'entry-id',
+			    width: 50,
+			},
+			{
+			    text: gettext("Content"),
+			    dataIndex: 'label-text',
+			    flex: 1,
+			    renderer: (value) => value || '',
+			},
+			{
+			    text: gettext('Actions'),
+			    xtype: 'actioncolumn',
+			    width: 100,
+			    items: [
+				{
+				    iconCls: 'fa fa-rotate-90 fa-exchange',
+				    handler: 'slotTransfer',
+				    isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+				},
+				{
+				    iconCls: 'fa fa-rotate-90 fa-upload',
+				    handler: 'load',
+				    isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+				},
+			    ],
+			},
+		    ],
+		},
+		{
+		    xtype: 'container',
+		    flex: 2,
+		    defaults: {
+			padding: 5,
+		    },
+		    items: [
+			{
+			    xtype: 'grid',
+			    reference: 'drives',
+			    title: gettext('Drives'),
+			    store: {
+				fields: ['entry-id', 'label-text', 'model', 'name', 'vendor', 'serial'],
+				data: [],
+			    },
+			    columns: [
+				{
+				    text: gettext('Slot'),
+				    dataIndex: 'entry-id',
+				    width: 50,
+				},
+				{
+				    text: gettext("Content"),
+				    dataIndex: 'label-text',
+				    flex: 1,
+				    renderer: (value) => value || '',
+				},
+				{
+				    text: gettext("Name"),
+				    sortable: true,
+				    dataIndex: 'name',
+				    flex: 1,
+				    renderer: Ext.htmlEncode,
+				},
+				{
+				    text: gettext("Vendor"),
+				    sortable: true,
+				    dataIndex: 'vendor',
+				    flex: 1,
+				    renderer: Ext.htmlEncode,
+				},
+				{
+				    text: gettext("Model"),
+				    sortable: true,
+				    dataIndex: 'model',
+				    flex: 1,
+				    renderer: Ext.htmlEncode,
+				},
+				{
+				    text: gettext("Serial"),
+				    sortable: true,
+				    dataIndex: 'serial',
+				    flex: 1,
+				    renderer: Ext.htmlEncode,
+				},
+				{
+				    xtype: 'actioncolumn',
+				    text: gettext('Actions'),
+				    width: 140,
+				    items: [
+					{
+					    iconCls: 'fa fa-rotate-270 fa-upload',
+					    handler: 'unload',
+					    isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+					},
+					{
+					    iconCls: 'fa fa-hdd-o',
+					    handler: 'cartridgeMemory',
+					    isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+					},
+					{
+					    iconCls: 'fa fa-line-chart',
+					    handler: 'volumeStatistics',
+					    isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+					},
+					{
+					    iconCls: 'fa fa-tag',
+					    handler: 'readLabel',
+					    isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+					},
+					{
+					    iconCls: 'fa fa-info-circle',
+					    handler: 'status',
+					},
+					{
+					    iconCls: 'fa fa-shower',
+					    handler: 'cleanDrive',
+					},
+				    ],
+				},
+			    ],
+			},
+			{
+			    xtype: 'grid',
+			    reference: 'import_export',
+			    store: {
+				data: [],
+			    },
+			    title: gettext('Import-Export'),
+			    columns: [
+				{
+				    text: gettext('Slot'),
+				    dataIndex: 'entry-id',
+				    width: 50,
+				},
+				{
+				    text: gettext("Content"),
+				    dataIndex: 'label-text',
+				    renderer: (value) => value || '',
+				    flex: 1,
+				},
+				{
+				    text: gettext('Actions'),
+				    items: [],
+				    width: 80,
+				},
+			    ],
+			},
+		    ],
+		},
+	    ],
+	},
+    ],
+});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 09/15] ui: tape: add DriveConfig panel
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (7 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 08/15] ui: tape: add ChangerStatus panel Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 10/15] ui: tape: add PoolConfig Dominik Csapak
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

mostly typical CRUD interface for managing drives, with an
additional actioncolumn containing some useful actions, e.g.
* reading the label
* show volume-statistics
* show the status
* label the inserted tape

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile            |   1 +
 www/tape/DriveConfig.js | 316 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 317 insertions(+)
 create mode 100644 www/tape/DriveConfig.js

diff --git a/www/Makefile b/www/Makefile
index db486f71..9a6ff357 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -22,6 +22,7 @@ TAPE_UI_FILES=						\
 	tape/window/TapeBackup.js			\
 	tape/BackupOverview.js				\
 	tape/ChangerStatus.js				\
+	tape/DriveConfig.js				\
 	TapeManagement.js
 endif
 
diff --git a/www/tape/DriveConfig.js b/www/tape/DriveConfig.js
new file mode 100644
index 00000000..8eb40da3
--- /dev/null
+++ b/www/tape/DriveConfig.js
@@ -0,0 +1,316 @@
+Ext.define('pbs-model-drives', {
+    extend: 'Ext.data.Model',
+    fields: ['path', 'model', 'name', 'serial', 'vendor', 'changer', 'changer-slot'],
+    idProperty: 'name',
+});
+
+Ext.define('PBS.TapeManagement.DrivePanel', {
+    extend: 'Ext.grid.Panel',
+    alias: 'widget.pbsTapeDrivePanel',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	onAdd: function() {
+	    let me = this;
+	    Ext.create('PBS.TapeManagement.DriveEditWindow', {
+		listeners: {
+		    destroy: function() {
+			me.reload();
+		    },
+		},
+	    }).show();
+	},
+
+	onEdit: function() {
+	    let me = this;
+	    let view = me.getView();
+	    let selection = view.getSelection();
+	    if (!selection || selection.length < 1) {
+		return;
+	    }
+	    Ext.create('PBS.TapeManagement.DriveEditWindow', {
+		driveid: selection[0].data.name,
+		autoLoad: true,
+		listeners: {
+		    destroy: () => me.reload(),
+		},
+	    }).show();
+	},
+
+	status: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'status', function(response) {
+		let lines = [];
+		for (const [key, val] of Object.entries(response.result.data)) {
+		    lines.push(`${key}: ${val}`);
+		}
+
+		let txt = lines.join('<br>');
+
+		Ext.Msg.show({
+		    title: gettext('Label Information'),
+		    message: txt,
+		    icon: undefined,
+		});
+	    });
+	},
+
+	readLabel: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'read-label', function(response) {
+		let lines = [];
+		for (const [key, val] of Object.entries(response.result.data)) {
+		    lines.push(`${key}: ${val}`);
+		}
+
+		let txt = lines.join('<br>');
+
+		Ext.Msg.show({
+		    title: gettext('Label Information'),
+		    message: txt,
+		    icon: undefined,
+		});
+	    });
+	},
+
+	volumeStatistics: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'volume-statistics', function(response) {
+		Ext.create('Ext.window.Window', {
+		    title: gettext('Volume Statistics'),
+		    modal: true,
+		    width: 600,
+		    height: 450,
+		    layout: 'fit',
+		    scrollable: true,
+		    items: [
+			{
+			    xtype: 'grid',
+			    store: {
+				data: response.result.data,
+			    },
+			    columns: [
+				{
+				    text: gettext('ID'),
+				    dataIndex: 'id',
+				    width: 60,
+				},
+				{
+				    text: gettext('Name'),
+				    dataIndex: 'name',
+				    flex: 2,
+				},
+				{
+				    text: gettext('Value'),
+				    dataIndex: 'value',
+				    flex: 1,
+				},
+			    ],
+			},
+		    ],
+		}).show();
+	    });
+	},
+
+	cartridgeMemory: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let drive = record.data.name;
+	    me.driveCommand(drive, 'cartridge-memory', function(response) {
+		Ext.create('Ext.window.Window', {
+		    title: gettext('Cartridge Memory'),
+		    modal: true,
+		    width: 600,
+		    height: 450,
+		    layout: 'fit',
+		    scrollable: true,
+		    items: [
+			{
+			    xtype: 'grid',
+			    store: {
+				data: response.result.data,
+			    },
+			    columns: [
+				{
+				    text: gettext('ID'),
+				    dataIndex: 'id',
+				    width: 60,
+				},
+				{
+				    text: gettext('Name'),
+				    dataIndex: 'name',
+				    flex: 2,
+				},
+				{
+				    text: gettext('Value'),
+				    dataIndex: 'value',
+				    flex: 1,
+				},
+			    ],
+			},
+		    ],
+		}).show();
+	    });
+	},
+
+	driveCommand: function(driveid, command, callback, params, method) {
+	    let me = this;
+	    let view = me.getView();
+	    params = params || {};
+	    method = method || 'GET';
+	    Proxmox.Utils.API2Request({
+		url: `/api2/extjs/tape/drive/${driveid}/${command}`,
+		method,
+		waitMsgTarget: view,
+		params,
+		success: function(response) {
+		    callback(response);
+		},
+		failure: function(response) {
+		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		},
+	    });
+	},
+
+	labelMedia: function(view, rI, cI, button, el, record) {
+	    let me = this;
+	    let driveid = record.data.name;
+
+	    Ext.create('PBS.TapeManagement.LabelMediaWindow', {
+		driveid,
+	    }).show();
+	},
+
+	reload: function() {
+	    this.getView().getStore().rstore.load();
+	},
+
+	stopStore: function() {
+	    this.getView().getStore().rstore.stopUpdate();
+	},
+
+	startStore: function() {
+	    this.getView().getStore().rstore.startUpdate();
+	},
+    },
+
+    listeners: {
+	beforedestroy: 'stopStore',
+	deactivate: 'stopStore',
+	activate: 'startStore',
+	itemdblclick: 'onEdit',
+    },
+
+    store: {
+	type: 'diff',
+	rstore: {
+	    type: 'update',
+	    storeid: 'proxmox-tape-drives',
+	    model: 'pbs-model-drives',
+	    proxy: {
+		type: 'proxmox',
+		url: "/api2/json/tape/drive",
+	    },
+	},
+	sorters: 'name',
+    },
+
+    tbar: [
+	{
+	    text: gettext('Add'),
+	    xtype: 'proxmoxButton',
+	    handler: 'onAdd',
+	    selModel: false,
+	},
+	'-',
+	{
+	    text: gettext('Edit'),
+	    xtype: 'proxmoxButton',
+	    handler: 'onEdit',
+	    disabled: true,
+	},
+	{
+	    xtype: 'proxmoxStdRemoveButton',
+	    baseurl: '/api2/extjs/config/drive',
+	    callback: 'reload',
+	},
+    ],
+    columns: [
+	{
+	    text: gettext('Name'),
+	    dataIndex: 'name',
+	    flex: 1,
+	},
+	{
+	    text: gettext('Path'),
+	    dataIndex: 'path',
+	    flex: 2,
+	},
+	{
+	    text: gettext('Vendor'),
+	    dataIndex: 'vendor',
+	    flex: 1,
+	},
+	{
+	    text: gettext('Model'),
+	    dataIndex: 'model',
+	    flex: 1,
+	},
+	{
+	    text: gettext('Serial'),
+	    dataIndex: 'serial',
+	    flex: 1,
+	},
+	{
+	    text: gettext('Changer'),
+	    flex: 1,
+	    dataIndex: 'changer',
+	    renderer: function(value, mD, record) {
+		if (!value) {
+		    return "";
+		}
+		let drive_num = record.data['changer-drivenum'] || 0;
+		let drive_text = gettext("Drive {0}");
+		return `${value} (${Ext.String.format(drive_text, drive_num)})`;
+	    },
+	    sorter: function(a, b) {
+		let ch_a = a.data.changer || "";
+		let ch_b = b.data.changer || "";
+		let num_a = a.data['changer-drivenum'] || 0;
+		let num_b = b.data['changer-drivenum'] || 0;
+		return ch_a > ch_b ? -1 : ch_a < ch_b ? 1 : num_b - num_a;
+	    },
+	},
+	{
+	    text: gettext('Actions'),
+	    width: 120,
+	    xtype: 'actioncolumn',
+	    items: [
+		{
+		    iconCls: 'fa fa-hdd-o',
+		    handler: 'cartridgeMemory',
+		},
+		{
+		    iconCls: 'fa fa-line-chart',
+		    handler: 'volumeStatistics',
+		},
+		{
+		    iconCls: 'fa fa-tag',
+		    handler: 'readLabel',
+		},
+		{
+		    iconCls: 'fa fa-info-circle',
+		    handler: 'status',
+		},
+		{
+		    iconCls: 'fa fa-pencil-square-o',
+		    handler: 'labelMedia',
+		},
+	    ],
+	},
+    ],
+});
+
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 10/15] ui: tape: add PoolConfig
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (8 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 09/15] ui: tape: add DriveConfig panel Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 11/15] ui: tape: move TapeManagement.js to tape dir Dominik Csapak
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

CRUD interface to manage media pools

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile           |   1 +
 www/tape/PoolConfig.js | 119 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 120 insertions(+)
 create mode 100644 www/tape/PoolConfig.js

diff --git a/www/Makefile b/www/Makefile
index 9a6ff357..27a89d90 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -23,6 +23,7 @@ TAPE_UI_FILES=						\
 	tape/BackupOverview.js				\
 	tape/ChangerStatus.js				\
 	tape/DriveConfig.js				\
+	tape/PoolConfig.js				\
 	TapeManagement.js
 endif
 
diff --git a/www/tape/PoolConfig.js b/www/tape/PoolConfig.js
new file mode 100644
index 00000000..1782d9dc
--- /dev/null
+++ b/www/tape/PoolConfig.js
@@ -0,0 +1,119 @@
+Ext.define('pbs-model-media-pool', {
+    extend: 'Ext.data.Model',
+    fields: ['drive', 'name', 'allocation', 'retention', 'template', 'encrypt'],
+    idProperty: 'name',
+});
+
+Ext.define('PBS.TapeManagement.PoolPanel', {
+    extend: 'Ext.grid.Panel',
+    alias: 'widget.pbsMediaPoolPanel',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	onAdd: function() {
+	    let me = this;
+	    Ext.create('PBS.TapeManagement.PoolEditWindow', {
+		listeners: {
+		    destroy: function() {
+			me.reload();
+		    },
+		},
+	    }).show();
+	},
+
+	onEdit: function() {
+	    let me = this;
+	    let view = me.getView();
+	    let selection = view.getSelection();
+	    if (!selection || selection.length < 1) {
+		return;
+	    }
+	    Ext.create('PBS.TapeManagement.PoolEditWindow', {
+		poolid: selection[0].data.name,
+		autoLoad: true,
+		listeners: {
+		    destroy: () => me.reload(),
+		},
+	    }).show();
+	},
+
+	reload: function() {
+	    this.getView().getStore().rstore.load();
+	},
+
+	stopStore: function() {
+	    this.getView().getStore().rstore.stopUpdate();
+	},
+
+	startStore: function() {
+	    this.getView().getStore().rstore.startUpdate();
+	},
+    },
+
+    listeners: {
+	beforedestroy: 'stopStore',
+	deactivate: 'stopStore',
+	activate: 'startStore',
+	itemdblclick: 'onEdit',
+    },
+
+    store: {
+	type: 'diff',
+	rstore: {
+	    type: 'update',
+	    storeid: 'proxmox-tape-media-pools',
+	    model: 'pbs-model-media-pool',
+	    proxy: {
+		type: 'proxmox',
+		url: "/api2/json/config/media-pool",
+	    },
+	},
+	sorters: 'name',
+    },
+
+    tbar: [
+	{
+	    text: gettext('Add'),
+	    xtype: 'proxmoxButton',
+	    handler: 'onAdd',
+	    selModel: false,
+	},
+	'-',
+	{
+	    text: gettext('Edit'),
+	    xtype: 'proxmoxButton',
+	    handler: 'onEdit',
+	    disabled: true,
+	},
+	{
+	    xtype: 'proxmoxStdRemoveButton',
+	    baseurl: '/api2/extjs/config/media-pool',
+	    callback: 'reload',
+	},
+    ],
+
+    columns: [
+	{
+	    text: gettext('Name'),
+	    dataIndex: 'name',
+	},
+	{
+	    text: gettext('Drive'),
+	    dataIndex: 'drive',
+	},
+	{
+	    text: gettext('Allocation'),
+	    dataIndex: 'allocation',
+	},
+	{
+	    text: gettext('Retention'),
+	    dataIndex: 'retention',
+	},
+	{
+	    text: gettext('Encryption Fingerprint'),
+	    dataIndex: 'encryption',
+	},
+    ],
+});
+
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 11/15] ui: tape: move TapeManagement.js to tape dir
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (9 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 10/15] ui: tape: add PoolConfig Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 12/15] ui: tape: use panels in tape interface Dominik Csapak
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile                     | 3 ++-
 www/{ => tape}/TapeManagement.js | 0
 2 files changed, 2 insertions(+), 1 deletion(-)
 rename www/{ => tape}/TapeManagement.js (100%)

diff --git a/www/Makefile b/www/Makefile
index 27a89d90..91899028 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -24,7 +24,8 @@ TAPE_UI_FILES=						\
 	tape/ChangerStatus.js				\
 	tape/DriveConfig.js				\
 	tape/PoolConfig.js				\
-	TapeManagement.js
+	tape/TapeManagement.js				\
+
 endif
 
 JSSRC=							\
diff --git a/www/TapeManagement.js b/www/tape/TapeManagement.js
similarity index 100%
rename from www/TapeManagement.js
rename to www/tape/TapeManagement.js
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 12/15] ui: tape: use panels in tape interface
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (10 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 11/15] ui: tape: move TapeManagement.js to tape dir Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 13/15] tape/changer: add vendor/model to DriveStatus Dominik Csapak
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/TapeManagement.js | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/www/tape/TapeManagement.js b/www/tape/TapeManagement.js
index d7400f06..f61c26d7 100644
--- a/www/tape/TapeManagement.js
+++ b/www/tape/TapeManagement.js
@@ -5,7 +5,31 @@ Ext.define('PBS.TapeManagement', {
     title: gettext('Tape Backup'),
 
     border: true,
-    defaults: { border: false },
+    defaults: {
+	border: false,
+	xtype: 'panel',
+    },
 
-    html: "Experimental tape backup GUI.",
+    items: [
+	{
+	    title: gettext('Backup'),
+	    itemId: 'backup',
+	    xtype: 'pbsBackupOverview',
+	},
+	{
+	    title: gettext('Changers'),
+	    itemId: 'changers',
+	    xtype: 'pbsChangerStatus',
+	},
+	{
+	    title: gettext('Drives'),
+	    itemId: 'drives',
+	    xtype: 'pbsTapeDrivePanel',
+	},
+	{
+	    title: gettext('Media Pools'),
+	    itemId: 'pools',
+	    xtype: 'pbsMediaPoolPanel',
+	},
+    ],
 });
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 13/15] tape/changer: add vendor/model to DriveStatus
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (11 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 12/15] ui: tape: use panels in tape interface Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 14/15] tape/changer: refactor marking of import/export slots from config Dominik Csapak
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/tape/changer/mod.rs                  |  4 ++++
 src/tape/changer/mtx/parse_mtx_status.rs |  6 ++++++
 src/tape/changer/sg_pt_changer.rs        | 16 ++++++++++------
 src/tape/drive/virtual_tape.rs           |  2 ++
 4 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs
index 30e30b94..0d378587 100644
--- a/src/tape/changer/mod.rs
+++ b/src/tape/changer/mod.rs
@@ -38,6 +38,10 @@ pub struct DriveStatus {
     pub status: ElementStatus,
     /// Drive Identifier (Serial number)
     pub drive_serial_number: Option<String>,
+    /// Drive Vendor
+    pub vendor: Option<String>,
+    /// Drive Model
+    pub model: Option<String>,
     /// Element Address
     pub element_address: u16,
 }
diff --git a/src/tape/changer/mtx/parse_mtx_status.rs b/src/tape/changer/mtx/parse_mtx_status.rs
index c042c318..b2f5c5d7 100644
--- a/src/tape/changer/mtx/parse_mtx_status.rs
+++ b/src/tape/changer/mtx/parse_mtx_status.rs
@@ -46,6 +46,8 @@ fn parse_drive_status(i: &str, id: u64) -> IResult<&str, DriveStatus> {
             loaded_slot,
             status: ElementStatus::Empty,
             drive_serial_number: None,
+            vendor: None,
+            model: None,
             element_address: id as u16,
         };
         return Ok((empty, status));
@@ -71,6 +73,8 @@ fn parse_drive_status(i: &str, id: u64) -> IResult<&str, DriveStatus> {
             loaded_slot,
             status: ElementStatus::VolumeTag(tag.to_string()),
             drive_serial_number: None,
+            vendor: None,
+            model: None,
             element_address: id as u16,
         };
         return Ok((i, status));
@@ -82,6 +86,8 @@ fn parse_drive_status(i: &str, id: u64) -> IResult<&str, DriveStatus> {
         loaded_slot,
         status: ElementStatus::Full,
         drive_serial_number: None,
+        vendor: None,
+        model: None,
         element_address: id as u16,
     };
     Ok((i, status))
diff --git a/src/tape/changer/sg_pt_changer.rs b/src/tape/changer/sg_pt_changer.rs
index 85961457..b6c64381 100644
--- a/src/tape/changer/sg_pt_changer.rs
+++ b/src/tape/changer/sg_pt_changer.rs
@@ -641,23 +641,25 @@ fn decode_element_status_page(
 
                         let dvcid: DvcidHead = unsafe { reader.read_be_value()? };
 
-                        let drive_serial_number = match (dvcid.code_set, dvcid.identifier_type) {
+                        let (drive_serial_number, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) {
                             (2, 0) => { // Serial number only (Quantum Superloader3 uses this)
                                 let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?;
                                 let serial = scsi_ascii_to_string(&serial);
-                                Some(serial)
+                                (Some(serial), None, None)
                             }
                             (2, 1) => {
                                 if dvcid.identifier_len != 34 {
                                     bail!("got wrong DVCID length");
                                 }
-                                let _vendor = reader.read_exact_allocated(8)?;
-                                let _product = reader.read_exact_allocated(16)?;
+                                let vendor = reader.read_exact_allocated(8)?;
+                                let vendor = scsi_ascii_to_string(&vendor);
+                                let model = reader.read_exact_allocated(16)?;
+                                let model = scsi_ascii_to_string(&model);
                                 let serial = reader.read_exact_allocated(10)?;
                                 let serial = scsi_ascii_to_string(&serial);
-                                Some(serial)
+                                (Some(serial), Some(vendor), Some(model))
                             }
-                            _ => None,
+                            _ => (None, None, None),
                         };
 
                         result.last_element_address = Some(desc.element_address);
@@ -666,6 +668,8 @@ fn decode_element_status_page(
                             loaded_slot,
                             status: create_element_status(full, volume_tag),
                             drive_serial_number,
+                            vendor,
+                            model,
                             element_address: desc.element_address,
                         };
                         result.drives.push(drive);
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index 7dea49de..7fbc1167 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -399,6 +399,8 @@ impl MediaChange for VirtualTapeHandle {
                 loaded_slot: None,
                 status: ElementStatus::VolumeTag(current_tape.name.clone()),
                 drive_serial_number: None,
+                vendor: None,
+                model: None,
                 element_address: 0,
            });
         }
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 14/15] tape/changer: refactor marking of import/export slots from config
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (12 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 13/15] tape/changer: add vendor/model to DriveStatus Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 15/15] tape: change changer-drive-id to changer-drivenum Dominik Csapak
  2021-01-28 14:13 ` [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dietmar Maurer
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

we did this for 'mtx', but missed it for the sg_pt_changer code
refactor it into the MtxStatus strut, and call it from both
code paths

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/tape/changer/mod.rs             | 32 ++++++++++++++++++++--
 src/tape/changer/mtx/mtx_wrapper.rs | 41 ++++-------------------------
 src/tape/changer/sg_pt_changer.rs   | 16 +++++++++++
 3 files changed, 51 insertions(+), 38 deletions(-)

diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs
index 0d378587..f9f9da98 100644
--- a/src/tape/changer/mod.rs
+++ b/src/tape/changer/mod.rs
@@ -10,10 +10,16 @@ pub mod mtx;
 mod online_status_map;
 pub use online_status_map::*;
 
+use std::collections::HashSet;
+
 use anyhow::{bail, Error};
 use serde::{Serialize, Deserialize};
+use serde_json::Value;
+
+use proxmox::api::schema::parse_property_string;
 
 use crate::api2::types::{
+    SLOT_ARRAY_SCHEMA,
     ScsiTapeChanger,
     LinuxTapeDrive,
 };
@@ -124,6 +130,29 @@ impl MtxStatus {
         }
         free_slot
     }
+
+    pub fn mark_import_export_slots(&mut self, config: &ScsiTapeChanger) -> Result<(), Error>{
+        let mut export_slots: HashSet<u64> = HashSet::new();
+
+        if let Some(slots) = &config.export_slots {
+            let slots: Value = parse_property_string(&slots, &SLOT_ARRAY_SCHEMA)?;
+            export_slots = slots
+                .as_array()
+                .unwrap()
+                .iter()
+                .filter_map(|v| v.as_u64())
+                .collect();
+        }
+
+        for (i, entry) in self.slots.iter_mut().enumerate() {
+            let slot = i as u64 + 1;
+            if export_slots.contains(&slot) {
+                entry.import_export = true; // mark as IMPORT/EXPORT
+            }
+        }
+
+        Ok(())
+    }
 }
 
 /// Interface to SCSI changer devices
@@ -373,8 +402,7 @@ impl ScsiMediaChange for ScsiTapeChanger {
         if USE_MTX {
             mtx::mtx_status(&self)
         } else {
-            let mut file = sg_pt_changer::open(&self.path)?;
-            sg_pt_changer::read_element_status(&mut file)
+            sg_pt_changer::status(&self)
         }
     }
 
diff --git a/src/tape/changer/mtx/mtx_wrapper.rs b/src/tape/changer/mtx/mtx_wrapper.rs
index 116382f1..44d03251 100644
--- a/src/tape/changer/mtx/mtx_wrapper.rs
+++ b/src/tape/changer/mtx/mtx_wrapper.rs
@@ -1,45 +1,19 @@
-use std::collections::HashSet;
-
 use anyhow::Error;
 use serde_json::Value;
 
-use proxmox::{
-    api::schema::parse_property_string,
-};
-
 use crate::{
     tools::run_command,
-    api2::types::{
-        SLOT_ARRAY_SCHEMA,
-        ScsiTapeChanger,
-    },
-    tape::{
-        changer::{
-            MtxStatus,
-            mtx::{
-                parse_mtx_status,
-            },
-        },
+    api2::types::ScsiTapeChanger,
+    tape::changer::{
+        MtxStatus,
+        mtx::parse_mtx_status,
     },
 };
 
 /// Run 'mtx status' and return parsed result.
 pub fn mtx_status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
-
     let path = &config.path;
 
-    let mut export_slots: HashSet<u64> = HashSet::new();
-
-    if let Some(slots) = &config.export_slots {
-        let slots: Value = parse_property_string(&slots, &SLOT_ARRAY_SCHEMA)?;
-        export_slots = slots
-            .as_array()
-            .unwrap()
-            .iter()
-            .filter_map(|v| v.as_u64())
-            .collect();
-    }
-
     let mut command = std::process::Command::new("mtx");
     command.args(&["-f", path, "status"]);
 
@@ -47,12 +21,7 @@ pub fn mtx_status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
 
     let mut status = parse_mtx_status(&output)?;
 
-    for (i, entry) in status.slots.iter_mut().enumerate() {
-        let slot = i as u64 + 1;
-        if export_slots.contains(&slot) {
-            entry.import_export = true; // mark as IMPORT/EXPORT
-        }
-    }
+    status.mark_import_export_slots(&config)?;
 
     Ok(status)
 }
diff --git a/src/tape/changer/sg_pt_changer.rs b/src/tape/changer/sg_pt_changer.rs
index b6c64381..b2be9548 100644
--- a/src/tape/changer/sg_pt_changer.rs
+++ b/src/tape/changer/sg_pt_changer.rs
@@ -31,6 +31,7 @@ use crate::{
         scsi_ascii_to_string,
         scsi_inquiry,
     },
+    api2::types::ScsiTapeChanger,
 };
 
 const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60*5; // 5 minutes
@@ -397,6 +398,21 @@ pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error>
     Ok(status)
 }
 
+/// Read status and map import-export slots from config
+pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
+    let path = &config.path;
+
+    let mut file = open(path)
+        .map_err(|err| format_err!("error opening '{}': {}", path, err))?;
+    let mut status = read_element_status(&mut file)
+        .map_err(|err| format_err!("error reading element status: {}", err))?;
+
+    status.mark_import_export_slots(&config)?;
+
+    Ok(status)
+}
+
+
 #[repr(C, packed)]
 #[derive(Endian)]
 struct ElementStatusHeader {
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v2 15/15] tape: change changer-drive-id to changer-drivenum
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (13 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 14/15] tape/changer: refactor marking of import/export slots from config Dominik Csapak
@ 2021-01-28 11:59 ` Dominik Csapak
  2021-01-28 14:13 ` [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dietmar Maurer
  15 siblings, 0 replies; 17+ messages in thread
From: Dominik Csapak @ 2021-01-28 11:59 UTC (permalink / raw)
  To: pbs-devel

because it changed in the config

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 docs/tape-backup.rst          | 8 ++++----
 src/api2/config/drive.rs      | 2 +-
 src/bin/proxmox_tape/drive.rs | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/docs/tape-backup.rst b/docs/tape-backup.rst
index 3c0bd2b6..c54eeed6 100644
--- a/docs/tape-backup.rst
+++ b/docs/tape-backup.rst
@@ -299,11 +299,11 @@ configuration entry::
 If you have a tape library, you also need to set the associated
 changer device::
 
- # proxmox-tape drive update mydrive --changer sl3  --changer-drive-id 0
+ # proxmox-tape drive update mydrive --changer sl3  --changer-drivenum 0
 
-The ``--changer-drive-id`` is only necessary if the tape library
+The ``--changer-drivenum`` is only necessary if the tape library
 includes more than one drive (The changer status command lists all
-drive IDs).
+drivenums).
 
 You can show the final configuration with::
 
@@ -318,7 +318,7 @@ You can show the final configuration with::
  │ changer │ sl3                            │
  └─────────┴────────────────────────────────┘
 
-.. NOTE:: The ``changer-drive-id`` value 0 is not stored in the
+.. NOTE:: The ``changer-drivenum`` value 0 is not stored in the
    configuration, because that is the default.
 
 To list all configured drives use::
diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs
index 7d7803d3..8255d445 100644
--- a/src/api2/config/drive.rs
+++ b/src/api2/config/drive.rs
@@ -224,7 +224,7 @@ pub fn update_drive(
             data.changer_drivenum = None;
         } else {
             if data.changer.is_none() {
-                bail!("Option 'changer-drive-id' requires option 'changer'.");
+                bail!("Option 'changer-drivenum' requires option 'changer'.");
             }
             data.changer_drivenum = Some(changer_drivenum);
         }
diff --git a/src/bin/proxmox_tape/drive.rs b/src/bin/proxmox_tape/drive.rs
index e99ee860..da63f3a7 100644
--- a/src/bin/proxmox_tape/drive.rs
+++ b/src/bin/proxmox_tape/drive.rs
@@ -165,7 +165,7 @@ fn get_config(
         .column(ColumnConfig::new("name"))
         .column(ColumnConfig::new("path"))
         .column(ColumnConfig::new("changer"))
-        .column(ColumnConfig::new("changer-drive-id"))
+        .column(ColumnConfig::new("changer-drivenum"))
         ;
 
     format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
-- 
2.20.1





^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui
  2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
                   ` (14 preceding siblings ...)
  2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 15/15] tape: change changer-drive-id to changer-drivenum Dominik Csapak
@ 2021-01-28 14:13 ` Dietmar Maurer
  15 siblings, 0 replies; 17+ messages in thread
From: Dietmar Maurer @ 2021-01-28 14:13 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Dominik Csapak

applied




^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2021-01-28 14:14 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-28 11:59 [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 01/15] api2/tape/changer: add changer filter to list_drives api call Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 02/15] api2/tape/drive: add load_media as " Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 03/15] api2/tape/drive: change methods of some api calls from put to get Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 04/15] api2/config/{drive, changer}: prevent adding same device multiple times Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 05/15] ui: tape: add form fields Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 06/15] ui: tape: add Edit Windows Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 07/15] ui: tape: add BackupOverview Panel Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 08/15] ui: tape: add ChangerStatus panel Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 09/15] ui: tape: add DriveConfig panel Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 10/15] ui: tape: add PoolConfig Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 11/15] ui: tape: move TapeManagement.js to tape dir Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 12/15] ui: tape: use panels in tape interface Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 13/15] tape/changer: add vendor/model to DriveStatus Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 14/15] tape/changer: refactor marking of import/export slots from config Dominik Csapak
2021-01-28 11:59 ` [pbs-devel] [PATCH proxmox-backup v2 15/15] tape: change changer-drive-id to changer-drivenum Dominik Csapak
2021-01-28 14:13 ` [pbs-devel] [PATCH proxmox-backup v2 00/15] implement first version of tape gui Dietmar Maurer

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