ui: content: show namespaces also inline and rework node type detection
this not only makes the action disable/hide checks simpler, but also prepares the view a bit for the idea of adding a new API endpoint that returns the whole datastore content tree as structured JSON so that it can be directly loaded into a tree store. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
fe79687c59
commit
7e8b24bd8c
|
@ -14,6 +14,8 @@ Ext.define('pbs-data-store-snapshots', {
|
||||||
'verification',
|
'verification',
|
||||||
'fingerprint',
|
'fingerprint',
|
||||||
{ name: 'size', type: 'int', allowNull: true },
|
{ name: 'size', type: 'int', allowNull: true },
|
||||||
|
{ name: 'sortWeight', type: 'int', allowNull: true },
|
||||||
|
{ name: 'ty', type: 'string', allowNull: true },
|
||||||
{
|
{
|
||||||
name: 'crypt-mode',
|
name: 'crypt-mode',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
@ -50,7 +52,7 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
alias: 'widget.pbsDataStoreContent',
|
alias: 'widget.pbsDataStoreContent',
|
||||||
mixins: ['Proxmox.Mixin.CBind'],
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
rootVisible: true,
|
rootVisible: false,
|
||||||
|
|
||||||
title: gettext('Content'),
|
title: gettext('Content'),
|
||||||
|
|
||||||
|
@ -69,7 +71,7 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
this.store.on('load', this.onLoad, this);
|
this.store.on('load', this.onLoad, this);
|
||||||
|
|
||||||
view.getStore().setSorters([
|
view.getStore().setSorters([
|
||||||
'backup-group',
|
'sortWeight',
|
||||||
'text',
|
'text',
|
||||||
'backup-time',
|
'backup-time',
|
||||||
]);
|
]);
|
||||||
|
@ -77,13 +79,28 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
},
|
},
|
||||||
|
|
||||||
control: {
|
control: {
|
||||||
|
'#': { // view
|
||||||
|
rowdblclick: 'rowDoubleClicked',
|
||||||
|
},
|
||||||
'pbsNamespaceSelector': {
|
'pbsNamespaceSelector': {
|
||||||
change: 'nsChange',
|
change: 'nsChange',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
rowDoubleClicked: function(table, rec, el, rowId, ev) {
|
||||||
|
if (rec?.data?.ty !== 'ns') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.nsChange(null, rec.data.ns);
|
||||||
|
},
|
||||||
|
|
||||||
nsChange: function(field, value) {
|
nsChange: function(field, value) {
|
||||||
let view = this.getView();
|
let view = this.getView();
|
||||||
|
if (field === null) {
|
||||||
|
field = view.down('pbsNamespaceSelector');
|
||||||
|
field.setValue(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
view.namespace = value;
|
view.namespace = value;
|
||||||
this.reload();
|
this.reload();
|
||||||
},
|
},
|
||||||
|
@ -162,7 +179,22 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad: function(store, records, success, operation) {
|
loadNamespaceFromSameLevel: async function() {
|
||||||
|
let view = this.getView();
|
||||||
|
try {
|
||||||
|
let url = `/api2/extjs/admin/datastore/${view.datastore}/namespace?max-depth=1`;
|
||||||
|
if (view.namespace && view.namespace !== '') {
|
||||||
|
url += `&parent=${encodeURIComponent(view.namespace)}`;
|
||||||
|
}
|
||||||
|
let { result: { data: ns } } = await Proxmox.Async.api2({ url });
|
||||||
|
return ns;
|
||||||
|
} catch (err) {
|
||||||
|
console.debug(err);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad: async function(store, records, success, operation) {
|
||||||
let me = this;
|
let me = this;
|
||||||
let view = this.getView();
|
let view = this.getView();
|
||||||
|
|
||||||
|
@ -171,6 +203,8 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let namespaces = await me.loadNamespaceFromSameLevel();
|
||||||
|
|
||||||
let groups = this.getRecordGroups(records);
|
let groups = this.getRecordGroups(records);
|
||||||
|
|
||||||
let selected;
|
let selected;
|
||||||
|
@ -207,6 +241,7 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
data.leaf = false;
|
data.leaf = false;
|
||||||
data.cls = 'no-leaf-icons';
|
data.cls = 'no-leaf-icons';
|
||||||
data.matchesFilter = true;
|
data.matchesFilter = true;
|
||||||
|
data.ty = 'dir';
|
||||||
|
|
||||||
data.expanded = !!expanded[data.text];
|
data.expanded = !!expanded[data.text];
|
||||||
|
|
||||||
|
@ -217,6 +252,7 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
file.fingerprint = data.fingerprint;
|
file.fingerprint = data.fingerprint;
|
||||||
file.leaf = true;
|
file.leaf = true;
|
||||||
file.matchesFilter = true;
|
file.matchesFilter = true;
|
||||||
|
file.ty = 'file';
|
||||||
|
|
||||||
data.children.push(file);
|
data.children.push(file);
|
||||||
}
|
}
|
||||||
|
@ -271,22 +307,62 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
crypt.count = group.count;
|
crypt.count = group.count;
|
||||||
group['crypt-mode'] = PBS.Utils.calculateCryptMode(crypt);
|
group['crypt-mode'] = PBS.Utils.calculateCryptMode(crypt);
|
||||||
group.expanded = !!expanded[name];
|
group.expanded = !!expanded[name];
|
||||||
|
group.sortWeight = 0;
|
||||||
|
group.ty = 'group';
|
||||||
children.push(group);
|
children.push(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of namespaces) {
|
||||||
|
if (item.ns === view.namespace || (!view.namespace && item.ns === '')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
children.push({
|
||||||
|
text: item.ns,
|
||||||
|
iconCls: 'fa fa-object-group',
|
||||||
|
expanded: true,
|
||||||
|
expandable: false,
|
||||||
|
ns: (view.namespaces ?? '') !== '' ? `/${item.ns}` : item.ns,
|
||||||
|
ty: 'ns',
|
||||||
|
sortWeight: 10,
|
||||||
|
//qtip: gettext('Double-click to browse namespace.'),
|
||||||
|
leaf: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let isRootNS = !view.namespace || view.namespace === '';
|
let isRootNS = !view.namespace || view.namespace === '';
|
||||||
let rootText = isRootNS
|
let rootText = isRootNS
|
||||||
? gettext('Root Namespace')
|
? gettext('Root Namespace')
|
||||||
: Ext.String.format(gettext("Namespace '{0}'"), view.namespace);
|
: Ext.String.format(gettext("Namespace '{0}'"), view.namespace);
|
||||||
|
|
||||||
view.setRootNode({
|
let topNodes = [];
|
||||||
|
if (!isRootNS) {
|
||||||
|
let parentNS = view.namespace.split('/').slice(0, -1).join('/');
|
||||||
|
topNodes.push({
|
||||||
|
text: `.. (${parentNS === '' ? gettext('Root') : parentNS})`,
|
||||||
|
iconCls: 'fa fa-level-up',
|
||||||
|
//qtip: gettext('Double-click to go one namespace level up.'),
|
||||||
|
ty: 'ns',
|
||||||
|
ns: parentNS,
|
||||||
|
sortWeight: -10,
|
||||||
|
leaf: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
topNodes.push({
|
||||||
text: rootText,
|
text: rootText,
|
||||||
iconCls: "fa fa-" + (isRootNS ? 'database' : 'object-group'),
|
iconCls: "fa fa-" + (isRootNS ? 'database' : 'object-group'),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
expandable: false,
|
expandable: false,
|
||||||
|
sortWeight: -5,
|
||||||
|
root: true, // fake root
|
||||||
|
ty: 'ns',
|
||||||
children: children,
|
children: children,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
view.setRootNode({
|
||||||
|
expanded: true,
|
||||||
|
children: topNodes,
|
||||||
|
});
|
||||||
|
|
||||||
if (!children.length) {
|
if (!children.length) {
|
||||||
view.setEmptyText(Ext.String.format(
|
view.setEmptyText(Ext.String.format(
|
||||||
gettext('No accessible snapshots found in namespace {0}'),
|
gettext('No accessible snapshots found in namespace {0}'),
|
||||||
|
@ -668,6 +744,11 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
let me = this;
|
let me = this;
|
||||||
let view = me.getView();
|
let view = me.getView();
|
||||||
|
|
||||||
|
if (rec.data.ty === 'ns') {
|
||||||
|
me.nsChange(null, rec.data.ns);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(rec && rec.data)) return;
|
if (!(rec && rec.data)) return;
|
||||||
let data = rec.parentNode.data;
|
let data = rec.parentNode.data;
|
||||||
|
|
||||||
|
@ -711,7 +792,8 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
let store = view.getStore();
|
let store = view.getStore();
|
||||||
if (!value && value !== 0) {
|
if (!value && value !== 0) {
|
||||||
store.clearFilter();
|
store.clearFilter();
|
||||||
store.getRoot().collapseChildren(true);
|
// only collapse the children below our toplevel namespace "root"
|
||||||
|
store.getRoot().lastChild.collapseChildren(true);
|
||||||
tf.triggers.clear.setVisible(false);
|
tf.triggers.clear.setVisible(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -844,64 +926,62 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
{
|
{
|
||||||
handler: 'onVerify',
|
handler: 'onVerify',
|
||||||
getTip: (v, m, rec) => Ext.String.format(gettext("Verify '{0}'"), v),
|
getTip: (v, m, rec) => Ext.String.format(gettext("Verify '{0}'"), v),
|
||||||
getClass: (v, m, rec) => rec.data.root || rec.data.leaf ? 'pmx-hidden' : 'pve-icon-verify-lettering',
|
getClass: (v, m, { data }) => data.ty === 'group' || data.ty === 'dir' ? 'pve-icon-verify-lettering' : 'pmx-hidden',
|
||||||
isActionDisabled: (v, r, c, i, rec) => !!rec.data.leaf,
|
isActionDisabled: (v, r, c, i, rec) => !!rec.data.leaf,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: 'onChangeOwner',
|
handler: 'onChangeOwner',
|
||||||
getClass: (v, m, rec) => rec.parentNode && rec.parentNode.id ==='root' ? 'fa fa-user' : 'pmx-hidden',
|
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),
|
getTip: (v, m, rec) => Ext.String.format(gettext("Change owner of '{0}'"), v),
|
||||||
isActionDisabled: (v, r, c, i, rec) => !rec.parentNode || rec.parentNode.id !=='root',
|
isActionDisabled: (v, r, c, i, { data }) => data.ty !== 'group',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: 'onPrune',
|
handler: 'onPrune',
|
||||||
getTip: (v, m, rec) => Ext.String.format(gettext("Prune '{0}'"), v),
|
getTip: (v, m, rec) => Ext.String.format(gettext("Prune '{0}'"), v),
|
||||||
getClass: (v, m, rec) => rec.parentNode && rec.parentNode.id ==='root' ? 'fa fa-scissors' : 'pmx-hidden',
|
getClass: (v, m, { data }) => data.ty === 'group' ? 'fa fa-scissors' : 'pmx-hidden',
|
||||||
isActionDisabled: (v, r, c, i, rec) => rec.parentNode?.id !=='root',
|
isActionDisabled: (v, r, c, i, { data }) => data.ty !== 'group',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: 'onProtectionChange',
|
handler: 'onProtectionChange',
|
||||||
getTip: (v, m, rec) => Ext.String.format(gettext("Change protection of '{0}'"), v),
|
getTip: (v, m, rec) => Ext.String.format(gettext("Change protection of '{0}'"), v),
|
||||||
getClass: (v, m, rec) => {
|
getClass: (v, m, rec) => {
|
||||||
if (!rec.data.leaf && rec.parentNode && rec.parentNode.id !== 'root') {
|
if (rec.data.ty === 'dir') {
|
||||||
let extraCls = rec.data.protected ? 'good' : 'faded';
|
let extraCls = rec.data.protected ? 'good' : 'faded';
|
||||||
return `fa fa-shield ${extraCls}`;
|
return `fa fa-shield ${extraCls}`;
|
||||||
}
|
}
|
||||||
return 'pmx-hidden';
|
return 'pmx-hidden';
|
||||||
},
|
},
|
||||||
isActionDisabled: (v, r, c, i, rec) => !!rec.data.leaf || !rec.parentNode || rec.parentNode.id === 'root',
|
isActionDisabled: (v, r, c, i, rec) => rec.data.ty !== 'dir',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: 'onForget',
|
handler: 'onForget',
|
||||||
getTip: (v, m, rec) => rec.parentNode?.id !=='root'
|
getTip: (v, m, { data }) => data ==='dir'
|
||||||
? Ext.String.format(gettext("Permanently forget snapshot '{0}'"), v)
|
? Ext.String.format(gettext("Permanently forget snapshot '{0}'"), v)
|
||||||
: Ext.String.format(gettext("Permanently forget group '{0}'"), v),
|
: Ext.String.format(gettext("Permanently forget group '{0}'"), v),
|
||||||
getClass: (v, m, rec) => !(rec.data.leaf || rec.data.root) ? 'fa critical fa-trash-o' : 'pmx-hidden',
|
getClass: (v, m, { data }) =>
|
||||||
isActionDisabled: (v, r, c, i, rec) => !!rec.data.leaf,
|
data.ty === 'group' || data.ty === 'dir' ? 'fa critical fa-trash-o' : 'pmx-hidden',
|
||||||
|
isActionDisabled: (v, r, c, i, { data }) => !(data.ty === 'group' || data.ty === 'dir'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: 'downloadFile',
|
handler: 'downloadFile',
|
||||||
getTip: (v, m, rec) => Ext.String.format(gettext("Download '{0}'"), v),
|
getTip: (v, m, rec) => Ext.String.format(gettext("Download '{0}'"), v),
|
||||||
getClass: (v, m, rec) => rec.data.leaf && rec.data.filename ? 'fa fa-download' : 'pmx-hidden',
|
getClass: (v, m, { data }) => data.ty === 'file' ? 'fa fa-download' : 'pmx-hidden',
|
||||||
isActionDisabled: (v, r, c, i, rec) => !rec.data.leaf || !rec.data.filename || rec.data['crypt-mode'] > 2,
|
isActionDisabled: (v, r, c, i, rec) => rec.data.ty !== 'file' || rec.data['crypt-mode'] > 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: 'openPxarBrowser',
|
handler: 'openPxarBrowser',
|
||||||
tooltip: gettext('Browse'),
|
tooltip: gettext('Browse'),
|
||||||
getClass: (v, m, rec) => {
|
getClass: (v, m, { data }) => {
|
||||||
let data = rec.data;
|
if (
|
||||||
if (data.leaf && data.filename && data.filename.endsWith('pxar.didx')) {
|
(data.ty === 'file' && data.filename.endsWith('pxar.didx')) ||
|
||||||
|
(data.ty === 'ns' && !data.root)
|
||||||
|
) {
|
||||||
return 'fa fa-folder-open-o';
|
return 'fa fa-folder-open-o';
|
||||||
}
|
}
|
||||||
return 'pmx-hidden';
|
return 'pmx-hidden';
|
||||||
},
|
},
|
||||||
isActionDisabled: (v, r, c, i, rec) => {
|
isActionDisabled: (v, r, c, i, { data }) =>
|
||||||
let data = rec.data;
|
!(data.ty === 'file' && data.filename.endsWith('pxar.didx') && data['crypt-mode'] < 3) && data.ty !== 'ns',
|
||||||
return !(data.leaf &&
|
|
||||||
data.filename &&
|
|
||||||
data.filename.endsWith('pxar.didx') &&
|
|
||||||
data['crypt-mode'] < 3);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -917,8 +997,8 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
header: gettext("Size"),
|
header: gettext("Size"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
dataIndex: 'size',
|
dataIndex: 'size',
|
||||||
renderer: (v, meta, record) => {
|
renderer: (v, meta, { data }) => {
|
||||||
if ((record.data.text === 'client.log.blob' && v === undefined) || record.data.root) {
|
if ((data.text === 'client.log.blob' && v === undefined) || (data.ty !== 'dir' && data.ty !== 'file')) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (v === undefined || v === null) {
|
if (v === undefined || v === null) {
|
||||||
|
@ -992,7 +1072,7 @@ Ext.define('PBS.DataStoreContent', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer: (v, meta, record) => {
|
renderer: (v, meta, record) => {
|
||||||
if (!record.parentNode) {
|
if (record.data.ty === 'ns') {
|
||||||
return ''; // TODO: accumulate verify of all groups into root NS node?
|
return ''; // TODO: accumulate verify of all groups into root NS node?
|
||||||
}
|
}
|
||||||
let i = (cls, txt) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
|
let i = (cls, txt) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
|
||||||
|
|
Loading…
Reference in New Issue