* [pbs-devel] [PATCH proxmox-backup v2 1/4] api-types: add an UploadStatistic api type
2023-12-05 10:53 [pbs-devel] [PATCH proxmox-backup v2 0/4] add 'show information' for snapshots Dominik Csapak
@ 2023-12-05 10:53 ` Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api: datastore admin: add 'snapshot-information' api call Dominik Csapak
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2023-12-05 10:53 UTC (permalink / raw)
To: pbs-devel
We'll want this to expose on the api, but the internal type used in the
backup environment does not use kebab case, so simply add a new type
I opted for a new type instead of modifying the existing one, so that we
keep the casing for the options in the manifest the same.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
pbs-api-types/src/datastore.rs | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs
index d4ead1d1..52f83f48 100644
--- a/pbs-api-types/src/datastore.rs
+++ b/pbs-api-types/src/datastore.rs
@@ -421,6 +421,22 @@ pub struct SnapshotVerifyState {
pub state: VerifyState,
}
+#[api()]
+#[derive(Copy, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Chunk upload statistics of a snapshot
+pub struct UploadStatistic {
+ /// Amount of chunks uploaded (incl. duplicates)
+ pub count: u64,
+ /// Uncompressed bytes uploaded
+ pub size: u64,
+ /// Compressed bytes uploaded
+ #[serde(alias = "compressed_size")]
+ pub compressed_size: u64,
+ /// Amount of duplicate chunks uploaded
+ pub duplicates: u64,
+}
+
/// A namespace provides a logical separation between backup groups from different domains
/// (cluster, sites, ...) where uniqueness cannot be guaranteed anymore. It allows users to share a
/// datastore (i.e., one deduplication domain (chunk store)) with multiple (trusted) sites and
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 2/4] api: datastore admin: add 'snapshot-information' api call
2023-12-05 10:53 [pbs-devel] [PATCH proxmox-backup v2 0/4] add 'show information' for snapshots Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] api-types: add an UploadStatistic api type Dominik Csapak
@ 2023-12-05 10:53 ` Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] ui: datastore content: add snapshot information to context menu Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: datastore content: add 'more actions' menu to actions Dominik Csapak
3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2023-12-05 10:53 UTC (permalink / raw)
To: pbs-devel
get information about a snapshot. This is intended to contain all
relevant information about a snapshot, so that we don't fill the
'list_snapshots' api call with further info
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
pbs-api-types/src/datastore.rs | 24 +++++++
src/api2/admin/datastore.rs | 117 +++++++++++++++++++++++++++++++--
2 files changed, 135 insertions(+), 6 deletions(-)
diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs
index 52f83f48..76d1811f 100644
--- a/pbs-api-types/src/datastore.rs
+++ b/pbs-api-types/src/datastore.rs
@@ -421,6 +421,30 @@ pub struct SnapshotVerifyState {
pub state: VerifyState,
}
+#[api(
+ properties: {
+ info : {
+ type: SnapshotListItem,
+ },
+ "upload-statistics": {
+ type: UploadStatistic,
+ optional: true,
+ },
+ },
+)]
+#[derive(Serialize, Deserialize, Clone, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Information about a Snapshot
+pub struct SnapshotInformation {
+ #[serde(flatten)]
+ pub info: SnapshotListItem,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub upload_statistics: Option<UploadStatistic>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ /// "notes" for the whole backup group
+ pub group_comment: Option<String>,
+}
+
#[api()]
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index a95031e7..f6fcb8ff 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -34,12 +34,12 @@ use pxar::EntryKind;
use pbs_api_types::{
print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupNamespace, BackupType,
Counts, CryptMode, DataStoreListItem, DataStoreStatus, GarbageCollectionStatus, GroupListItem,
- KeepOptions, Operation, PruneJobOptions, RRDMode, RRDTimeFrame, SnapshotListItem,
- SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA,
- BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA,
- MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
- PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY,
- UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
+ KeepOptions, Operation, PruneJobOptions, RRDMode, RRDTimeFrame, SnapshotInformation,
+ SnapshotListItem, SnapshotVerifyState, UploadStatistic, BACKUP_ARCHIVE_NAME_SCHEMA,
+ BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA,
+ DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA,
+ PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE,
+ PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
};
use pbs_client::pxar::{create_tar, create_zip};
use pbs_config::CachedUserInfo;
@@ -2239,6 +2239,107 @@ pub async fn set_backup_owner(
.await?
}
+#[api(
+ input: {
+ properties: {
+ store: { schema: DATASTORE_SCHEMA },
+ ns: {
+ type: BackupNamespace,
+ optional: true,
+ },
+ backup_dir: {
+ type: pbs_api_types::BackupDir,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Anybody,
+ description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_AUDIT for any \
+ or DATASTORE_BACKUP and being the owner of the group",
+ },
+)]
+/// Get Information about a snapshot
+pub fn get_snapshot_info(
+ store: String,
+ ns: Option<BackupNamespace>,
+ backup_dir: pbs_api_types::BackupDir,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<SnapshotInformation, Error> {
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let ns = ns.unwrap_or_default();
+
+ let datastore = check_privs_and_load_store(
+ &store,
+ &ns,
+ &auth_id,
+ PRIV_DATASTORE_AUDIT,
+ PRIV_DATASTORE_BACKUP,
+ Some(Operation::Read),
+ &backup_dir.group,
+ )?;
+
+ let group = datastore.backup_group(ns.clone(), backup_dir.group.clone());
+ let owner = group.get_owner()?;
+ let backup = datastore.backup_dir(ns.clone(), backup_dir.clone())?;
+
+ let protected = backup.is_protected();
+ let backup_info = BackupInfo::new(backup)?;
+
+ let (manifest, files) = get_all_snapshot_files(&backup_info)?;
+ let comment: Option<String> = manifest.unprotected["notes"].as_str().map(String::from);
+
+ let fingerprint = match manifest.fingerprint() {
+ Ok(fp) => fp,
+ Err(err) => {
+ eprintln!("error parsing fingerprint: '{}'", err);
+ None
+ }
+ };
+
+ let verification = manifest.unprotected["verify_state"].clone();
+ let verification: Option<SnapshotVerifyState> = match serde_json::from_value(verification) {
+ Ok(verify) => verify,
+ Err(err) => {
+ eprintln!("error parsing verification state : '{}'", err);
+ None
+ }
+ };
+
+ let size = Some(files.iter().map(|x| x.size.unwrap_or(0)).sum());
+
+ let group = backup_dir.group.clone();
+
+ let info = SnapshotListItem {
+ backup: backup_dir,
+ comment,
+ verification,
+ fingerprint,
+ files,
+ size,
+ owner: Some(owner),
+ protected,
+ };
+
+ let upload_statistics =
+ match Option::<UploadStatistic>::deserialize(&manifest.unprotected["chunk_upload_stats"]) {
+ Ok(stats) => stats,
+ Err(err) => {
+ log::warn!("error parsing chunk_upload_stats: {err}");
+ None
+ }
+ };
+
+ let note_path = get_group_note_path(&datastore, &ns, &group);
+ let group_comment = file_read_optional_string(note_path)?;
+
+ Ok(SnapshotInformation {
+ info,
+ upload_statistics,
+ group_comment,
+ })
+}
+
#[sortable]
const DATASTORE_INFO_SUBDIRS: SubdirMap = &[
(
@@ -2304,6 +2405,10 @@ const DATASTORE_INFO_SUBDIRS: SubdirMap = &[
&Router::new().download(&API_METHOD_PXAR_FILE_DOWNLOAD),
),
("rrd", &Router::new().get(&API_METHOD_GET_RRD_STATS)),
+ (
+ "snapshot-information",
+ &Router::new().get(&API_METHOD_GET_SNAPSHOT_INFO),
+ ),
(
"snapshots",
&Router::new()
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 3/4] ui: datastore content: add snapshot information to context menu
2023-12-05 10:53 [pbs-devel] [PATCH proxmox-backup v2 0/4] add 'show information' for snapshots Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] api-types: add an UploadStatistic api type Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api: datastore admin: add 'snapshot-information' api call Dominik Csapak
@ 2023-12-05 10:53 ` Dominik Csapak
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: datastore content: add 'more actions' menu to actions Dominik Csapak
3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2023-12-05 10:53 UTC (permalink / raw)
To: pbs-devel
contains a summary of snapshot information, especially interesting since
it contains the upload statistics. This may allow us to reduce the
default column count in the grid.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/Makefile | 1 +
www/datastore/Content.js | 22 ++++
www/window/SnapshotInfo.js | 259 +++++++++++++++++++++++++++++++++++++
3 files changed, 282 insertions(+)
create mode 100644 www/window/SnapshotInfo.js
diff --git a/www/Makefile b/www/Makefile
index be7e27ab..071ef5d8 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -87,6 +87,7 @@ JSSRC= \
window/ZFSCreate.js \
window/InfluxDbEdit.js \
window/DatastoreRepoInfo.js \
+ window/SnapshotInfo.js \
dashboard/DataStoreStatistics.js \
dashboard/LongestTasks.js \
dashboard/RunningTasks.js \
diff --git a/www/datastore/Content.js b/www/datastore/Content.js
index 87317ec1..f715f814 100644
--- a/www/datastore/Content.js
+++ b/www/datastore/Content.js
@@ -712,6 +712,17 @@ Ext.define('PBS.DataStoreContent', {
});
},
+ onShowInformation: function(view, rI, cI, item, e, rec) {
+ let me = this;
+ Ext.create('PBS.window.SnapshotInfo', {
+ autoShow: true,
+ datastore: view.datastore,
+ 'backup-type': rec.data['backup-type'],
+ 'backup-id': rec.data['backup-id'],
+ 'backup-time': (rec.data["backup-time"].getTime()/1000).toFixed(0),
+ });
+ },
+
onForget: function(table, rI, cI, item, e, { data }) {
let me = this;
let view = this.getView();
@@ -893,6 +904,7 @@ Ext.define('PBS.DataStoreContent', {
title: gettext('Snapshot'),
onVerify: createControllerCallback('onVerify'),
onProtectionChange: createControllerCallback('onProtectionChange'),
+ onShowInformation: createControllerCallback('onShowInformation'),
onForget: createControllerCallback('onForget'),
});
}
@@ -1331,6 +1343,7 @@ Ext.define('PBS.datastore.SnapshotCmdMenu', {
onVerify: undefined,
onProtectionChange: undefined,
+ onShowInformation: undefined,
onForget: undefined,
items: [
@@ -1352,6 +1365,15 @@ Ext.define('PBS.datastore.SnapshotCmdMenu', {
disabled: '{!onProtectionChange}',
},
},
+ {
+ text: gettext('Show Information'),
+ iconCls: 'fa fa-info-circle',
+ handler: function() { this.up('menu').onShowInformation(); },
+ cbind: {
+ hidden: '{!onShowInformation}',
+ disabled: '{!onShowInformation}',
+ },
+ },
{ xtype: 'menuseparator' },
{
text: gettext('Remove'),
diff --git a/www/window/SnapshotInfo.js b/www/window/SnapshotInfo.js
new file mode 100644
index 00000000..f2cc7a55
--- /dev/null
+++ b/www/window/SnapshotInfo.js
@@ -0,0 +1,259 @@
+Ext.define('PBS.window.SnapshotInfo', {
+ extend: 'Ext.window.Window',
+ alias: 'widget.pbsSnapshotInfo',
+
+ modal: true,
+ width: 700,
+ resizable: true,
+ referenceHolder: true,
+
+ bodyPadding: 25,
+ defaults: {
+ xtype: 'pmxInfoWidget',
+ printBar: false,
+ padding: '2 0',
+ },
+
+ items: [
+ {
+ reference: 'backup-time',
+ iconCls: 'fa fa-clock-o',
+ title: gettext('Backup Time'),
+ },
+ {
+ reference: 'protected',
+ iconCls: 'fa fa-shield',
+ title: gettext('Protected'),
+ },
+ {
+ reference: 'size',
+ iconCls: 'fa fa-hdd-o',
+ title: gettext('Size'),
+ },
+ {
+ reference: 'owner',
+ iconCls: 'fa fa-user-o',
+ title: gettext('Owner'),
+ },
+ {
+ // spacer
+ xtype: 'box',
+ padding: 10,
+ },
+ {
+ xtype: 'container',
+ layout: {
+ type: 'hbox',
+ align: 'stretch',
+ },
+ items: [
+ {
+ xtype: 'container',
+ flex: 1,
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+ defaults: {
+ xtype: 'pmxInfoWidget',
+ printBar: false,
+ padding: '2 0',
+ },
+ items: [
+ {
+ xtype: 'box',
+ html: `<i class="fa fa-upload"></i> ${gettext('Upload Statistics')}`,
+ reference: 'upload-title',
+ padding: '0 0 10 0',
+ },
+ {
+ title: gettext('Size'),
+ reference: 'upload-size',
+ },
+ {
+ title: gettext('Compressed Size'),
+ reference: 'compressed-size',
+ },
+ {
+ title: gettext('Chunk Count'),
+ reference: 'chunk-count',
+ },
+ {
+ title: gettext('Duplicate Chunks'),
+ reference: 'duplicate-chunks',
+ },
+ ],
+ },
+ {
+ // spacer
+ xtype: 'box',
+ padding: 10,
+ },
+ {
+ xtype: 'container',
+ flex: 1,
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+ defaults: {
+ xtype: 'pmxInfoWidget',
+ printBar: false,
+ padding: '2 0',
+ },
+ items: [
+ {
+ xtype: 'box',
+ html: `<i class="pve-icon-verify-lettering"></i> ${gettext('Verification')}`,
+ reference: 'verify-title',
+ padding: '0 0 10 0',
+ },
+ {
+ title: gettext('Verify State'),
+ reference: 'verify-state',
+ },
+ {
+ title: gettext('Last Verificaton'),
+ iconCls: 'fa fa-list-alt',
+ reference: 'verify-last',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ // spacer
+ xtype: 'box',
+ padding: 10,
+ },
+ {
+ xtype: 'tabpanel',
+ height: 200,
+ items: [{
+ title: gettext('Files'),
+ xtype: 'grid',
+ reference: 'files',
+ store: {
+ data: [],
+ },
+ scrollable: true,
+ columns: [
+ {
+ dataIndex: 'filename',
+ text: gettext('Filename'),
+ flex: 1,
+ },
+ {
+ dataIndex: 'size',
+ text: gettext('Size'),
+ flex: 1,
+ renderer: (v) => v !== undefined ? Proxmox.Utils.format_size(v) : '',
+ },
+ {
+ dataIndex: 'crypt-mode',
+ text: gettext('Encrypted'),
+ flex: 1,
+ renderer: (v) => {
+ let modeIdx = PBS.Utils.cryptmap.indexOf(v);
+ if (modeIdx === -1) {
+ modeIdx = 0;
+ }
+ let iconCls = PBS.Utils.cryptIconCls[modeIdx];
+ return `<i class="fa fa-${iconCls}"></i> ${PBS.Utils.cryptText[modeIdx]}`;
+ },
+ },
+ ],
+ }, {
+ xtype: 'box',
+ padding: 10,
+ style: {
+ 'white-space': 'pre',
+ },
+ scrollable: true,
+ title: gettext('Comment'),
+ reference: 'comment',
+ }, {
+ xtype: 'box',
+ padding: 10,
+ style: {
+ 'white-space': 'pre',
+ },
+ scrollable: true,
+ title: gettext('Group Comment'),
+ reference: 'group-comment',
+ }],
+ },
+ ],
+
+ initComponent: function() {
+ let me = this;
+ let type = me['backup-type'];
+ let id = me['backup-id'];
+ let time = me['backup-time'];
+ let datetime = new Date(time*1000);
+ if (type === undefined || id === undefined || time === undefined) {
+ throw "snapshot id not given";
+ }
+
+ let snapshotText = `${type}/${id}/${PBS.Utils.render_datetime_utc(datetime)}`;
+ me.title = Ext.String.format(gettext('Snapshot {0}'), snapshotText);
+
+ me.callParent();
+ Proxmox.Utils.API2Request({
+ url: `/api2/extjs/admin/datastore/${me.datastore}/snapshot-information`,
+ params: {
+ 'backup-type': type,
+ 'backup-id': id,
+ 'backup-time': time,
+ },
+ method: 'GET',
+ success: ({ result }) => {
+ me.lookup('backup-time').updateValue(datetime);
+ me.lookup('protected').updateValue(Proxmox.Utils.format_boolean(result.data.protected));
+ me.lookup('size').updateValue(Proxmox.Utils.format_size(result.data.size));
+ me.lookup('owner').updateValue(result.data.owner);
+ me.lookup('comment').setData(result.data.comment);
+ me.lookup('group-comment').setData(result.data['group-notes']);
+
+ me.lookup('files').getStore().setData(result.data.files);
+
+ let verification = result.data.verification;
+ if (verification !== undefined) {
+ let iconCls = 'times critical';
+ if (verification.state === 'ok') {
+ iconCls = 'check good';
+ }
+ let txt = Ext.htmlEncode(verification.state);
+ let verifyState = `<i class="fa fa-${iconCls}"></i> ${txt}`;
+
+ let task = Proxmox.Utils.parse_task_upid(verification.upid);
+ let verifyTime = Proxmox.Utils.render_timestamp(task.starttime);
+ me.lookup('verify-state').updateValue(verifyState);
+ me.lookup('verify-last').updateValue(verifyTime);
+ } else {
+ me.lookup('verify-state').updateValue(Proxmox.Utils.NoneText);
+ me.lookup('verify-last').updateValue(Proxmox.Utils.NoneText);
+ }
+
+ let uploadStats = result.data['upload-statistics'];
+ if (uploadStats !== undefined) {
+ me.lookup('upload-size').updateValue(Proxmox.Utils.format_size(uploadStats.size));
+ me.lookup('compressed-size').updateValue(Proxmox.Utils.format_size(uploadStats['compressed-size']));
+ me.lookup('chunk-count').updateValue(uploadStats.count);
+ me.lookup('duplicate-chunks').updateValue(uploadStats.duplicates);
+ } else {
+ me.lookup('upload-title').setVisible(false);
+ me.lookup('upload-size').setVisible(false);
+ me.lookup('compressed-size').setVisible(false);
+ me.lookup('chunk-count').setVisible(false);
+ me.lookup('duplicate-chunks').setVisible(false);
+ }
+ },
+ failure: function(response, opts) {
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
+ me.close();
+ });
+ },
+ });
+ },
+});
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: datastore content: add 'more actions' menu to actions
2023-12-05 10:53 [pbs-devel] [PATCH proxmox-backup v2 0/4] add 'show information' for snapshots Dominik Csapak
` (2 preceding siblings ...)
2023-12-05 10:53 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] ui: datastore content: add snapshot information to context menu Dominik Csapak
@ 2023-12-05 10:53 ` Dominik Csapak
3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2023-12-05 10:53 UTC (permalink / raw)
To: pbs-devel
and move change owner and change protection there, since these are not
that often used. Also add the 'Show Information' action there for
snapshots, as this was not available via actions.
Refactor the menu creation for this.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/datastore/Content.js | 110 ++++++++++++++++++++++-----------------
1 file changed, 61 insertions(+), 49 deletions(-)
diff --git a/www/datastore/Content.js b/www/datastore/Content.js
index f715f814..8f063287 100644
--- a/www/datastore/Content.js
+++ b/www/datastore/Content.js
@@ -806,6 +806,48 @@ Ext.define('PBS.DataStoreContent', {
}).show();
},
+ onMoreActions: function(panel, rI, cI, item, event, record) {
+ this.showContextMenu(panel, record, event, false);
+ },
+
+ showContextMenu: function(panel, record, event, rightClick) {
+ let me = this;
+ event.stopEvent();
+ let menu;
+ let view = me.getView();
+ let createControllerCallback = function(name) {
+ return function() {
+ me[name](view, undefined, undefined, undefined, undefined, record);
+ };
+ };
+ if (record.data.ty === 'group') {
+ let params = {
+ title: gettext('Group'),
+ onChangeOwner: createControllerCallback('onChangeOwner'),
+ };
+ if (rightClick) {
+ params.onVerify = createControllerCallback('onVerify');
+ params.onPrune = createControllerCallback('onPrune');
+ params.onForget = createControllerCallback('onForget');
+ }
+ menu = Ext.create('PBS.datastore.GroupCmdMenu', params);
+ } else if (record.data.ty === 'dir') {
+ let params = {
+ title: gettext('Snapshot'),
+ onProtectionChange: createControllerCallback('onProtectionChange'),
+ onShowInformation: createControllerCallback('onShowInformation'),
+ };
+ if (rightClick) {
+ params.onVerify = createControllerCallback('onVerify');
+ params.onForget = createControllerCallback('onForget');
+ }
+ menu = Ext.create('PBS.datastore.SnapshotCmdMenu', params);
+ }
+ if (menu) {
+ menu.showAt(event.getXY());
+ }
+ },
+
filter: function(item, value) {
if (item.data.text.indexOf(value) !== -1) {
return true;
@@ -882,35 +924,7 @@ Ext.define('PBS.DataStoreContent', {
}
},
itemcontextmenu: function(panel, record, item, index, event) {
- event.stopEvent();
- let menu;
- let view = panel.up('pbsDataStoreContent');
- let controller = view.getController();
- let createControllerCallback = function(name) {
- return function() {
- controller[name](view, undefined, undefined, undefined, undefined, record);
- };
- };
- if (record.data.ty === 'group') {
- menu = Ext.create('PBS.datastore.GroupCmdMenu', {
- title: gettext('Group'),
- onVerify: createControllerCallback('onVerify'),
- onChangeOwner: createControllerCallback('onChangeOwner'),
- onPrune: createControllerCallback('onPrune'),
- onForget: createControllerCallback('onForget'),
- });
- } else if (record.data.ty === 'dir') {
- menu = Ext.create('PBS.datastore.SnapshotCmdMenu', {
- title: gettext('Snapshot'),
- onVerify: createControllerCallback('onVerify'),
- onProtectionChange: createControllerCallback('onProtectionChange'),
- onShowInformation: createControllerCallback('onShowInformation'),
- onForget: createControllerCallback('onForget'),
- });
- }
- if (menu) {
- menu.showAt(event.getXY());
- }
+ this.getController().showContextMenu(panel, record, event, true);
},
},
@@ -1000,30 +1014,12 @@ Ext.define('PBS.DataStoreContent', {
? 'pve-icon-verify-lettering' : 'pmx-hidden',
isActionDisabled: (v, r, c, i, rec) => !!rec.data.leaf,
},
- {
- handler: 'onChangeOwner',
- getClass: (v, m, { data }) => data.ty === 'group' ? 'fa fa-user' : 'pmx-hidden',
- getTip: (v, m, rec) => Ext.String.format(gettext("Change owner of '{0}'"), v),
- isActionDisabled: (v, r, c, i, { data }) => data.ty !== 'group',
- },
{
handler: 'onPrune',
getTip: (v, m, rec) => Ext.String.format(gettext("Prune '{0}'"), v),
getClass: (v, m, { data }) => data.ty === 'group' ? 'fa fa-scissors' : 'pmx-hidden',
isActionDisabled: (v, r, c, i, { data }) => data.ty !== 'group',
},
- {
- handler: 'onProtectionChange',
- getTip: (v, m, rec) => Ext.String.format(gettext("Change protection of '{0}'"), v),
- getClass: (v, m, rec) => {
- if (rec.data.ty === 'dir') {
- let extraCls = rec.data.protected ? 'good' : 'faded';
- return `fa fa-shield ${extraCls}`;
- }
- return 'pmx-hidden';
- },
- isActionDisabled: (v, r, c, i, rec) => rec.data.ty !== 'dir',
- },
{
handler: 'onForget',
getTip: (v, m, { data }) => {
@@ -1044,6 +1040,12 @@ Ext.define('PBS.DataStoreContent', {
: 'pmx-hidden',
isActionDisabled: (v, r, c, i, { data }) => false,
},
+ {
+ handler: 'onMoreActions',
+ getTip: (v, m, { data }) => gettext('Show more actions'),
+ getClass: (v, m, { data }) => data.ty === 'group' || data.ty === 'dir' ? 'fa fa-bars' : 'pmx-hidden',
+ isActionDisabled: (v, r, c, i, { data }) => data.ty !== 'group' && data.ty !== 'dir',
+ },
{
handler: 'downloadFile',
getTip: (v, m, rec) => Ext.String.format(gettext("Download '{0}'"), v),
@@ -1325,7 +1327,12 @@ Ext.define('PBS.datastore.GroupCmdMenu', {
hidden: '{!onPrune}',
},
},
- { xtype: 'menuseparator' },
+ {
+ xtype: 'menuseparator',
+ cbind: {
+ hidden: '{!onForget}',
+ },
+ },
{
text: gettext('Remove'),
iconCls: 'fa critical fa-trash-o',
@@ -1374,7 +1381,12 @@ Ext.define('PBS.datastore.SnapshotCmdMenu', {
disabled: '{!onShowInformation}',
},
},
- { xtype: 'menuseparator' },
+ {
+ xtype: 'menuseparator',
+ cbind: {
+ hidden: '{!onForget}',
+ },
+ },
{
text: gettext('Remove'),
iconCls: 'fa critical fa-trash-o',
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread