nodename: 'localhost',
storageContent: 'backup',
allowBlank: false,
- name: 'storage'
+ name: 'storage',
+ listeners: {
+ change: function(f, v) {
+ let store = f.getStore();
+ let rec = store.findRecord('storage', v, 0, false, true, true);
+ let compressionSelector = me.down('pveCompressionSelector');
+
+ if (rec && rec.data && rec.data.type === 'pbs') {
+ compressionSelector.setValue('zstd');
+ compressionSelector.setDisabled(true);
+ } else if (!compressionSelector.getEditable()) {
+ compressionSelector.setDisabled(false);
+ }
+ }
+ }
});
var store = new Ext.data.Store({
sm.deselectAll(true);
if (list) {
Ext.Array.each(list.split(','), function(vmid) {
- var rec = store.findRecord('vmid', vmid);
+ var rec = store.findRecord('vmid', vmid, 0, false, true, true);
if (rec) {
sm.select(rec, true);
}
});
+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'),
+ scope: this,
+ callback: function(panel) {
+ panel.expandAll();
+ },
+ },
+ {
+ type: 'collapse',
+ tooltip: gettext('Collapse All'),
+ scope: this,
+ callback: function(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 {
+ // volume level
+ // 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() {
+ var me = this;
+ var 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();
+ searchValue = searchValue.toLowerCase();
+
+ me.store.clearFilter(true);
+ me.store.filterBy(function(record) {
+ let match = false;
+
+ 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;
+ }
+
+ Ext.each(['name', 'id', 'type'], function(property) {
+ if (data[property] === null) {
+ return;
+ }
+
+ let v = data[property].toString();
+ if (v !== undefined) {
+ v = v.toLowerCase();
+ if (v.includes(searchValue)) {
+ match = true;
+ return;
+ }
+ }
+ });
+ return match;
+ });
+ }
+ }
+ }
+ ]},
+ });
+
+ me.callParent();
+
+ me.reload();
+ }
+});
+
+Ext.define('PVE.dc.BackupInfo', {
+ extend: 'Proxmox.panel.InputPanel',
+ alias: 'widget.pveBackupInfo',
+
+ padding: '5 0 5 10',
+
+ column1: [
+ {
+ name: 'node',
+ fieldLabel: gettext('Node'),
+ xtype: 'displayfield',
+ renderer: function (value) {
+ if (!value) {
+ return '-- ' + gettext('All') + ' --';
+ } else {
+ return value;
+ }
+ },
+ },
+ {
+ name: 'storage',
+ fieldLabel: gettext('Storage'),
+ xtype: 'displayfield',
+ },
+ {
+ name: 'dow',
+ fieldLabel: gettext('Day of week'),
+ xtype: 'displayfield',
+ renderer: PVE.Utils.render_backup_days_of_week,
+ },
+ {
+ name: 'starttime',
+ fieldLabel: gettext('Start Time'),
+ xtype: 'displayfield',
+ },
+ {
+ name: 'selMode',
+ fieldLabel: gettext('Selection mode'),
+ xtype: 'displayfield',
+ },
+ {
+ name: 'pool',
+ fieldLabel: gettext('Pool to backup'),
+ xtype: 'displayfield',
+ }
+ ],
+ column2: [
+ {
+ name: 'mailto',
+ fieldLabel: gettext('Send email to'),
+ xtype: 'displayfield',
+ },
+ {
+ name: 'mailnotification',
+ fieldLabel: gettext('Email notification'),
+ xtype: 'displayfield',
+ renderer: function (value) {
+ let msg;
+ switch (value) {
+ case 'always':
+ msg = gettext('Always');
+ break;
+ case 'failure':
+ msg = gettext('On failure only');
+ break;
+ }
+ return msg;
+ },
+ },
+ {
+ name: 'compress',
+ fieldLabel: gettext('Compression'),
+ xtype: 'displayfield',
+ },
+ {
+ name: 'mode',
+ fieldLabel: gettext('Mode'),
+ xtype: 'displayfield',
+ 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;
+ },
+ },
+ {
+ name: 'enabled',
+ fieldLabel: gettext('Enabled'),
+ xtype: 'displayfield',
+ renderer: function (value) {
+ if (PVE.Parser.parseBoolean(value.toString())) {
+ return gettext('Yes');
+ } else {
+ return gettext('No');
+ }
+ },
+ },
+ ],
+
+ setValues: function(values) {
+ var me = this;
+
+ Ext.iterate(values, function(fieldId, val) {
+ let field = me.query('[isFormField][name=' + fieldId + ']')[0];
+ if (field) {
+ field.setValue(val);
+ }
+ });
+
+ // 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();
+ searchValue = searchValue.toLowerCase();
+
+ me.store.clearFilter(true);
+ me.store.filterBy(function(record) {
+ let match = false;
+
+ Ext.each(['name', 'vmid', 'type'], function(property) {
+ if (record.data[property] == null) {
+ return;
+ }
+
+ let v = record.data[property].toString();
+ if (v !== undefined) {
+ v = v.toLowerCase();
+ if (v.includes(searchValue)) {
+ match = true;
+ return;
+ }
+ }
+ });
+ return match;
+ });
+ }
+ }
+ }
+ ],
+ viewConfig: {
+ stripeRows: true,
+ trackOver: false,
+ },
+ });
+ me.callParent();
+ },
+});
+
Ext.define('PVE.dc.BackupView', {
extend: 'Ext.grid.GridPanel',
onlineHelp: 'chapter_vzdump',
allText: '-- ' + gettext('All') + ' --',
- allExceptText: gettext('All except {0}'),
initComponent : function() {
var me = this;
}
});
+ var not_backed_store = new Ext.data.Store({
+ sorters: 'vmid',
+ proxy:{
+ type: 'proxmox',
+ url: 'api2/json/cluster/backupinfo/not_backed_up',
+ },
+ });
+
var reload = function() {
store.load();
+ not_backed_store.load({
+ callback: function(records, operation, success) {
+ if (records.length) {
+ not_backed_warning.setVisible(true);
+ not_backed_btn.setVisible(true);
+ } else {
+ not_backed_warning.setVisible(false);
+ not_backed_btn.setVisible(false);
+ }
+ },
+ });
};
var sm = Ext.create('Ext.selection.RowModel', {});
win.show();
};
+ var run_detail = function() {
+ let me = this;
+ let record = sm.getSelection()[0]
+ if (!record) {
+ return;
+ }
+ let infoview = Ext.create('PVE.dc.BackupInfo', {
+ flex: 0,
+ layout: 'fit',
+ record: record.data,
+ });
+ let disktree = Ext.create('PVE.dc.BackupDiskTree', {
+ title: gettext('Included disks'),
+ flex: 1,
+ jobid: record.data.id,
+ });
+
+ Ext.create('Ext.window.Window', {
+ modal: true,
+ width: 800,
+ height: 600,
+ stateful: true,
+ stateId: 'backup-detail-view',
+ resizable: true,
+ layout: 'fit',
+ title: gettext('Backup Details'),
+
+ items:[{
+ xtype: 'panel',
+ region: 'center',
+ layout: {
+ type: 'vbox',
+ align: 'stretch'
+ },
+ items: [infoview, disktree],
+ }]
+ }).show();
+ };
+
var run_backup_now = function(job) {
job = Ext.clone(job);
}));
};
+ var run_show_not_backed = function() {
+ var me = this;
+ var backedinfo = Ext.create('PVE.dc.BackedGuests', {
+ flex: 1,
+ layout: 'fit',
+ store: not_backed_store,
+ });
+
+ var win = Ext.create('Ext.window.Window', {
+ modal: true,
+ width: 600,
+ height: 500,
+ resizable: true,
+ layout: 'fit',
+ title: gettext('Guests without backup job'),
+
+ items:[{
+ xtype: 'panel',
+ region: 'center',
+ layout: {
+ type: 'vbox',
+ align: 'stretch'
+ },
+ items: [backedinfo],
+ }]
+ }).show();
+ };
+
var edit_btn = new Proxmox.button.Button({
text: gettext('Edit'),
disabled: true,
}
});
+ var detail_btn = new Proxmox.button.Button({
+ text: gettext('Job Detail'),
+ disabled: true,
+ tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
+ selModel: sm,
+ handler: run_detail,
+ });
+
+ var not_backed_warning = Ext.create('Ext.toolbar.TextItem', {
+ html: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'),
+ hidden: true,
+ });
+
+ var not_backed_btn = new Proxmox.button.Button({
+ text: gettext('Show'),
+ hidden: true,
+ handler: run_show_not_backed,
+ });
+
Proxmox.Utils.monStoreErrors(me, store);
Ext.apply(me, {
'-',
remove_btn,
edit_btn,
+ detail_btn,
'-',
- run_btn
+ run_btn,
+ '->',
+ not_backed_warning,
+ not_backed_btn,
],
columns: [
{
width: 200,
sortable: false,
dataIndex: 'dow',
- renderer: function(val) {
- var dows = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
- var selected = [];
- var cur = -1;
- val.split(',').forEach(function(day){
- cur++;
- var dow = (dows.indexOf(day)+6)%7;
- if (cur === dow) {
- if (selected.length === 0 || selected[selected.length-1] === 0) {
- selected.push(1);
- } else {
- selected[selected.length-1]++;
- }
- } else {
- while (cur < dow) {
- cur++;
- selected.push(0);
- }
- selected.push(1);
- }
- });
-
- cur = -1;
- var days = [];
- selected.forEach(function(item) {
- cur++;
- if (item > 2) {
- days.push(Ext.Date.dayNames[(cur+1)] + '-' + Ext.Date.dayNames[(cur+item)%7]);
- cur += item-1;
- } else if (item == 2) {
- days.push(Ext.Date.dayNames[cur+1]);
- days.push(Ext.Date.dayNames[(cur+2)%7]);
- cur++;
- } else if (item == 1) {
- days.push(Ext.Date.dayNames[(cur+1)%7]);
- }
- });
- return days.join(', ');
- }
+ renderer: PVE.Utils.render_backup_days_of_week
},
{
header: gettext('Start Time'),
flex: 1,
sortable: false,
dataIndex: 'vmid',
- renderer: function(value, metaData, record) {
- if (record.data.all) {
- if (record.data.exclude) {
- return Ext.String.format(me.allExceptText, record.data.exclude);
- }
- return me.allText;
- }
- if (record.data.vmid) {
- return record.data.vmid;
- }
-
- if (record.data.pool) {
- return "Pool '"+ record.data.pool + "'";
- }
-
- return "-";
- }
+ renderer: PVE.Utils.render_backup_selection
}
],
listeners: {