* [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(¶m);
- 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
* 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
* [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