mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-05-02 16:34:47 +00:00

we showed 'last month' even if we did not limit the api call implement that and make the number of days configurable (we have most of the code already available for that, since the base dashboard got copied from pmg and never cleaned up) Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
405 lines
8.1 KiB
JavaScript
405 lines
8.1 KiB
JavaScript
Ext.define('PBS.Dashboard', {
|
|
extend: 'Ext.panel.Panel',
|
|
xtype: 'pbsDashboard',
|
|
|
|
controller: {
|
|
xclass: 'Ext.app.ViewController',
|
|
|
|
openDashboardOptions: function() {
|
|
var me = this;
|
|
var viewModel = me.getViewModel();
|
|
Ext.create('Ext.window.Window', {
|
|
modal: true,
|
|
width: 300,
|
|
title: gettext('Dashboard Options'),
|
|
layout: {
|
|
type: 'auto',
|
|
},
|
|
items: [{
|
|
xtype: 'form',
|
|
bodyPadding: '10 10 10 10',
|
|
defaultButton: 'savebutton',
|
|
items: [{
|
|
xtype: 'proxmoxintegerfield',
|
|
itemId: 'days',
|
|
labelWidth: 100,
|
|
anchor: '100%',
|
|
allowBlank: false,
|
|
minValue: 1,
|
|
maxValue: 60,
|
|
value: viewModel.get('days'),
|
|
fieldLabel: gettext('Hours to show'),
|
|
}],
|
|
buttons: [{
|
|
text: gettext('Save'),
|
|
reference: 'savebutton',
|
|
formBind: true,
|
|
handler: function() {
|
|
var win = this.up('window');
|
|
var days = win.down('#days').getValue();
|
|
me.setDays(days, true);
|
|
win.close();
|
|
},
|
|
}],
|
|
}],
|
|
}).show();
|
|
},
|
|
|
|
setDays: function(days, setState) {
|
|
var me = this;
|
|
var viewModel = me.getViewModel();
|
|
viewModel.set('days', days);
|
|
viewModel.notify();
|
|
|
|
viewModel.getStore('tasks').reload();
|
|
|
|
if (setState) {
|
|
var sp = Ext.state.Manager.getProvider();
|
|
sp.set('dashboard-days', days);
|
|
}
|
|
},
|
|
|
|
|
|
updateSubscription: function(store, records, success) {
|
|
if (!success) { return; }
|
|
let me = this;
|
|
let subStatus = records[0].data.status === 'Active' ? 2 : 0; // 2 = all good, 1 = different leves, 0 = none
|
|
me.lookup('subscription').setSubStatus(subStatus);
|
|
},
|
|
|
|
updateUsageStats: function(store, records, success) {
|
|
if (!success) {
|
|
return;
|
|
}
|
|
if (records === undefined || records.length < 1) {
|
|
return;
|
|
}
|
|
let me = this;
|
|
let viewmodel = me.getViewModel();
|
|
|
|
let res = records[0].data;
|
|
viewmodel.set('fingerprint', res.info.fingerprint || Proxmox.Utils.unknownText);
|
|
|
|
let cpu = res.cpu,
|
|
mem = res.memory,
|
|
root = res.root;
|
|
|
|
var cpuPanel = me.lookup('cpu');
|
|
cpuPanel.updateValue(cpu);
|
|
|
|
var memPanel = me.lookup('mem');
|
|
memPanel.updateValue(mem.used / mem.total);
|
|
|
|
var hdPanel = me.lookup('root');
|
|
hdPanel.updateValue(root.used / root.total);
|
|
},
|
|
|
|
showFingerPrint: function() {
|
|
let me = this;
|
|
let vm = me.getViewModel();
|
|
let fingerprint = vm.get('fingerprint');
|
|
Ext.create('Ext.window.Window', {
|
|
modal: true,
|
|
width: 600,
|
|
title: gettext('Fingerprint'),
|
|
layout: 'form',
|
|
bodyPadding: '10 0',
|
|
items: [
|
|
{
|
|
xtype: 'textfield',
|
|
inputId: 'fingerprintField',
|
|
value: fingerprint,
|
|
editable: false,
|
|
},
|
|
],
|
|
buttons: [
|
|
{
|
|
xtype: 'button',
|
|
iconCls: 'fa fa-clipboard',
|
|
handler: function(b) {
|
|
var el = document.getElementById('fingerprintField');
|
|
el.select();
|
|
document.execCommand("copy");
|
|
},
|
|
text: gettext('Copy'),
|
|
},
|
|
{
|
|
text: gettext('Ok'),
|
|
handler: function() {
|
|
this.up('window').close();
|
|
},
|
|
},
|
|
],
|
|
}).show();
|
|
},
|
|
|
|
updateTasks: function(store, records, success) {
|
|
if (!success) return;
|
|
let me = this;
|
|
|
|
records.sort((a, b) => a.data.duration - b.data.duration);
|
|
let top10 = records.slice(-10);
|
|
me.lookup('longesttasks').updateTasks(top10);
|
|
|
|
let data = {
|
|
backup: { error: 0, warning: 0, ok: 0 },
|
|
prune: { error: 0, warning: 0, ok: 0 },
|
|
garbage_collection: { error: 0, warning: 0, ok: 0 },
|
|
sync: { error: 0, warning: 0, ok: 0 },
|
|
};
|
|
|
|
records.forEach(record => {
|
|
let type = record.data.worker_type;
|
|
if (type === 'syncjob') {
|
|
type = 'sync';
|
|
}
|
|
|
|
if (data[type] && record.data.status) {
|
|
let parsed = Proxmox.Utils.parse_task_status(record.data.status);
|
|
data[type][parsed]++;
|
|
}
|
|
});
|
|
|
|
me.lookup('tasksummary').updateTasks(data);
|
|
},
|
|
|
|
init: function(view) {
|
|
var me = this;
|
|
var sp = Ext.state.Manager.getProvider();
|
|
var days = sp.get('dashboard-days') || 30;
|
|
me.setDays(days, false);
|
|
},
|
|
},
|
|
|
|
viewModel: {
|
|
data: {
|
|
fingerprint: "",
|
|
days: 30,
|
|
},
|
|
|
|
formulas: {
|
|
disableFPButton: (get) => get('fingerprint') === "",
|
|
sinceEpoch: (get) => (Date.now()/1000 - get('days') * 24*3600).toFixed(0),
|
|
},
|
|
|
|
stores: {
|
|
usage: {
|
|
storeid: 'dash-usage',
|
|
type: 'update',
|
|
interval: 3000,
|
|
autoStart: true,
|
|
autoLoad: true,
|
|
autoDestroy: true,
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: '/api2/json/nodes/localhost/status',
|
|
},
|
|
listeners: {
|
|
load: 'updateUsageStats',
|
|
},
|
|
},
|
|
subscription: {
|
|
storeid: 'dash-subscription',
|
|
type: 'update',
|
|
interval: 10000,
|
|
autoStart: true,
|
|
autoLoad: true,
|
|
autoDestroy: true,
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: '/api2/json/nodes/localhost/subscription',
|
|
},
|
|
listeners: {
|
|
load: 'updateSubscription',
|
|
},
|
|
},
|
|
tasks: {
|
|
storeid: 'dash-tasks',
|
|
type: 'update',
|
|
interval: 15000,
|
|
autoStart: true,
|
|
autoLoad: true,
|
|
autoDestroy: true,
|
|
model: 'proxmox-tasks',
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: '/api2/json/status/tasks',
|
|
extraParams: {
|
|
since: '{sinceEpoch}',
|
|
},
|
|
},
|
|
listeners: {
|
|
load: 'updateTasks',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
title: gettext('Dashboard'),
|
|
|
|
layout: {
|
|
type: 'column',
|
|
},
|
|
|
|
bodyPadding: '20 0 0 20',
|
|
|
|
defaults: {
|
|
columnWidth: 0.49,
|
|
xtype: 'panel',
|
|
margin: '0 20 20 0',
|
|
},
|
|
|
|
tools: [
|
|
{
|
|
type: 'gear',
|
|
handler: 'openDashboardOptions',
|
|
},
|
|
],
|
|
|
|
scrollable: true,
|
|
|
|
items: [
|
|
{
|
|
height: 250,
|
|
iconCls: 'fa fa-tasks',
|
|
title: gettext('Server Resources'),
|
|
bodyPadding: '0 20 0 20',
|
|
tools: [
|
|
{
|
|
xtype: 'button',
|
|
text: gettext('Show Fingerprint'),
|
|
handler: 'showFingerPrint',
|
|
bind: {
|
|
disabled: '{disableFPButton}',
|
|
},
|
|
},
|
|
],
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'center',
|
|
},
|
|
defaults: {
|
|
xtype: 'proxmoxGauge',
|
|
spriteFontSize: '20px',
|
|
flex: 1,
|
|
},
|
|
items: [
|
|
{
|
|
title: gettext('CPU'),
|
|
reference: 'cpu',
|
|
},
|
|
{
|
|
title: gettext('Memory'),
|
|
reference: 'mem',
|
|
},
|
|
{
|
|
title: gettext('Root Disk'),
|
|
reference: 'root',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'pbsDatastoresStatistics',
|
|
height: 250,
|
|
},
|
|
{
|
|
xtype: 'pbsLongestTasks',
|
|
bind: {
|
|
title: gettext('Longest Tasks') + ' (' +
|
|
Ext.String.format(gettext('{0} days'), '{days}') + ')',
|
|
},
|
|
reference: 'longesttasks',
|
|
height: 250,
|
|
},
|
|
{
|
|
xtype: 'pbsRunningTasks',
|
|
height: 250,
|
|
},
|
|
{
|
|
bind: {
|
|
title: gettext('Task Summary') + ' (' +
|
|
Ext.String.format(gettext('{0} days'), '{days}') + ')',
|
|
},
|
|
xtype: 'pbsTaskSummary',
|
|
reference: 'tasksummary',
|
|
},
|
|
{
|
|
iconCls: 'fa fa-ticket',
|
|
title: 'Subscription',
|
|
height: 166,
|
|
reference: 'subscription',
|
|
xtype: 'pbsSubscriptionInfo',
|
|
},
|
|
],
|
|
});
|
|
|
|
Ext.define('PBS.dashboard.SubscriptionInfo', {
|
|
extend: 'Ext.panel.Panel',
|
|
xtype: 'pbsSubscriptionInfo',
|
|
|
|
style: {
|
|
cursor: 'pointer',
|
|
},
|
|
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'middle',
|
|
},
|
|
|
|
items: [
|
|
{
|
|
xtype: 'box',
|
|
itemId: 'icon',
|
|
data: {
|
|
icon: 'question-circle',
|
|
},
|
|
width: 100,
|
|
tpl: '<center><i class="fa fa-3x fa-{icon}"></i></center>',
|
|
},
|
|
{
|
|
flex: 1,
|
|
xtype: 'box',
|
|
data: {
|
|
message: gettext('Unknown'),
|
|
},
|
|
itemId: 'message',
|
|
tpl: '<center>{message}</center>',
|
|
},
|
|
],
|
|
|
|
setSubStatus: function(status) {
|
|
var me = this;
|
|
let icon = '';
|
|
let message = '';
|
|
|
|
switch (status) {
|
|
case 2:
|
|
icon = 'check good';
|
|
message = gettext('Your subscription status is valid.');
|
|
break;
|
|
case 1:
|
|
icon = 'exclamation-triangle warning';
|
|
message = gettext('Warning: Your subscription levels are not the same.');
|
|
break;
|
|
case 0:
|
|
icon = 'times-circle critical';
|
|
message = gettext('This node does not have a subscription.');
|
|
break;
|
|
default:
|
|
throw 'invalid subscription status';
|
|
}
|
|
me.getComponent('icon').update({ icon });
|
|
me.getComponent('message').update({ message });
|
|
},
|
|
|
|
listeners: {
|
|
click: {
|
|
element: 'body',
|
|
fn: function() {
|
|
var mainview = this.component.up('mainview');
|
|
mainview.getController().redirectTo('pbsSubscription');
|
|
},
|
|
},
|
|
},
|
|
});
|