ui: backup job detail view: factor out to own file

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2021-09-09 17:45:13 +02:00
parent 3071423551
commit 8d26335729
3 changed files with 489 additions and 489 deletions

View File

@ -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 \

View File

@ -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',

View File

@ -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();
},
});