c7d42dac97
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>
339 lines
7.7 KiB
JavaScript
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',
|
|
});
|