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',
+ },
+ ],
+ }
+ ],
+
+});