proxmox-backup/www/NavigationTree.js
Dominik Csapak c7d42dac97 ui: navigation tree: fix losing datastore selection on store load
instead of using 'replaceChild', simply set the appropriate
properties. When using the 'nodeUpdate' (protected function of extjs,
intended to be overwritten) instead of the private 'updateNode', it
will be called when the properties change

This way, the treenode stays the same and it can keep the selection

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-05-12 14:28:16 +02:00

339 lines
7.7 KiB
JavaScript

Ext.define('pbs-datastore-list', {
extend: 'Ext.data.Model',
fields: ['name', 'comment', 'maintenance'],
proxy: {
type: 'proxmox',
url: "/api2/json/admin/datastore",
},
idProperty: 'store',
});
Ext.define('pbs-tape-drive-list', {
extend: 'Ext.data.Model',
fields: ['name', 'changer'],
proxy: {
type: 'proxmox',
url: "/api2/json/tape/drive",
},
idProperty: 'name',
});
Ext.define('PBS.store.NavigationStore', {
extend: 'Ext.data.TreeStore',
storeId: 'NavigationStore',
root: {
expanded: true,
children: [
{
text: gettext('Dashboard'),
iconCls: 'fa fa-tachometer',
path: 'pbsDashboard',
leaf: true,
},
{
text: gettext('Notes'),
iconCls: 'fa fa-sticky-note-o',
path: 'pbsNodeNotes',
leaf: true,
},
{
text: gettext('Configuration'),
iconCls: 'fa fa-gears',
path: 'pbsSystemConfiguration',
expanded: true,
children: [
{
text: gettext('Access Control'),
iconCls: 'fa fa-key',
path: 'pbsAccessControlPanel',
leaf: true,
},
{
text: gettext('Remotes'),
iconCls: 'fa fa-server',
path: 'pbsRemoteView',
leaf: true,
},
{
text: gettext('Traffic Control'),
iconCls: 'fa fa-signal fa-rotate-90',
path: 'pbsTrafficControlView',
leaf: true,
},
{
text: gettext('Certificates'),
iconCls: 'fa fa-certificate',
path: 'pbsCertificateConfiguration',
leaf: true,
},
{
text: gettext('Subscription'),
iconCls: 'fa fa-support',
path: 'pbsSubscription',
leaf: true,
},
],
},
{
text: gettext('Administration'),
iconCls: 'fa fa-wrench',
path: 'pbsServerAdministration',
expanded: true,
leaf: false,
children: [
{
text: gettext('Shell'),
iconCls: 'fa fa-terminal',
path: 'pbsXtermJsConsole',
leaf: true,
},
{
text: gettext('Storage / Disks'),
iconCls: 'fa fa-hdd-o',
path: 'pbsStorageAndDiskPanel',
leaf: true,
},
],
},
{
text: "Tape Backup",
iconCls: 'pbs-icon-tape',
id: 'tape_management',
path: 'pbsTapeManagement',
expanded: true,
children: [],
},
{
text: gettext('Datastore'),
iconCls: 'fa fa-archive',
id: 'datastores',
path: 'pbsDataStores',
expanded: true,
expandable: false,
leaf: false,
children: [
{
text: gettext('Add Datastore'),
iconCls: 'fa fa-plus-circle',
leaf: true,
id: 'addbutton',
virtualEntry: true,
},
],
},
],
},
});
Ext.define('CustomTreeListItem', {
extend: 'Ext.list.TreeItem',
xtype: 'qtiptreelistitem',
nodeUpdate: function(node, modifiedFieldNames) {
this.callParent(arguments);
const qtip = node ? node.get('qtip') : null;
if (qtip) {
this.element.dom.setAttribute('data-qtip', qtip);
} else {
this.element.dom.removeAttribute('data-qtip');
}
},
});
Ext.define('PBS.view.main.NavigationTree', {
extend: 'Ext.list.Tree',
xtype: 'navigationtree',
defaults: {
xtype: 'qtiptreelistitem',
},
controller: {
xclass: 'Ext.app.ViewController',
init: function(view) {
view.rstore = Ext.create('Proxmox.data.UpdateStore', {
autoStart: true,
interval: 5 * 1000,
storeid: 'pbs-datastore-list',
model: 'pbs-datastore-list',
});
view.rstore.on('load', this.onLoad, this);
view.on('destroy', view.rstore.stopUpdate);
if (view.tapeStore === undefined) {
view.tapeStore = Ext.create('Proxmox.data.UpdateStore', {
autoStart: true,
interval: 60 * 1000,
storeid: 'pbs-tape-drive-list',
model: 'pbs-tape-drive-list',
});
view.tapeStore.on('load', this.onTapeDriveLoad, this);
view.on('destroy', view.tapeStore.stopUpdate);
}
},
onTapeDriveLoad: function(store, records, success) {
if (!success) return;
let view = this.getView();
let root = view.getStore().getRoot();
records.sort((a, b) => a.data.name.localeCompare(b.data.name));
let list = root.findChild('id', 'tape_management', false);
let existingChildren = {};
for (const drive of records) {
let path, text, iconCls;
if (drive.data.changer !== undefined) {
text = drive.data.changer;
path = `Changer-${text}`;
iconCls = 'fa fa-exchange';
} else {
text = drive.data.name;
path = `Drive-${text}`;
iconCls = 'pbs-icon-tape-drive';
}
existingChildren[path] = {
text,
path,
iconCls,
leaf: true,
};
}
let paths = Object.keys(existingChildren).sort();
let oldIdx = 0;
for (let newIdx = 0; newIdx < paths.length; newIdx++) {
let newPath = paths[newIdx];
// find index to insert
while (oldIdx < list.childNodes.length && newPath > list.getChildAt(oldIdx).data.path) {
oldIdx++;
}
if (oldIdx >= list.childNodes.length || list.getChildAt(oldIdx).data.path !== newPath) {
list.insertChild(oldIdx, existingChildren[newPath]);
}
}
let toRemove = [];
list.eachChild((child) => {
if (!existingChildren[child.data.path]) {
toRemove.push(child);
}
});
toRemove.forEach((child) => list.removeChild(child, true));
if (view.pathToSelect !== undefined) {
let path = view.pathToSelect;
delete view.pathToSelect;
view.select(path, true);
}
},
onLoad: function(store, records, success) {
if (!success) {
return;
}
let view = this.getView();
let root = view.getStore().getRoot();
records.sort((a, b) => a.id.localeCompare(b.id));
let list = root.findChild('id', 'datastores', false);
let getChildTextAt = i => list.getChildAt(i).data.text;
let existingChildren = {};
for (let i = 0, j = 0, length = records.length; i < length; i++) {
let name = records[i].id;
existingChildren[name] = true;
while (name.localeCompare(getChildTextAt(j)) > 0 && (j+1) < list.childNodes.length) {
j++;
}
let [qtip, iconCls] = ['', 'fa fa-database'];
const maintenance = records[i].data.maintenance;
if (maintenance) {
const [type, message] = PBS.Utils.parseMaintenanceMode(maintenance);
qtip = `${type}${message ? ': ' + message : ''}`;
iconCls = 'fa fa-database pmx-tree-icon-custom maintenance';
}
if (getChildTextAt(j).localeCompare(name) !== 0) {
list.insertChild(j, {
text: name,
qtip,
path: `DataStore-${name}`,
iconCls,
leaf: true,
});
} else {
let oldChild = list.getChildAt(j);
oldChild.set('qtip', qtip);
oldChild.set('iconCls', iconCls);
}
}
// remove entries which are not existing anymore
let toRemove = [];
list.eachChild(child => {
if (!existingChildren[child.data.text] && !child.data.virtualEntry) {
toRemove.push(child);
}
});
toRemove.forEach(child => list.removeChild(child, true));
if (view.pathToSelect !== undefined) {
let path = view.pathToSelect;
delete view.pathToSelect;
view.select(path, true);
}
},
},
listeners: {
itemclick: function(tl, info) {
if (info.node.data.id === 'addbutton') {
let me = this;
Ext.create('PBS.DataStoreEdit', {
listeners: {
destroy: () => me.rstore.reload(),
},
}).show();
return false;
}
return true;
},
},
reloadTapeStore: function() {
let me = this;
me.tapeStore.load();
},
select: function(path, silent) {
var me = this;
if (me.rstore.isLoaded() && me.tapeStore.isLoaded()) {
if (silent) {
me.suspendEvents(false);
}
var item = me.getStore().findRecord('path', path, 0, false, true, true);
me.setSelection(item);
if (silent) {
me.resumeEvents(true);
}
} else {
me.pathToSelect = path;
}
},
animation: false,
expanderOnly: true,
expanderFirst: false,
store: 'NavigationStore',
ui: 'nav',
});