ui: add Panels necessary for Datastores Overview
a panel for a single datastore that gets updated from an external caller shows the usage, estimated full date, history and task summary grid a panel that dynamically generates the panel above for each datastore and a tabpanel that includes the panel above, as well as a global syncview, verifiyview and aclview Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
63c07d950c
commit
2371c1e371
|
@ -56,6 +56,8 @@ JSSRC= \
|
||||||
datastore/Content.js \
|
datastore/Content.js \
|
||||||
datastore/OptionView.js \
|
datastore/OptionView.js \
|
||||||
datastore/Panel.js \
|
datastore/Panel.js \
|
||||||
|
datastore/DataStoreListSummary.js \
|
||||||
|
datastore/DataStoreList.js \
|
||||||
ServerStatus.js \
|
ServerStatus.js \
|
||||||
ServerAdministration.js \
|
ServerAdministration.js \
|
||||||
Dashboard.js \
|
Dashboard.js \
|
||||||
|
|
|
@ -0,0 +1,229 @@
|
||||||
|
// Overview over all datastores
|
||||||
|
Ext.define('PBS.datastore.DataStoreList', {
|
||||||
|
extend: 'Ext.panel.Panel',
|
||||||
|
alias: 'widget.pbsDataStoreList',
|
||||||
|
|
||||||
|
title: gettext('Summary'),
|
||||||
|
|
||||||
|
scrollable: true,
|
||||||
|
|
||||||
|
bodyPadding: 5,
|
||||||
|
defaults: {
|
||||||
|
xtype: 'pbsDataStoreListSummary',
|
||||||
|
padding: 5,
|
||||||
|
},
|
||||||
|
|
||||||
|
datastores: {},
|
||||||
|
tasks: {},
|
||||||
|
|
||||||
|
updateTasks: function(taskStore, records, success) {
|
||||||
|
let me = this;
|
||||||
|
if (!success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const store of Object.keys(me.tasks)) {
|
||||||
|
me.tasks[store] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
records.forEach(record => {
|
||||||
|
let task = record.data;
|
||||||
|
if (!task.worker_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = task.worker_type;
|
||||||
|
if (type === 'syncjob') {
|
||||||
|
type = 'sync';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.startsWith('verif')) {
|
||||||
|
type = 'verify';
|
||||||
|
}
|
||||||
|
|
||||||
|
let datastore = PBS.Utils.parse_datastore_worker_id(type, task.worker_id);
|
||||||
|
if (!datastore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!me.tasks[datastore]) {
|
||||||
|
me.tasks[datastore] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!me.tasks[datastore][type]) {
|
||||||
|
me.tasks[datastore][type] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me.tasks[datastore][type] && task.status) {
|
||||||
|
let parsed = Proxmox.Utils.parse_task_status(task.status);
|
||||||
|
if (!me.tasks[datastore][type][parsed]) {
|
||||||
|
me.tasks[datastore][type][parsed] = 0;
|
||||||
|
}
|
||||||
|
me.tasks[datastore][type][parsed]++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [store, panel] of Object.entries(me.datastores)) {
|
||||||
|
panel.setTasks(me.tasks[store], me.since);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStores: function(usageStore, records, success) {
|
||||||
|
let me = this;
|
||||||
|
if (!success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = {};
|
||||||
|
|
||||||
|
records.forEach((rec) => {
|
||||||
|
found[rec.data.store] = true;
|
||||||
|
me.addSorted(rec.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [store, panel] of Object.entries(me.datastores)) {
|
||||||
|
if (!found[store]) {
|
||||||
|
me.remove(panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addSorted: function(data) {
|
||||||
|
let me = this;
|
||||||
|
let i = 0;
|
||||||
|
let datastores = Object
|
||||||
|
.keys(me.datastores)
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
for (const datastore of datastores) {
|
||||||
|
let result = datastore.localeCompare(data.store);
|
||||||
|
if (result === 0) {
|
||||||
|
me.datastores[datastore].setStatus(data);
|
||||||
|
return;
|
||||||
|
} else if (result > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
me.datastores[data.store] = me.insert(i, {
|
||||||
|
datastore: data.store,
|
||||||
|
});
|
||||||
|
me.datastores[data.store].setStatus(data);
|
||||||
|
me.datastores[data.store].setTasks(me.tasks[data.store], me.since);
|
||||||
|
},
|
||||||
|
|
||||||
|
initComponent: function() {
|
||||||
|
let me = this;
|
||||||
|
me.items = [];
|
||||||
|
me.datastores = {};
|
||||||
|
// todo make configurable?
|
||||||
|
me.since = (Date.now()/1000 - 30 * 24*3600).toFixed(0);
|
||||||
|
|
||||||
|
me.usageStore = Ext.create('Proxmox.data.UpdateStore', {
|
||||||
|
storeid: 'datastore-overview-usage',
|
||||||
|
interval: 5000,
|
||||||
|
proxy: {
|
||||||
|
type: 'proxmox',
|
||||||
|
url: '/api2/json/status/datastore-usage',
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
load: {
|
||||||
|
fn: me.updateStores,
|
||||||
|
scope: me,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
me.taskStore = Ext.create('Proxmox.data.UpdateStore', {
|
||||||
|
storeid: 'datastore-overview-tasks',
|
||||||
|
interval: 15000,
|
||||||
|
model: 'proxmox-tasks',
|
||||||
|
proxy: {
|
||||||
|
type: 'proxmox',
|
||||||
|
url: '/api2/json/nodes/localhost/tasks',
|
||||||
|
extraParams: {
|
||||||
|
limit: 0,
|
||||||
|
since: me.since,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
load: {
|
||||||
|
fn: me.updateTasks,
|
||||||
|
scope: me,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
me.callParent();
|
||||||
|
Proxmox.Utils.monStoreErrors(me, me.usageStore);
|
||||||
|
Proxmox.Utils.monStoreErrors(me, me.taskStore);
|
||||||
|
me.on('activate', function() {
|
||||||
|
me.usageStore.startUpdate();
|
||||||
|
me.taskStore.startUpdate();
|
||||||
|
});
|
||||||
|
me.on('destroy', function() {
|
||||||
|
me.usageStore.stopUpdate();
|
||||||
|
me.taskStore.stopUpdate();
|
||||||
|
});
|
||||||
|
me.on('deactivate', function() {
|
||||||
|
me.usageStore.stopUpdate();
|
||||||
|
me.taskStore.stopUpdate();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Ext.define('PBS.datastore.DataStores', {
|
||||||
|
extend: 'Ext.tab.Panel',
|
||||||
|
alias: 'widget.pbsDataStores',
|
||||||
|
|
||||||
|
title: gettext('Datastores'),
|
||||||
|
|
||||||
|
stateId: 'pbs-datastores-panel',
|
||||||
|
stateful: true,
|
||||||
|
|
||||||
|
stateEvents: ['tabchange'],
|
||||||
|
|
||||||
|
applyState: function(state) {
|
||||||
|
let me = this;
|
||||||
|
if (state.tab !== undefined) {
|
||||||
|
me.setActiveTab(state.tab);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getState: function() {
|
||||||
|
let me = this;
|
||||||
|
return {
|
||||||
|
tab: me.getActiveTab().getItemId(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
border: false,
|
||||||
|
defaults: {
|
||||||
|
border: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'pbsDataStoreList',
|
||||||
|
iconCls: 'fa fa-book',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
iconCls: 'fa fa-refresh',
|
||||||
|
itemId: 'syncjobs',
|
||||||
|
xtype: 'pbsSyncJobView',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconCls: 'fa fa-check-circle',
|
||||||
|
itemId: 'verifyjobs',
|
||||||
|
xtype: 'pbsVerifyJobView',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId: 'acl',
|
||||||
|
xtype: 'pbsACLView',
|
||||||
|
iconCls: 'fa fa-unlock',
|
||||||
|
aclPath: '/datastore',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -0,0 +1,138 @@
|
||||||
|
// Summary Panel for a single datastore in overview
|
||||||
|
Ext.define('PBS.datastore.DataStoreListSummary', {
|
||||||
|
extend: 'Ext.panel.Panel',
|
||||||
|
alias: 'widget.pbsDataStoreListSummary',
|
||||||
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
|
cbind: {
|
||||||
|
title: '{datastore}',
|
||||||
|
},
|
||||||
|
bodyPadding: 10,
|
||||||
|
|
||||||
|
viewModel: {
|
||||||
|
data: {
|
||||||
|
usage: "N/A",
|
||||||
|
full: "N/A",
|
||||||
|
history: [],
|
||||||
|
},
|
||||||
|
|
||||||
|
stores: {
|
||||||
|
historystore: {
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setTasks: function(taskdata, since) {
|
||||||
|
let me = this;
|
||||||
|
me.down('pbsTaskSummary').updateTasks(taskdata, since);
|
||||||
|
},
|
||||||
|
|
||||||
|
setStatus: function(statusData) {
|
||||||
|
let me = this;
|
||||||
|
let vm = me.getViewModel();
|
||||||
|
vm.set('usagetext', PBS.Utils.render_size_usage(statusData.used, statusData.total));
|
||||||
|
vm.set('usage', statusData.used/statusData.total);
|
||||||
|
let estimate = PBS.Utils.render_estimate(statusData['estimated-full-date']);
|
||||||
|
vm.set('full', estimate);
|
||||||
|
let last = 0;
|
||||||
|
let data = statusData.history.map((val) => {
|
||||||
|
if (val === null) {
|
||||||
|
val = last;
|
||||||
|
} else {
|
||||||
|
last = val;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
let historyStore = vm.getStore('historystore');
|
||||||
|
historyStore.setData([
|
||||||
|
{
|
||||||
|
history: data,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'container',
|
||||||
|
layout: {
|
||||||
|
type: 'hbox',
|
||||||
|
align: 'stretch',
|
||||||
|
},
|
||||||
|
|
||||||
|
defaults: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 5,
|
||||||
|
},
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'pmxInfoWidget',
|
||||||
|
iconCls: 'fa fa-fw fa-hdd-o',
|
||||||
|
title: gettext('Usage'),
|
||||||
|
bind: {
|
||||||
|
data: {
|
||||||
|
usage: '{usage}',
|
||||||
|
text: '{usagetext}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pmxInfoWidget',
|
||||||
|
title: gettext('Estimated Full'),
|
||||||
|
printBar: false,
|
||||||
|
bind: {
|
||||||
|
data: {
|
||||||
|
usage: '0',
|
||||||
|
text: '{full}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// we cannot autosize a sparklineline widget,
|
||||||
|
// abuse a grid with a single column/row to do it for us
|
||||||
|
xtype: 'grid',
|
||||||
|
hideHeaders: true,
|
||||||
|
minHeight: 50,
|
||||||
|
border: false,
|
||||||
|
bodyBorder: false,
|
||||||
|
rowLines: false,
|
||||||
|
disableSelection: true,
|
||||||
|
viewConfig: {
|
||||||
|
trackOver: false,
|
||||||
|
},
|
||||||
|
bind: {
|
||||||
|
store: '{historystore}',
|
||||||
|
},
|
||||||
|
columns: [{
|
||||||
|
xtype: 'widgetcolumn',
|
||||||
|
flex: 1,
|
||||||
|
dataIndex: 'history',
|
||||||
|
widget: {
|
||||||
|
xtype: 'sparklineline',
|
||||||
|
bind: '{record.history}',
|
||||||
|
spotRadius: 0,
|
||||||
|
fillColor: '#ddd',
|
||||||
|
lineColor: '#555',
|
||||||
|
lineWidth: 0,
|
||||||
|
chartRangeMin: 0,
|
||||||
|
chartRangeMax: 1,
|
||||||
|
tipTpl: '{y:number("0.00")*100}%',
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pbsTaskSummary',
|
||||||
|
border: false,
|
||||||
|
header: false,
|
||||||
|
subPanelModal: true,
|
||||||
|
bodyPadding: 0,
|
||||||
|
minHeight: 0,
|
||||||
|
cbind: {
|
||||||
|
datastore: '{datastore}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
Loading…
Reference in New Issue