2e63a46414
This provides immediate feedback for adding the respective icon in
the navigation tree entry most of the time, and we can then increase
the query period of the datastore list store to the original 15
again, as it was lowered to 5 seconds for just this reason in commit
fbd6f54f39
Signed-off-by: Thomas Lamprecht <t.lamprecht@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: 15 * 1000,
|
|
storeId: 'pbs-datastore-list', // NOTE: this is queried by selectors, avoid change!
|
|
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',
|
|
});
|