ui: tape: rework BackupOverview
instead of grouping by tape (which is rarely interesting), group by pool -> group -> id -> mediaset this way a user looking for a backup of specific vm can do just that we may want to have an additional view here were we list all snapshots included in the selected media-set ? Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
f806c0effa
commit
631e550920
|
@ -260,6 +260,15 @@ Ext.define('PBS.Utils', {
|
||||||
return dedup;
|
return dedup;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
parse_snapshot_id: function(snapshot) {
|
||||||
|
if (!snapshot) {
|
||||||
|
return [undefined, undefined, undefined];
|
||||||
|
}
|
||||||
|
let [_match, type, group, id] = /^([^/]+)\/([^/]+)\/(.+)$/.exec(snapshot);
|
||||||
|
|
||||||
|
return [type, group, id];
|
||||||
|
},
|
||||||
|
|
||||||
get_type_icon_cls: function(btype) {
|
get_type_icon_cls: function(btype) {
|
||||||
var cls = '';
|
var cls = '';
|
||||||
if (btype.startsWith('vm')) {
|
if (btype.startsWith('vm')) {
|
||||||
|
|
|
@ -16,6 +16,90 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
|
||||||
}).show();
|
}).show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
restore: function(button, record) {
|
||||||
|
let me = this;
|
||||||
|
let view = me.getView();
|
||||||
|
let selection = view.getSelection();
|
||||||
|
if (!selection || selection.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaset = selection[0].data.text;
|
||||||
|
let uuid = selection[0].data.uuid;
|
||||||
|
Ext.create('PBS.TapeManagement.TapeRestoreWindow', {
|
||||||
|
mediaset,
|
||||||
|
uuid,
|
||||||
|
listeners: {
|
||||||
|
destroy: function() {
|
||||||
|
me.reload();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).show();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadContent: async function() {
|
||||||
|
let content_response = await PBS.Async.api2({
|
||||||
|
url: '/api2/extjs/tape/media/content',
|
||||||
|
});
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
for (const entry of content_response.result.data) {
|
||||||
|
let pool = entry.pool;
|
||||||
|
let [type, group_id, id] = PBS.Utils.parse_snapshot_id(entry.snapshot);
|
||||||
|
let group = `${type}/${group_id}`;
|
||||||
|
let media_set = entry['media-set-name'];
|
||||||
|
let uuid = entry['media-set-uuid'];
|
||||||
|
let ctime = entry['media-set-ctime'];
|
||||||
|
if (data[pool] === undefined) {
|
||||||
|
data[pool] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[pool][group] === undefined) {
|
||||||
|
data[pool][group] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[pool][group][id] === undefined) {
|
||||||
|
data[pool][group][id] = [];
|
||||||
|
}
|
||||||
|
data[pool][group][id].push({
|
||||||
|
text: media_set,
|
||||||
|
uuid,
|
||||||
|
ctime,
|
||||||
|
leaf: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = [];
|
||||||
|
|
||||||
|
for (const [pool, groups] of Object.entries(data)) {
|
||||||
|
let pool_entry = {
|
||||||
|
text: pool,
|
||||||
|
leaf: false,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
for (const [group, ids] of Object.entries(groups)) {
|
||||||
|
let group_entry = {
|
||||||
|
text: group,
|
||||||
|
iconCls: "fa " + PBS.Utils.get_type_icon_cls(group),
|
||||||
|
leaf: false,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
for (const [id, media_sets] of Object.entries(ids)) {
|
||||||
|
let id_entry = {
|
||||||
|
text: `${group}/${id}`,
|
||||||
|
leaf: false,
|
||||||
|
children: media_sets,
|
||||||
|
};
|
||||||
|
group_entry.children.push(id_entry);
|
||||||
|
}
|
||||||
|
pool_entry.children.push(group_entry);
|
||||||
|
}
|
||||||
|
list.push(pool_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
|
||||||
reload: async function() {
|
reload: async function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
let view = me.getView();
|
let view = me.getView();
|
||||||
|
@ -23,43 +107,7 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
|
||||||
Proxmox.Utils.setErrorMask(view, true);
|
Proxmox.Utils.setErrorMask(view, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let list_response = await PBS.Async.api2({
|
let list = await me.loadContent();
|
||||||
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({
|
view.setRootNode({
|
||||||
expanded: true,
|
expanded: true,
|
||||||
|
@ -78,8 +126,14 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
|
||||||
},
|
},
|
||||||
|
|
||||||
store: {
|
store: {
|
||||||
sorters: 'label-text',
|
|
||||||
data: [],
|
data: [],
|
||||||
|
sorters: function(a, b) {
|
||||||
|
if (a.data.leaf && b.data.leaf) {
|
||||||
|
return a.data.ctime - b.data.ctime;
|
||||||
|
} else {
|
||||||
|
return a.data.text.localeCompare(b.data.text);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
rootVisible: false,
|
rootVisible: false,
|
||||||
|
@ -99,50 +153,15 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
xtype: 'treecolumn',
|
xtype: 'treecolumn',
|
||||||
text: gettext('Tape/Backup'),
|
text: gettext('Pool/Group/Snapshot/Media Set'),
|
||||||
dataIndex: 'label-text',
|
dataIndex: 'text',
|
||||||
|
sortable: false,
|
||||||
flex: 3,
|
flex: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Location'),
|
text: gettext('Media Set UUID'),
|
||||||
dataIndex: 'location',
|
dataIndex: 'uuid',
|
||||||
flex: 1,
|
sortable: false,
|
||||||
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,
|
flex: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue