* [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