diff --git a/www/Makefile b/www/Makefile index 9ebc155d..878a6e9d 100644 --- a/www/Makefile +++ b/www/Makefile @@ -19,6 +19,9 @@ JSSRC= \ window/ACLEdit.js \ window/DataStoreEdit.js \ dashboard/DataStoreStatistics.js \ + dashboard/LongestTasks.js \ + dashboard/RunningTasks.js \ + dashboard/TaskSummary.js \ Utils.js \ LoginView.js \ VersionInfo.js \ diff --git a/www/dashboard/LongestTasks.js b/www/dashboard/LongestTasks.js new file mode 100644 index 00000000..76238e5b --- /dev/null +++ b/www/dashboard/LongestTasks.js @@ -0,0 +1,104 @@ +Ext.define('PBS.LongestTasks', { + extend: 'Ext.grid.Panel', + alias: 'widget.pbsLongestTasks', + + title: gettext('Longest Tasks (last Month)'), + + hideHeaders: true, + rowLines: false, + + emptyText: gettext('No Tasks'), + + controller: { + xclass: 'Ext.app.ViewController', + + openTask: function(record) { + let me = this; + let view = me.getView(); + Ext.create('Proxmox.window.TaskViewer', { + upid: record.data.upid, + endtime: record.data.endtime, + }).show(); + }, + + openTaskItemDblClick: function(grid, record) { + this.openTask(record); + }, + + openTaskActionColumn: function(grid, rowIndex) { + this.openTask(grid.getStore().getAt(rowIndex)); + }, + + render_status: function(value) { + let cls = 'times-circle critical'; + if (value === 'OK') { + cls = 'check-circle good'; + } else if (value.startsWith('WARNINGS:')) { + cls = 'exclamation-circle warning'; + } else if (value === 'unknown') { + cls = 'question-circle faded'; + } + + return ``; + }, + }, + + updateTasks: function(data) { + let me = this; + me.getStore().setData(data); + }, + + listeners: { + itemdblclick: 'openTaskItemDblClick', + }, + + store: { + type: 'diff', + autoDestroy: true, + autoDestroyRstore: true, + sorters: { + property: 'duration', + direction: 'DESC', + }, + rstore: { + storeid: 'proxmox-tasks-dash', + type: 'store', + model: 'proxmox-tasks', + proxy: { + type: 'memory', + } + }, + }, + + columns: [ + { + text: gettext('Task'), + dataIndex: 'upid', + renderer: Proxmox.Utils.render_upid, + flex: 1, + }, + { + text: gettext('Duration'), + dataIndex: 'duration', + renderer: Proxmox.Utils.format_duration_human, + }, + { + text: gettext('Status'), + align: 'center', + width: 40, + dataIndex: 'status', + renderer: 'render_status', + }, + { + xtype: 'actioncolumn', + width: 40, + items: [ + { + iconCls: 'fa fa-chevron-right', + tooltip: gettext('Open Task'), + handler: 'openTaskActionColumn', + }, + ], + }, + ], +}); diff --git a/www/dashboard/RunningTasks.js b/www/dashboard/RunningTasks.js new file mode 100644 index 00000000..9b53d1be --- /dev/null +++ b/www/dashboard/RunningTasks.js @@ -0,0 +1,108 @@ +Ext.define('PBS.RunningTasks', { + extend: 'Ext.grid.Panel', + alias: 'widget.pbsRunningTasks', + + title: gettext('Running Tasks'), + emptyText: gettext('No running tasks'), + + hideHeaders: true, + rowLines: false, + + controller: { + xclass: 'Ext.app.ViewController', + + openTask: function(record) { + let me = this; + let view = me.getView(); + Ext.create('Proxmox.window.TaskViewer', { + upid: record.data.upid, + endtime: record.data.endtime, + }).show(); + }, + + openTaskItemDblClick: function(grid, record) { + this.openTask(record); + }, + + openTaskActionColumn: function(grid, rowIndex) { + this.openTask(grid.getStore().getAt(rowIndex)); + }, + + render_status: function(value) { + let cls = 'times-circle critical'; + if (value === 'OK') { + cls = 'check-circle good'; + } else if (value.startsWith('WARNINGS:')) { + cls = 'exclamation-circle warning'; + } else if (value === 'unknown') { + cls = 'question-circle faded'; + } + + return ``; + }, + }, + + updateTasks: function(data) { + let me = this; + me.getStore().setData(data); + }, + + listeners: { + itemdblclick: 'openTaskItemDblClick', + }, + + store: { + type: 'diff', + autoDestroy: true, + autoDestroyRstore: true, + sorters: 'starttime', + rstore: { + type: 'update', + autoStart: true, + interval: 3000, + storeid: 'pbs-running-tasks-dash', + model: 'proxmox-tasks', + proxy: { + type: 'proxmox', + // maybe separate api call? + url: '/api2/json/nodes/localhost/tasks?running=1' + }, + }, + }, + + columns: [ + { + text: 'Task', + dataIndex: 'upid', + renderer: Proxmox.Utils.render_upid, + flex: 2, + }, + { + text: 'Starttime', + dataIndex: 'starttime', + renderer: function(value) { + return Ext.Date.format(value, "Y-m-d H:i:s"); + }, + flex: 1, + }, + { + text: 'Duration', + dataIndex: 'duration', + renderer: function(value, md, record) { + return Proxmox.Utils.format_duration_human((Date.now() - record.data.starttime)/1000); + } + }, + { + xtype: 'actioncolumn', + width: 40, + items: [ + { + iconCls: 'fa fa-chevron-right', + tooltip: gettext('Open Task'), + handler: 'openTaskActionColumn', + }, + ], + }, + ], +}); + diff --git a/www/dashboard/TaskSummary.js b/www/dashboard/TaskSummary.js new file mode 100644 index 00000000..c51eaffb --- /dev/null +++ b/www/dashboard/TaskSummary.js @@ -0,0 +1,81 @@ +Ext.define('PBS.TaskSummary', { + extend: 'Ext.panel.Panel', + alias: 'widget.pbsTaskSummary', + + title: gettext('Task Summary (last Month)'), + + controller: { + xclass: 'Ext.app.ViewController', + + render_count: function(value, md, record, rowindex, colindex) { + let cls = 'question'; + switch (colindex) { + case 1: cls = "times-circle critical"; break; + case 2: cls = "exclamation-circle warning"; break; + case 3: cls = "check-circle good"; break; + default: break; + } + return ` ${value}`; + }, + }, + + updateTasks: function(data) { + let me = this; + data.backup.type = gettext('Backups'); + data.prune.type = gettext('Prunes'); + data.garbage_collection.type = gettext('Garbage collections'); + data.sync.type = gettext('Syncs'); + me.lookup('grid').getStore().setData([ + data.backup, + data.prune, + data.garbage_collection, + data.sync, + ]); + }, + + layout: 'fit', + bodyPadding: 15, + minHeight: 166, + + // we have to wrap the grid in a panel to get the padding right + items: [ + { + xtype: 'grid', + reference: 'grid', + hideHeaders: true, + border: false, + bodyBorder: false, + rowLines: false, + viewConfig: { + stripeRows: false, + trackOver: false, + }, + scrollable: false, + disableSelection: true, + + store: { + data: [] + }, + + columns: [ + { + dataIndex: 'type', + flex: 1, + }, + { + dataIndex: 'error', + renderer: 'render_count', + }, + { + dataIndex: 'warning', + renderer: 'render_count', + }, + { + dataIndex: 'ok', + renderer: 'render_count', + }, + ], + } + ], + +});