From faee4219576da79f7c5f56b4e28055cc6c74ed15 Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Thu, 9 Sep 2021 17:45:13 +0200 Subject: [PATCH] ui: backup job detail view: factor out to own file Signed-off-by: Thomas Lamprecht --- www/manager6/Makefile | 1 + www/manager6/dc/Backup.js | 489 ----------------------------- www/manager6/dc/BackupJobDetail.js | 488 ++++++++++++++++++++++++++++ 3 files changed, 489 insertions(+), 489 deletions(-) create mode 100644 www/manager6/dc/BackupJobDetail.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index aac780aa..7d491f57 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -124,6 +124,7 @@ JSSRC= \ dc/AuthEditLDAP.js \ dc/AuthEditOpenId.js \ dc/AuthView.js \ + dc/BackupJobDetail.js \ dc/Backup.js \ dc/Cluster.js \ dc/ClusterEdit.js \ diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js index 72420fc8..0293c099 100644 --- a/www/manager6/dc/Backup.js +++ b/www/manager6/dc/Backup.js @@ -428,495 +428,6 @@ Ext.define('PVE.dc.BackupEdit', { }, }); - -Ext.define('PVE.dc.BackupDiskTree', { - extend: 'Ext.tree.Panel', - alias: 'widget.pveBackupDiskTree', - - folderSort: true, - rootVisible: false, - - store: { - sorters: 'id', - data: {}, - }, - - tools: [ - { - type: 'expand', - tooltip: gettext('Expand All'), - callback: panel => panel.expandAll(), - }, - { - type: 'collapse', - tooltip: gettext('Collapse All'), - callback: panel => panel.collapseAll(), - }, - ], - - columns: [ - { - xtype: 'treecolumn', - text: gettext('Guest Image'), - renderer: function(value, meta, record) { - if (record.data.type) { - // guest level - let ret = value; - if (record.data.name) { - ret += " (" + record.data.name + ")"; - } - return ret; - } else { - // extJS needs unique IDs but we only want to show the volumes key from "vmid:key" - return value.split(':')[1] + " - " + record.data.name; - } - }, - dataIndex: 'id', - flex: 6, - }, - { - text: gettext('Type'), - dataIndex: 'type', - flex: 1, - }, - { - text: gettext('Backup Job'), - renderer: PVE.Utils.render_backup_status, - dataIndex: 'included', - flex: 3, - }, - ], - - reload: function() { - let me = this; - let sm = me.getSelectionModel(); - - Proxmox.Utils.API2Request({ - url: `/cluster/backup/${me.jobid}/included_volumes`, - waitMsgTarget: me, - method: 'GET', - failure: function(response, opts) { - Proxmox.Utils.setErrorMask(me, response.htmlStatus); - }, - success: function(response, opts) { - sm.deselectAll(); - me.setRootNode(response.result.data); - me.expandAll(); - }, - }); - }, - - initComponent: function() { - var me = this; - - if (!me.jobid) { - throw "no job id specified"; - } - - var sm = Ext.create('Ext.selection.TreeModel', {}); - - Ext.apply(me, { - selModel: sm, - fields: ['id', 'type', - { - type: 'string', - name: 'iconCls', - calculate: function(data) { - var txt = 'fa x-fa-tree fa-'; - if (data.leaf && !data.type) { - return txt + 'hdd-o'; - } else if (data.type === 'qemu') { - return txt + 'desktop'; - } else if (data.type === 'lxc') { - return txt + 'cube'; - } else { - return txt + 'question-circle'; - } - }, - }, - ], - header: { - items: [{ - xtype: 'textfield', - fieldLabel: gettext('Search'), - labelWidth: 50, - emptyText: 'Name, VMID, Type', - width: 200, - padding: '0 5 0 0', - enableKeyEvents: true, - listeners: { - buffer: 500, - keyup: function(field) { - let searchValue = field.getValue().toLowerCase(); - me.store.clearFilter(true); - me.store.filterBy(function(record) { - let data = {}; - if (record.data.depth === 0) { - return true; - } else if (record.data.depth === 1) { - data = record.data; - } else if (record.data.depth === 2) { - data = record.parentNode.data; - } - - for (const property of ['name', 'id', 'type']) { - if (!data[property]) { - continue; - } - let v = data[property].toString(); - if (v !== undefined) { - v = v.toLowerCase(); - if (v.includes(searchValue)) { - return true; - } - } - } - return false; - }); - }, - }, - }], - }, - }); - - me.callParent(); - - me.reload(); - }, -}); - -Ext.define('PVE.dc.BackupInfo', { - extend: 'Proxmox.panel.InputPanel', - alias: 'widget.pveBackupInfo', - - viewModel: { - data: { - retentionType: 'none', - }, - formulas: { - hasRetention: (get) => get('retentionType') !== 'none', - retentionKeepAll: (get) => get('retentionType') === 'all', - }, - }, - - padding: '5 0 5 10', - - column1: [ - { - xtype: 'displayfield', - name: 'node', - fieldLabel: gettext('Node'), - renderer: value => value || `-- ${gettext('All')} --`, - }, - { - xtype: 'displayfield', - name: 'storage', - fieldLabel: gettext('Storage'), - }, - { - xtype: 'displayfield', - name: 'dow', - fieldLabel: gettext('Day of week'), - renderer: PVE.Utils.render_backup_days_of_week, - }, - { - xtype: 'displayfield', - name: 'starttime', - fieldLabel: gettext('Start Time'), - }, - { - xtype: 'displayfield', - name: 'selMode', - fieldLabel: gettext('Selection mode'), - }, - { - xtype: 'displayfield', - name: 'pool', - fieldLabel: gettext('Pool to backup'), - }, - ], - column2: [ - { - xtype: 'displayfield', - name: 'mailnotification', - fieldLabel: gettext('Email notification'), - renderer: function(value) { - let mailto = this.up('pveBackupInfo')?.record?.mailto || 'root@localhost'; - let when = gettext('Always'); - if (value === 'failure') { - when = gettext('On failure only'); - } - return `${when} (${mailto})`; - }, - }, - { - xtype: 'displayfield', - name: 'compress', - fieldLabel: gettext('Compression'), - }, - { - xtype: 'displayfield', - name: 'mode', - fieldLabel: gettext('Mode'), - renderer: function(value) { - let msg; - switch (value) { - case 'snapshot': - msg = gettext('Snapshot'); - break; - case 'suspend': - msg = gettext('Suspend'); - break; - case 'stop': - msg = gettext('Stop'); - break; - } - return msg; - }, - }, - { - xtype: 'displayfield', - name: 'enabled', - fieldLabel: gettext('Enabled'), - renderer: v => PVE.Parser.parseBoolean(v.toString()) ? gettext('Yes') : gettext('No'), - }, - ], - - columnB: [ - { - xtype: 'label', - name: 'pruneLabel', - text: gettext('Retention Configuration') + ':', - bind: { - hidden: '{!hasRetention}', - }, - }, - { - layout: 'hbox', - border: false, - defaults: { - border: false, - layout: 'anchor', - flex: 1, - }, - items: [ - { - padding: '0 10 0 0', - defaults: { - labelWidth: 110, - }, - items: [{ - xtype: 'displayfield', - name: 'keep-all', - fieldLabel: gettext('Keep All'), - renderer: Proxmox.Utils.format_boolean, - bind: { - hidden: '{!retentionKeepAll}', - }, - }].concat( - [ - ['keep-last', gettext('Keep Last')], - ['keep-hourly', gettext('Keep Hourly')], - ].map( - name => ({ - xtype: 'displayfield', - name: name[0], - fieldLabel: name[1], - bind: { - hidden: '{!hasRetention || retentionKeepAll}', - }, - }), - ), - ), - }, - { - padding: '0 0 0 10', - defaults: { - labelWidth: 110, - }, - items: [ - ['keep-daily', gettext('Keep Daily')], - ['keep-weekly', gettext('Keep Weekly')], - ].map( - name => ({ - xtype: 'displayfield', - name: name[0], - fieldLabel: name[1], - bind: { - hidden: '{!hasRetention || retentionKeepAll}', - }, - }), - ), - }, - { - padding: '0 0 0 10', - defaults: { - labelWidth: 110, - }, - items: [ - ['keep-monthly', gettext('Keep Monthly')], - ['keep-yearly', gettext('Keep Yearly')], - ].map( - name => ({ - xtype: 'displayfield', - name: name[0], - fieldLabel: name[1], - bind: { - hidden: '{!hasRetention || retentionKeepAll}', - }, - }), - ), - }, - ], - }, - ], - - setValues: function(values) { - var me = this; - let vm = me.getViewModel(); - - Ext.iterate(values, function(fieldId, val) { - let field = me.query('[isFormField][name=' + fieldId + ']')[0]; - if (field) { - field.setValue(val); - } - }); - - if (values['prune-backups'] || values.maxfiles !== undefined) { - let keepValues; - if (values['prune-backups']) { - keepValues = values['prune-backups']; - } else if (values.maxfiles > 0) { - keepValues = { 'keep-last': values.maxfiles }; - } else { - keepValues = { 'keep-all': 1 }; - } - - vm.set('retentionType', keepValues['keep-all'] ? 'all' : 'other'); - - // set values of all keep-X fields - ['all', 'last', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'].forEach(time => { - let name = `keep-${time}`; - me.query(`[isFormField][name=${name}]`)[0]?.setValue(keepValues[name]); - }); - } else { - vm.set('retentionType', 'none'); - } - - // selection Mode depends on the presence/absence of several keys - let selModeField = me.query('[isFormField][name=selMode]')[0]; - let selMode = 'none'; - if (values.vmid) { - selMode = gettext('Include selected VMs'); - } - if (values.all) { - selMode = gettext('All'); - } - if (values.exclude) { - selMode = gettext('Exclude selected VMs'); - } - if (values.pool) { - selMode = gettext('Pool based'); - } - selModeField.setValue(selMode); - - if (!values.pool) { - let poolField = me.query('[isFormField][name=pool]')[0]; - poolField.setVisible(0); - } - }, - - initComponent: function() { - var me = this; - - if (!me.record) { - throw "no data provided"; - } - me.callParent(); - - me.setValues(me.record); - }, -}); - - -Ext.define('PVE.dc.BackedGuests', { - extend: 'Ext.grid.GridPanel', - alias: 'widget.pveBackedGuests', - - textfilter: '', - - columns: [ - { - header: gettext('Type'), - dataIndex: "type", - renderer: PVE.Utils.render_resource_type, - flex: 1, - sortable: true, - }, - { - header: gettext('VMID'), - dataIndex: 'vmid', - flex: 1, - sortable: true, - }, - { - header: gettext('Name'), - dataIndex: 'name', - flex: 2, - sortable: true, - }, - ], - - initComponent: function() { - let me = this; - - me.store.clearFilter(true); - - Ext.apply(me, { - stateful: true, - stateId: 'grid-dc-backed-guests', - tbar: [ - '->', - gettext('Search') + ':', ' ', - { - xtype: 'textfield', - width: 200, - emptyText: 'Name, VMID, Type', - enableKeyEvents: true, - listeners: { - buffer: 500, - keyup: function(field) { - let searchValue = field.getValue().toLowerCase(); - me.store.clearFilter(true); - me.store.filterBy(function(record) { - let data = record.data; - for (const property in ['name', 'id', 'type']) { - if (data[property] === null) { - continue; - } - let v = data[property].toString(); - if (v !== undefined) { - v = v.toLowerCase(); - if (v.includes(searchValue)) { - return true; - } - } - } - return false; - }); - }, - }, - }, - ], - viewConfig: { - stripeRows: true, - trackOver: false, - }, - }); - me.callParent(); - }, -}); - Ext.define('PVE.dc.BackupView', { extend: 'Ext.grid.GridPanel', diff --git a/www/manager6/dc/BackupJobDetail.js b/www/manager6/dc/BackupJobDetail.js new file mode 100644 index 00000000..b91cb1b7 --- /dev/null +++ b/www/manager6/dc/BackupJobDetail.js @@ -0,0 +1,488 @@ +Ext.define('PVE.dc.BackupDiskTree', { + extend: 'Ext.tree.Panel', + alias: 'widget.pveBackupDiskTree', + + folderSort: true, + rootVisible: false, + + store: { + sorters: 'id', + data: {}, + }, + + tools: [ + { + type: 'expand', + tooltip: gettext('Expand All'), + callback: panel => panel.expandAll(), + }, + { + type: 'collapse', + tooltip: gettext('Collapse All'), + callback: panel => panel.collapseAll(), + }, + ], + + columns: [ + { + xtype: 'treecolumn', + text: gettext('Guest Image'), + renderer: function(value, meta, record) { + if (record.data.type) { + // guest level + let ret = value; + if (record.data.name) { + ret += " (" + record.data.name + ")"; + } + return ret; + } else { + // extJS needs unique IDs but we only want to show the volumes key from "vmid:key" + return value.split(':')[1] + " - " + record.data.name; + } + }, + dataIndex: 'id', + flex: 6, + }, + { + text: gettext('Type'), + dataIndex: 'type', + flex: 1, + }, + { + text: gettext('Backup Job'), + renderer: PVE.Utils.render_backup_status, + dataIndex: 'included', + flex: 3, + }, + ], + + reload: function() { + let me = this; + let sm = me.getSelectionModel(); + + Proxmox.Utils.API2Request({ + url: `/cluster/backup/${me.jobid}/included_volumes`, + waitMsgTarget: me, + method: 'GET', + failure: function(response, opts) { + Proxmox.Utils.setErrorMask(me, response.htmlStatus); + }, + success: function(response, opts) { + sm.deselectAll(); + me.setRootNode(response.result.data); + me.expandAll(); + }, + }); + }, + + initComponent: function() { + var me = this; + + if (!me.jobid) { + throw "no job id specified"; + } + + var sm = Ext.create('Ext.selection.TreeModel', {}); + + Ext.apply(me, { + selModel: sm, + fields: ['id', 'type', + { + type: 'string', + name: 'iconCls', + calculate: function(data) { + var txt = 'fa x-fa-tree fa-'; + if (data.leaf && !data.type) { + return txt + 'hdd-o'; + } else if (data.type === 'qemu') { + return txt + 'desktop'; + } else if (data.type === 'lxc') { + return txt + 'cube'; + } else { + return txt + 'question-circle'; + } + }, + }, + ], + header: { + items: [{ + xtype: 'textfield', + fieldLabel: gettext('Search'), + labelWidth: 50, + emptyText: 'Name, VMID, Type', + width: 200, + padding: '0 5 0 0', + enableKeyEvents: true, + listeners: { + buffer: 500, + keyup: function(field) { + let searchValue = field.getValue().toLowerCase(); + me.store.clearFilter(true); + me.store.filterBy(function(record) { + let data = {}; + if (record.data.depth === 0) { + return true; + } else if (record.data.depth === 1) { + data = record.data; + } else if (record.data.depth === 2) { + data = record.parentNode.data; + } + + for (const property of ['name', 'id', 'type']) { + if (!data[property]) { + continue; + } + let v = data[property].toString(); + if (v !== undefined) { + v = v.toLowerCase(); + if (v.includes(searchValue)) { + return true; + } + } + } + return false; + }); + }, + }, + }], + }, + }); + + me.callParent(); + + me.reload(); + }, +}); + +Ext.define('PVE.dc.BackupInfo', { + extend: 'Proxmox.panel.InputPanel', + alias: 'widget.pveBackupInfo', + + viewModel: { + data: { + retentionType: 'none', + }, + formulas: { + hasRetention: (get) => get('retentionType') !== 'none', + retentionKeepAll: (get) => get('retentionType') === 'all', + }, + }, + + padding: '5 0 5 10', + + column1: [ + { + xtype: 'displayfield', + name: 'node', + fieldLabel: gettext('Node'), + renderer: value => value || `-- ${gettext('All')} --`, + }, + { + xtype: 'displayfield', + name: 'storage', + fieldLabel: gettext('Storage'), + }, + { + xtype: 'displayfield', + name: 'dow', + fieldLabel: gettext('Day of week'), + renderer: PVE.Utils.render_backup_days_of_week, + }, + { + xtype: 'displayfield', + name: 'starttime', + fieldLabel: gettext('Start Time'), + }, + { + xtype: 'displayfield', + name: 'selMode', + fieldLabel: gettext('Selection mode'), + }, + { + xtype: 'displayfield', + name: 'pool', + fieldLabel: gettext('Pool to backup'), + }, + ], + column2: [ + { + xtype: 'displayfield', + name: 'mailnotification', + fieldLabel: gettext('Email notification'), + renderer: function(value) { + let mailto = this.up('pveBackupInfo')?.record?.mailto || 'root@localhost'; + let when = gettext('Always'); + if (value === 'failure') { + when = gettext('On failure only'); + } + return `${when} (${mailto})`; + }, + }, + { + xtype: 'displayfield', + name: 'compress', + fieldLabel: gettext('Compression'), + }, + { + xtype: 'displayfield', + name: 'mode', + fieldLabel: gettext('Mode'), + renderer: function(value) { + let msg; + switch (value) { + case 'snapshot': + msg = gettext('Snapshot'); + break; + case 'suspend': + msg = gettext('Suspend'); + break; + case 'stop': + msg = gettext('Stop'); + break; + } + return msg; + }, + }, + { + xtype: 'displayfield', + name: 'enabled', + fieldLabel: gettext('Enabled'), + renderer: v => PVE.Parser.parseBoolean(v.toString()) ? gettext('Yes') : gettext('No'), + }, + ], + + columnB: [ + { + xtype: 'label', + name: 'pruneLabel', + text: gettext('Retention Configuration') + ':', + bind: { + hidden: '{!hasRetention}', + }, + }, + { + layout: 'hbox', + border: false, + defaults: { + border: false, + layout: 'anchor', + flex: 1, + }, + items: [ + { + padding: '0 10 0 0', + defaults: { + labelWidth: 110, + }, + items: [{ + xtype: 'displayfield', + name: 'keep-all', + fieldLabel: gettext('Keep All'), + renderer: Proxmox.Utils.format_boolean, + bind: { + hidden: '{!retentionKeepAll}', + }, + }].concat( + [ + ['keep-last', gettext('Keep Last')], + ['keep-hourly', gettext('Keep Hourly')], + ].map( + name => ({ + xtype: 'displayfield', + name: name[0], + fieldLabel: name[1], + bind: { + hidden: '{!hasRetention || retentionKeepAll}', + }, + }), + ), + ), + }, + { + padding: '0 0 0 10', + defaults: { + labelWidth: 110, + }, + items: [ + ['keep-daily', gettext('Keep Daily')], + ['keep-weekly', gettext('Keep Weekly')], + ].map( + name => ({ + xtype: 'displayfield', + name: name[0], + fieldLabel: name[1], + bind: { + hidden: '{!hasRetention || retentionKeepAll}', + }, + }), + ), + }, + { + padding: '0 0 0 10', + defaults: { + labelWidth: 110, + }, + items: [ + ['keep-monthly', gettext('Keep Monthly')], + ['keep-yearly', gettext('Keep Yearly')], + ].map( + name => ({ + xtype: 'displayfield', + name: name[0], + fieldLabel: name[1], + bind: { + hidden: '{!hasRetention || retentionKeepAll}', + }, + }), + ), + }, + ], + }, + ], + + setValues: function(values) { + var me = this; + let vm = me.getViewModel(); + + Ext.iterate(values, function(fieldId, val) { + let field = me.query('[isFormField][name=' + fieldId + ']')[0]; + if (field) { + field.setValue(val); + } + }); + + if (values['prune-backups'] || values.maxfiles !== undefined) { + let keepValues; + if (values['prune-backups']) { + keepValues = values['prune-backups']; + } else if (values.maxfiles > 0) { + keepValues = { 'keep-last': values.maxfiles }; + } else { + keepValues = { 'keep-all': 1 }; + } + + vm.set('retentionType', keepValues['keep-all'] ? 'all' : 'other'); + + // set values of all keep-X fields + ['all', 'last', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'].forEach(time => { + let name = `keep-${time}`; + me.query(`[isFormField][name=${name}]`)[0]?.setValue(keepValues[name]); + }); + } else { + vm.set('retentionType', 'none'); + } + + // selection Mode depends on the presence/absence of several keys + let selModeField = me.query('[isFormField][name=selMode]')[0]; + let selMode = 'none'; + if (values.vmid) { + selMode = gettext('Include selected VMs'); + } + if (values.all) { + selMode = gettext('All'); + } + if (values.exclude) { + selMode = gettext('Exclude selected VMs'); + } + if (values.pool) { + selMode = gettext('Pool based'); + } + selModeField.setValue(selMode); + + if (!values.pool) { + let poolField = me.query('[isFormField][name=pool]')[0]; + poolField.setVisible(0); + } + }, + + initComponent: function() { + var me = this; + + if (!me.record) { + throw "no data provided"; + } + me.callParent(); + + me.setValues(me.record); + }, +}); + + +Ext.define('PVE.dc.BackedGuests', { + extend: 'Ext.grid.GridPanel', + alias: 'widget.pveBackedGuests', + + stateful: true, + stateId: 'grid-dc-backed-guests', + + textfilter: '', + + columns: [ + { + header: gettext('Type'), + dataIndex: "type", + renderer: PVE.Utils.render_resource_type, + flex: 1, + sortable: true, + }, + { + header: gettext('VMID'), + dataIndex: 'vmid', + flex: 1, + sortable: true, + }, + { + header: gettext('Name'), + dataIndex: 'name', + flex: 2, + sortable: true, + }, + ], + viewConfig: { + stripeRows: true, + trackOver: false, + }, + + initComponent: function() { + let me = this; + + me.store.clearFilter(true); + + Ext.apply(me, { + tbar: [ + '->', + gettext('Search') + ':', + ' ', + { + xtype: 'textfield', + width: 200, + emptyText: 'Name, VMID, Type', + enableKeyEvents: true, + listeners: { + buffer: 500, + keyup: function(field) { + let searchValue = field.getValue().toLowerCase(); + me.store.clearFilter(true); + me.store.filterBy(function(record) { + let data = record.data; + for (const property in ['name', 'id', 'type']) { + if (data[property] === null) { + continue; + } + let v = data[property].toString(); + if (v !== undefined) { + if (v.toLowerCase().includes(searchValue)) { + return true; + } + } + } + return false; + }); + }, + }, + }, + ], + }); + me.callParent(); + }, +});