public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui
@ 2021-01-27 10:33 Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 01/15] api2/types/tape/drive: add changer_drivenum Dominik Csapak
                   ` (14 more replies)
  0 siblings, 15 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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

Dominik Csapak (15):
  api2/types/tape/drive: add changer_drivenum
  api2/tape/changer: add get_drives api call
  api2/tape/drive: reorganize drive api
  api2/tape: add missing protected to some api calls
  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

 src/api2/config/changer.rs              |  13 +-
 src/api2/config/drive.rs                |  38 +-
 src/api2/config/media_pool.rs           |   3 +
 src/api2/tape/backup.rs                 |   1 +
 src/api2/tape/changer.rs                |  72 ++-
 src/api2/tape/drive.rs                  |  82 ++-
 src/api2/tape/media.rs                  |   2 +
 src/api2/tape/mod.rs                    |   5 +
 src/api2/types/tape/drive.rs            |   4 +
 src/bin/proxmox_tape/drive.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          |  69 +++
 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             |  69 +++
 www/tape/window/TapeBackup.js           |  43 ++
 28 files changed, 2026 insertions(+), 54 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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 01/15] api2/types/tape/drive: add changer_drivenum
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 02/15] api2/tape/changer: add get_drives api call Dominik Csapak
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 UTC (permalink / raw)
  To: pbs-devel

so that an api user can see which drive belongs to which drivenum of a changer
for ones with multiple drives

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/config/changer.rs   | 1 +
 src/api2/config/drive.rs     | 1 +
 src/api2/tape/changer.rs     | 1 +
 src/api2/types/tape/drive.rs | 4 ++++
 4 files changed, 7 insertions(+)

diff --git a/src/api2/config/changer.rs b/src/api2/config/changer.rs
index df8e042d..1b429659 100644
--- a/src/api2/config/changer.rs
+++ b/src/api2/config/changer.rs
@@ -134,6 +134,7 @@ pub fn list_changers(
             name: changer.name,
             path: changer.path.clone(),
             changer: None,
+            changer_drivenum: None,
             vendor: None,
             model: None,
             serial: None,
diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs
index 7a05238b..32d74c19 100644
--- a/src/api2/config/drive.rs
+++ b/src/api2/config/drive.rs
@@ -127,6 +127,7 @@ pub fn list_drives(
             name: drive.name,
             path: drive.path.clone(),
             changer: drive.changer,
+            changer_drivenum: drive.changer_drive_id,
             vendor: None,
             model: None,
             serial: None,
diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs
index 8239e041..87737d32 100644
--- a/src/api2/tape/changer.rs
+++ b/src/api2/tape/changer.rs
@@ -166,6 +166,7 @@ pub fn list_changers(
             name: changer.name,
             path: changer.path.clone(),
             changer: None,
+            changer_drivenum: None,
             vendor: None,
             model: None,
             serial: None,
diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs
index 6e5e3b15..537cf55d 100644
--- a/src/api2/types/tape/drive.rs
+++ b/src/api2/types/tape/drive.rs
@@ -82,6 +82,7 @@ pub struct LinuxTapeDrive {
 
 #[api()]
 #[derive(Serialize,Deserialize)]
+#[serde(rename_all = "kebab-case")]
 /// Drive list entry
 pub struct DriveListEntry {
     /// Drive name
@@ -91,6 +92,9 @@ pub struct DriveListEntry {
     /// Associated changer device
     #[serde(skip_serializing_if="Option::is_none")]
     pub changer: Option<String>,
+    /// Drive number in associated changer device
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub changer_drivenum: Option<u64>,
     /// Vendor (autodetected)
     #[serde(skip_serializing_if="Option::is_none")]
     pub vendor: Option<String>,
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup 02/15] api2/tape/changer: add get_drives api call
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 01/15] api2/types/tape/drive: add changer_drivenum Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 03/15] api2/tape/drive: reorganize drive api Dominik Csapak
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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 | 69 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 67 insertions(+), 2 deletions(-)

diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs
index 87737d32..232f0127 100644
--- a/src/api2/tape/changer.rs
+++ b/src/api2/tape/changer.rs
@@ -11,9 +11,10 @@ use crate::{
     api2::types::{
         CHANGER_NAME_SCHEMA,
         DriveListEntry,
-        ScsiTapeChanger,
-        MtxStatusEntry,
+        LinuxTapeDrive,
         MtxEntryKind,
+        MtxStatusEntry,
+        ScsiTapeChanger,
     },
     tape::{
         TAPE_STATUS_DIR,
@@ -25,6 +26,7 @@ use crate::{
             ScsiMediaChange,
             mtx_status_to_online_set,
         },
+        linux_tape_device_list,
         lookup_drive,
     },
 };
@@ -136,6 +138,64 @@ pub async fn transfer(
     }).await?
 }
 
+#[api(
+    input: {
+        properties: {
+            name: {
+                schema: CHANGER_NAME_SCHEMA,
+            },
+        },
+    },
+    returns: {
+        description: "The list of configured Drives for the given Changer.",
+        type: Array,
+        items: {
+            type: DriveListEntry,
+        },
+    },
+)]
+/// Get Drives belonging to changer
+pub fn get_drives(
+    name: String,
+) -> Result<Vec<DriveListEntry>, Error> {
+
+    let (config, _) = config::drive::config()?;
+
+    let linux_drives = linux_tape_device_list();
+
+    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+
+    let mut list = Vec::new();
+
+    let changer = Some(name);
+
+    for drive in drive_list {
+
+        if drive.changer != changer {
+            continue;
+        }
+
+        let mut entry = DriveListEntry {
+            name: drive.name,
+            path: drive.path.clone(),
+            changer: drive.changer,
+            changer_drivenum: drive.changer_drive_id,
+            vendor: None,
+            model: None,
+            serial: None,
+        };
+        if let Some(info) = lookup_drive(&linux_drives, &drive.path) {
+            entry.vendor = Some(info.vendor.clone());
+            entry.model = Some(info.model.clone());
+            entry.serial = Some(info.serial.clone());
+        }
+
+        list.push(entry);
+    }
+
+    Ok(list)
+}
+
 #[api(
     input: {
         properties: {},
@@ -183,6 +243,11 @@ pub fn list_changers(
 }
 
 const SUBDIRS: SubdirMap = &[
+    (
+        "get-drives",
+        &Router::new()
+            .get(&API_METHOD_GET_DRIVES)
+    ),
     (
         "status",
         &Router::new()
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup 03/15] api2/tape/drive: reorganize drive api
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 01/15] api2/types/tape/drive: add changer_drivenum Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 02/15] api2/tape/changer: add get_drives api call Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls Dominik Csapak
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 UTC (permalink / raw)
  To: pbs-devel

similar to the changers, create a listing at /tape/drive and put
the specific api calls below that

move the scan api call up one level

remove the status info from the config listing

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/config/drive.rs      | 28 ++--------------
 src/api2/tape/drive.rs        | 62 +++++++++++++++++++++++++++++++----
 src/api2/tape/mod.rs          |  5 +++
 src/bin/proxmox_tape/drive.rs |  2 +-
 4 files changed, 63 insertions(+), 34 deletions(-)

diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs
index 32d74c19..5e0a078e 100644
--- a/src/api2/config/drive.rs
+++ b/src/api2/config/drive.rs
@@ -19,7 +19,6 @@ use crate::{
     tape::{
         linux_tape_device_list,
         check_drive_path,
-        lookup_drive,
     },
 };
 
@@ -112,37 +111,14 @@ pub fn get_config(
 pub fn list_drives(
     _param: Value,
     mut rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Vec<DriveListEntry>, Error> {
+) -> Result<Vec<LinuxTapeDrive>, Error> {
 
     let (config, digest) = config::drive::config()?;
 
-    let linux_drives = linux_tape_device_list();
-
     let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
 
-    let mut list = Vec::new();
-
-    for drive in drive_list {
-        let mut entry = DriveListEntry {
-            name: drive.name,
-            path: drive.path.clone(),
-            changer: drive.changer,
-            changer_drivenum: drive.changer_drive_id,
-            vendor: None,
-            model: None,
-            serial: None,
-        };
-        if let Some(info) = lookup_drive(&linux_drives, &drive.path) {
-            entry.vendor = Some(info.vendor.clone());
-            entry.model = Some(info.model.clone());
-            entry.serial = Some(info.serial.clone());
-        }
-
-        list.push(entry);
-    }
-
     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
-    Ok(list)
+    Ok(drive_list)
 }
 
 #[api()]
diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index 97c28344..a88ef827 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -31,6 +31,7 @@ use crate::{
             MEDIA_LABEL_SCHEMA,
             MEDIA_POOL_NAME_SCHEMA,
             Authid,
+            DriveListEntry,
             LinuxTapeDrive,
             TapeDeviceInfo,
             MediaIdFlat,
@@ -48,6 +49,7 @@ use crate::{
         MediaCatalog,
         MediaId,
         linux_tape_device_list,
+        lookup_drive,
         file_formats::{
             MediaLabel,
             MediaSetLabel,
@@ -1096,6 +1098,53 @@ pub fn catalog_media(
     Ok(upid_str.into())
 }
 
+#[api(
+    input: {
+        properties: {},
+    },
+    returns: {
+        description: "The list of configured drives with model information.",
+        type: Array,
+        items: {
+            type: DriveListEntry,
+        },
+    },
+)]
+/// List drives
+pub fn list_drives(
+    _param: Value,
+) -> Result<Vec<DriveListEntry>, Error> {
+
+    let (config, _) = config::drive::config()?;
+
+    let linux_drives = linux_tape_device_list();
+
+    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+
+    let mut list = Vec::new();
+
+    for drive in drive_list {
+        let mut entry = DriveListEntry {
+            name: drive.name,
+            path: drive.path.clone(),
+            changer: drive.changer,
+            changer_drivenum: drive.changer_drive_id,
+            vendor: None,
+            model: None,
+            serial: None,
+        };
+        if let Some(info) = lookup_drive(&linux_drives, &drive.path) {
+            entry.vendor = Some(info.vendor.clone());
+            entry.model = Some(info.model.clone());
+            entry.serial = Some(info.serial.clone());
+        }
+
+        list.push(entry);
+    }
+
+    Ok(list)
+}
+
 #[sortable]
 pub const SUBDIRS: SubdirMap = &sorted!([
     (
@@ -1159,11 +1208,6 @@ pub const SUBDIRS: SubdirMap = &sorted!([
         &Router::new()
             .put(&API_METHOD_REWIND)
     ),
-    (
-        "scan",
-        &Router::new()
-            .get(&API_METHOD_SCAN_DRIVES)
-    ),
     (
         "status",
         &Router::new()
@@ -1176,6 +1220,10 @@ pub const SUBDIRS: SubdirMap = &sorted!([
     ),
 ]);
 
-pub const ROUTER: Router = Router::new()
+const ITEM_ROUTER: Router = Router::new()
     .get(&list_subdirs_api_method!(SUBDIRS))
-    .subdirs(SUBDIRS);
+    .subdirs(&SUBDIRS);
+
+pub const ROUTER: Router = Router::new()
+    .get(&API_METHOD_LIST_DRIVES)
+    .match_all("drive", &ITEM_ROUTER);
diff --git a/src/api2/tape/mod.rs b/src/api2/tape/mod.rs
index 05cdb693..466f3b8b 100644
--- a/src/api2/tape/mod.rs
+++ b/src/api2/tape/mod.rs
@@ -54,6 +54,11 @@ const SUBDIRS: SubdirMap = &[
         &Router::new()
             .get(&API_METHOD_SCAN_CHANGERS),
     ),
+    (
+        "scan-drives",
+        &Router::new()
+            .get(&drive::API_METHOD_SCAN_DRIVES),
+    ),
 ];
 
 pub const ROUTER: Router = Router::new()
diff --git a/src/bin/proxmox_tape/drive.rs b/src/bin/proxmox_tape/drive.rs
index da61ddcf..e99ee860 100644
--- a/src/bin/proxmox_tape/drive.rs
+++ b/src/bin/proxmox_tape/drive.rs
@@ -79,7 +79,7 @@ fn list_drives(
 ) -> Result<(), Error> {
 
     let output_format = get_output_format(&param);
-    let info = &api2::config::drive::API_METHOD_LIST_DRIVES;
+    let info = &api2::tape::drive::API_METHOD_LIST_DRIVES;
     let mut data = match info.handler {
         ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
         _ => unreachable!(),
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (2 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 03/15] api2/tape/drive: reorganize drive api Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 17:47   ` Dietmar Maurer
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 05/15] api2/tape/drive: add load_media as api call Dominik Csapak
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 UTC (permalink / raw)
  To: pbs-devel

they need root permission either to access the changer/drive or to
modify the config

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/config/media_pool.rs |  3 +++
 src/api2/tape/backup.rs       |  1 +
 src/api2/tape/changer.rs      |  2 ++
 src/api2/tape/drive.rs        | 11 ++++++++++-
 src/api2/tape/media.rs        |  2 ++
 5 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/api2/config/media_pool.rs b/src/api2/config/media_pool.rs
index d155f268..33e4993e 100644
--- a/src/api2/config/media_pool.rs
+++ b/src/api2/config/media_pool.rs
@@ -28,6 +28,7 @@ use crate::{
 };
 
 #[api(
+    protected: true,
     input: {
         properties: {
             name: {
@@ -153,6 +154,7 @@ pub enum DeletableProperty {
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             name: {
@@ -231,6 +233,7 @@ pub fn update_pool(
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             name: {
diff --git a/src/api2/tape/backup.rs b/src/api2/tape/backup.rs
index 6aa12d56..ea05e6a3 100644
--- a/src/api2/tape/backup.rs
+++ b/src/api2/tape/backup.rs
@@ -43,6 +43,7 @@ use crate::{
 };
 
 #[api(
+    protected: true,
    input: {
         properties: {
             store: {
diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs
index 232f0127..f79a7ee0 100644
--- a/src/api2/tape/changer.rs
+++ b/src/api2/tape/changer.rs
@@ -33,6 +33,7 @@ use crate::{
 
 
 #[api(
+    protected: true,
     input: {
         properties: {
             name: {
@@ -106,6 +107,7 @@ pub async fn get_status(name: String) -> Result<Vec<MtxStatusEntry>, Error> {
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             name: {
diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index a88ef827..936bc528 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -68,6 +68,7 @@ use crate::{
 };
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -150,6 +151,7 @@ pub async fn export_media(drive: String, label_text: String) -> Result<u64, Erro
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -313,6 +315,7 @@ pub async fn eject_media(drive: String) -> Result<(), Error> {
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -497,7 +500,8 @@ pub async fn restore_key(
     }).await?
 }
 
- #[api(
+#[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -575,6 +579,7 @@ pub async fn read_label(
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -793,6 +798,7 @@ pub fn update_inventory(
 
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -911,6 +917,7 @@ fn barcode_label_media_worker(
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -938,6 +945,7 @@ pub fn cartridge_memory(drive: String) -> Result<Vec<MamAttribute>, Error> {
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
@@ -961,6 +969,7 @@ pub fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Error> {
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             drive: {
diff --git a/src/api2/tape/media.rs b/src/api2/tape/media.rs
index 70af454c..d520288f 100644
--- a/src/api2/tape/media.rs
+++ b/src/api2/tape/media.rs
@@ -38,6 +38,7 @@ use crate::{
 };
 
 #[api(
+    protected: true,
     input: {
         properties: {
             pool: {
@@ -244,6 +245,7 @@ pub struct MediaContentListFilter {
 }
 
 #[api(
+    protected: true,
     input: {
         properties: {
             "filter": {
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup 05/15] api2/tape/drive: add load_media as api call
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (3 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 06/15] api2/tape/drive: change methods of some api calls from put to get Dominik Csapak
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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 936bc528..492d66a6 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -1192,6 +1192,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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 06/15] api2/tape/drive: change methods of some api calls from put to get
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (4 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 05/15] api2/tape/drive: add load_media as api call Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 07/15] api2/config/{drive, changer}: prevent adding same device multiple times Dominik Csapak
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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 492d66a6..009ed245 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -1205,12 +1205,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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 07/15] api2/config/{drive, changer}: prevent adding same device multiple times
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (5 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 06/15] api2/tape/drive: change methods of some api calls from put to get Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 08/15] ui: tape: add form fields Dominik Csapak
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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 5e0a078e..d3e4f41f 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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 08/15] ui: tape: add form fields
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (6 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 07/15] api2/config/{drive, changer}: prevent adding same device multiple times Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 09/15] ui: tape: add Edit Windows Dominik Csapak
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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          | 69 +++++++++++++++++++++++++
 www/tape/form/PoolSelector.js           | 44 ++++++++++++++++
 www/tape/form/RetentionSelector.js      | 26 ++++++++++
 www/tape/form/TapeDevicePathSelector.js | 62 ++++++++++++++++++++++
 7 files changed, 298 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..b6fcb037
--- /dev/null
+++ b/www/tape/form/DriveSelector.js
@@ -0,0 +1,69 @@
+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.filters = [
+		{
+		    property: 'changer',
+		    value: me.changer,
+		},
+	    ];
+	} else {
+	    me.store.filters = [];
+	}
+
+	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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 09/15] ui: tape: add Edit Windows
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (7 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 08/15] ui: tape: add form fields Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 10/15] ui: tape: add BackupOverview Panel Dominik Csapak
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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    | 69 ++++++++++++++++++++++++++++++
 www/tape/window/TapeBackup.js  | 43 +++++++++++++++++++
 6 files changed, 291 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..f81768a7
--- /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-drive-id]')
+			.setDisabled(disableSlotField);
+		},
+	    },
+	},
+	{
+	    fieldLabel: gettext('Changer Slot'),
+	    xtype: 'proxmoxintegerfield',
+	    name: 'changer-drive-id',
+	    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..e30f477d
--- /dev/null
+++ b/www/tape/window/PoolEdit.js
@@ -0,0 +1,69 @@
+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',
+	    skipEmptyText: true,
+	    allowBlank: true,
+	    autoSelect: false,
+	    emptyText: gettext('No Drive'),
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
+	{
+	    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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 10/15] ui: tape: add BackupOverview Panel
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (8 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 09/15] ui: tape: add Edit Windows Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 11/15] ui: tape: add ChangerStatus panel Dominik Csapak
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 11/15] ui: tape: add ChangerStatus panel
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (9 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 10/15] ui: tape: add BackupOverview Panel Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 12/15] ui: tape: add DriveConfig panel Dominik Csapak
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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..747167d3
--- /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/changer/${encodeURIComponent(changer)}/get-drives`,
+		});
+
+		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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 12/15] ui: tape: add DriveConfig panel
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (10 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 11/15] ui: tape: add ChangerStatus panel Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 13/15] ui: tape: add PoolConfig Dominik Csapak
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 13/15] ui: tape: add PoolConfig
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (11 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 12/15] ui: tape: add DriveConfig panel Dominik Csapak
@ 2021-01-27 10:33 ` Dominik Csapak
  2021-01-27 10:34 ` [pbs-devel] [PATCH proxmox-backup 14/15] ui: tape: move TapeManagement.js to tape dir Dominik Csapak
  2021-01-27 10:34 ` [pbs-devel] [PATCH proxmox-backup 15/15] ui: tape: use panels in tape interface Dominik Csapak
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:33 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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 14/15] ui: tape: move TapeManagement.js to tape dir
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (12 preceding siblings ...)
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 13/15] ui: tape: add PoolConfig Dominik Csapak
@ 2021-01-27 10:34 ` Dominik Csapak
  2021-01-27 10:34 ` [pbs-devel] [PATCH proxmox-backup 15/15] ui: tape: use panels in tape interface Dominik Csapak
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:34 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] 18+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 15/15] ui: tape: use panels in tape interface
  2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
                   ` (13 preceding siblings ...)
  2021-01-27 10:34 ` [pbs-devel] [PATCH proxmox-backup 14/15] ui: tape: move TapeManagement.js to tape dir Dominik Csapak
@ 2021-01-27 10:34 ` Dominik Csapak
  14 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-27 10:34 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] 18+ messages in thread

* Re: [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls
  2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls Dominik Csapak
@ 2021-01-27 17:47   ` Dietmar Maurer
  2021-01-28  8:05     ` Dominik Csapak
  0 siblings, 1 reply; 18+ messages in thread
From: Dietmar Maurer @ 2021-01-27 17:47 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Dominik Csapak


> On 01/27/2021 11:33 AM Dominik Csapak <d.csapak@proxmox.com> wrote:
> 
>  
> they need root permission either to access the changer/drive or to
> modify the config

This looks wrong to me. Most thing can/need to be done as user 'backup', especially the 'backup' API. Running backup as root user is wrong.




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

* Re: [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls
  2021-01-27 17:47   ` Dietmar Maurer
@ 2021-01-28  8:05     ` Dominik Csapak
  0 siblings, 0 replies; 18+ messages in thread
From: Dominik Csapak @ 2021-01-28  8:05 UTC (permalink / raw)
  To: Dietmar Maurer, Proxmox Backup Server development discussion

On 1/27/21 6:47 PM, Dietmar Maurer wrote:
> 
>> On 01/27/2021 11:33 AM Dominik Csapak <d.csapak@proxmox.com> wrote:
>>
>>   
>> they need root permission either to access the changer/drive or to
>> modify the config
> 
> This looks wrong to me. Most thing can/need to be done as user 'backup', especially the 'backup' API. Running backup as root user is wrong.
> 

ok for some things you're right, i had weird permissions on

/var/lib/proxmox-backup/tape

(for some reason it had no execute bit set..)

but if i omit the protected option, i can barely do anything
media/drive/changer listing works, but a backup
fails with:


2021-01-28T09:03:17+01:00: update media online status
2021-01-28T09:03:17+01:00: TASK ERROR: Permission denied (os error 13)

i did not find out yet where the problem is, but i'm on it




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

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

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-27 10:33 [pbs-devel] [PATCH proxmox-backup 00/15] implement first version of tape gui Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 01/15] api2/types/tape/drive: add changer_drivenum Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 02/15] api2/tape/changer: add get_drives api call Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 03/15] api2/tape/drive: reorganize drive api Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 04/15] api2/tape: add missing protected to some api calls Dominik Csapak
2021-01-27 17:47   ` Dietmar Maurer
2021-01-28  8:05     ` Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 05/15] api2/tape/drive: add load_media as api call Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 06/15] api2/tape/drive: change methods of some api calls from put to get Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 07/15] api2/config/{drive, changer}: prevent adding same device multiple times Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 08/15] ui: tape: add form fields Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 09/15] ui: tape: add Edit Windows Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 10/15] ui: tape: add BackupOverview Panel Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 11/15] ui: tape: add ChangerStatus panel Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 12/15] ui: tape: add DriveConfig panel Dominik Csapak
2021-01-27 10:33 ` [pbs-devel] [PATCH proxmox-backup 13/15] ui: tape: add PoolConfig Dominik Csapak
2021-01-27 10:34 ` [pbs-devel] [PATCH proxmox-backup 14/15] ui: tape: move TapeManagement.js to tape dir Dominik Csapak
2021-01-27 10:34 ` [pbs-devel] [PATCH proxmox-backup 15/15] ui: tape: use panels in tape interface Dominik Csapak

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