]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/Backup.js
1 Ext
.define('PVE.dc.BackupEdit', {
2 extend
: 'Proxmox.window.Edit',
3 alias
: ['widget.pveDcBackupEdit'],
5 mixins
: ['Proxmox.Mixin.CBind'],
7 defaultFocus
: undefined,
9 subject
: gettext("Backup Job"),
12 url
: '/api2/extjs/cluster/backup',
16 cbindData: function() {
21 me
.url
+= `/${me.jobid}`;
27 xclass
: 'Ext.app.ViewController',
29 onGetValues: function(values
) {
31 let isCreate
= me
.getView().isCreate
;
34 Proxmox
.Utils
.assemble_field_data(values
, { 'delete': 'node' });
39 if (!values
.id
&& isCreate
) {
40 values
.id
= 'backup-' + Ext
.data
.identifier
.Uuid
.Global
.generate().slice(0, 13);
43 let selMode
= values
.selMode
;
44 delete values
.selMode
;
46 if (selMode
=== 'all') {
50 } else if (selMode
=== 'exclude') {
52 values
.exclude
= values
.vmid
;
54 } else if (selMode
=== 'pool') {
58 if (selMode
!== 'pool') {
64 nodeChange: function(f
, value
) {
66 me
.lookup('storageSelector').setNodename(value
);
67 let vmgrid
= me
.lookup('vmgrid');
68 let store
= vmgrid
.getStore();
71 store
.filterBy(function(rec
) {
72 return !value
|| rec
.get('node') === value
;
75 let mode
= me
.lookup('modeSelector').getValue();
77 vmgrid
.selModel
.selectAll(true);
79 if (mode
=== 'pool') {
80 me
.selectPoolMembers();
84 storageChange: function(f
, v
) {
86 let rec
= f
.getStore().findRecord('storage', v
, 0, false, true, true);
87 let compressionSelector
= me
.lookup('compressionSelector');
89 if (rec
?.data
?.type
=== 'pbs') {
90 compressionSelector
.setValue('zstd');
91 compressionSelector
.setDisabled(true);
92 } else if (!compressionSelector
.getEditable()) {
93 compressionSelector
.setDisabled(false);
97 selectPoolMembers: function() {
99 let mode
= me
.lookup('modeSelector').getValue();
101 if (mode
!== 'pool') {
105 let vmgrid
= me
.lookup('vmgrid');
106 let poolid
= me
.lookup('poolSelector').getValue();
108 vmgrid
.getSelectionModel().deselectAll(true);
112 vmgrid
.getStore().filter([
119 vmgrid
.selModel
.selectAll(true);
122 modeChange: function(f
, value
, oldValue
) {
124 let vmgrid
= me
.lookup('vmgrid');
125 vmgrid
.getStore().removeFilter('poolFilter');
127 if (oldValue
=== 'all' && value
!== 'all') {
128 vmgrid
.getSelectionModel().deselectAll(true);
131 if (value
=== 'all') {
132 vmgrid
.getSelectionModel().selectAll(true);
135 if (value
=== 'pool') {
136 me
.selectPoolMembers();
140 init: function(view
) {
143 me
.lookup('modeSelector').setValue('include');
146 success: function(response
, _options
) {
147 let data
= response
.result
.data
;
150 data
.vmid
= data
.exclude
;
151 data
.selMode
= 'exclude';
152 } else if (data
.all
) {
154 data
.selMode
= 'all';
155 } else if (data
.pool
) {
156 data
.selMode
= 'pool';
157 data
.selPool
= data
.pool
;
159 data
.selMode
= 'include';
162 me
.getViewModel().set('selMode', data
.selMode
);
164 if (data
['prune-backups']) {
165 Object
.assign(data
, data
['prune-backups']);
166 delete data
['prune-backups'];
167 } else if (data
.maxfiles
!== undefined) {
168 if (data
.maxfiles
> 0) {
169 data
['keep-last'] = data
.maxfiles
;
171 data
['keep-all'] = 1;
173 delete data
.maxfiles
;
176 if (data
['notes-template']) {
177 data
['notes-template'] =
178 PVE
.Utils
.unEscapeNotesTemplate(data
['notes-template']);
181 view
.setValues(data
);
194 poolMode
: (get) => get('selMode') === 'pool',
195 disableVMSelection
: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude',
208 title
: gettext('General'),
217 onlineHelp
: 'chapter_vzdump',
220 xtype
: 'pveNodeSelector',
222 fieldLabel
: gettext('Node'),
226 emptyText
: '-- ' + gettext('All') + ' --',
228 change
: 'nodeChange',
232 xtype
: 'pveStorageSelector',
233 reference
: 'storageSelector',
234 fieldLabel
: gettext('Storage'),
236 storageContent
: 'backup',
240 change
: 'storageChange',
244 xtype
: 'pveCalendarEvent',
245 fieldLabel
: gettext('Schedule'),
250 xtype
: 'proxmoxKVComboBox',
251 reference
: 'modeSelector',
253 ['include', gettext('Include selected VMs')],
254 ['all', gettext('All')],
255 ['exclude', gettext('Exclude selected VMs')],
256 ['pool', gettext('Pool based')],
258 fieldLabel
: gettext('Selection mode'),
265 change
: 'modeChange',
269 xtype
: 'pvePoolSelector',
270 reference
: 'poolSelector',
271 fieldLabel
: gettext('Pool to backup'),
276 change
: 'selectPoolMembers',
279 hidden
: '{!poolMode}',
280 disabled
: '{!poolMode}',
287 fieldLabel
: gettext('Send email to'),
291 xtype
: 'pveEmailNotificationSelector',
292 fieldLabel
: gettext('Email'),
293 name
: 'mailnotification',
295 value
: (get) => get('isCreate') ? 'always' : '',
296 deleteEmpty
: '{!isCreate}',
300 xtype
: 'pveCompressionSelector',
301 reference
: 'compressionSelector',
302 fieldLabel
: gettext('Compression'),
305 deleteEmpty
: '{!isCreate}',
310 xtype
: 'pveBackupModeSelector',
311 fieldLabel
: gettext('Mode'),
316 xtype
: 'proxmoxcheckbox',
317 fieldLabel
: gettext('Enable'),
326 xtype
: 'proxmoxtextfield',
328 fieldLabel
: gettext('Job Comment'),
330 deleteEmpty
: '{!isCreate}',
334 'data-qtip': gettext('Description of the job'),
344 columnSelection
: ['vmid', 'node', 'status', 'name', 'type'],
346 disabled
: '{disableVMSelection}',
352 xtype
: 'proxmoxcheckbox',
353 fieldLabel
: gettext('Repeat missed'),
354 name
: 'repeat-missed',
358 deleteDefaultValue
: '{!isCreate}',
362 onGetValues: function(values
) {
363 return this.up('window').getController().onGetValues(values
);
369 xtype
: 'pveBackupJobPrunePanel',
370 title
: gettext('Retention'),
372 isCreate
: '{isCreate}',
374 keepAllDefaultForCreate
: false,
376 fallbackHintHtml
: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
380 title
: gettext('Note Template'),
386 onGetValues: function(values
) {
387 if (values
['notes-template']) {
388 values
['notes-template'] =
389 PVE
.Utils
.escapeNotesTemplate(values
['notes-template']);
396 name
: 'notes-template',
397 fieldLabel
: gettext('Backup Notes'),
401 deleteEmpty
: '{!isCreate}',
402 value
: (get) => get('isCreate') ? '{{guestname}}' : undefined,
409 'line-height': '1.5em',
411 html
: gettext('The notes are added to each backup created by this job.')
414 gettext('Possible template variables are: {0}'),
415 PVE
.Utils
.notesTemplateVars
.map(v
=> `<code>{{${v}}}</code>`).join(', '),
425 Ext
.define('PVE.dc.BackupView', {
426 extend
: 'Ext.grid.GridPanel',
428 alias
: ['widget.pveDcBackupView'],
430 onlineHelp
: 'chapter_vzdump',
432 allText
: '-- ' + gettext('All') + ' --',
434 initComponent: function() {
437 let store
= new Ext
.data
.Store({
438 model
: 'pve-cluster-backup',
441 url
: "/api2/json/cluster/backup",
445 let not_backed_store
= new Ext
.data
.Store({
449 url
: 'api2/json/cluster/backup-info/not-backed-up',
453 let noBackupJobInfoButton
;
454 let reload = function() {
456 not_backed_store
.load({
457 callback
: records
=> noBackupJobInfoButton
.setVisible(records
.length
> 0),
461 let sm
= Ext
.create('Ext.selection.RowModel', {});
463 let run_editor = function() {
464 let rec
= sm
.getSelection()[0];
469 let win
= Ext
.create('PVE.dc.BackupEdit', {
472 win
.on('destroy', reload
);
476 let run_detail = function() {
477 let record
= sm
.getSelection()[0];
481 Ext
.create('Ext.window.Window', {
484 height
: Ext
.getBody().getViewSize().height
> 1000 ? 800 : 600, // factor out as common infra?
487 title
: gettext('Backup Details'),
498 xtype
: 'pveBackupInfo',
504 xtype
: 'pveBackupDiskTree',
505 title
: gettext('Included disks'),
507 jobid
: record
.data
.id
,
515 let run_backup_now = function(job
) {
516 job
= Ext
.clone(job
);
518 let jobNode
= job
.node
;
519 // Remove properties related to scheduling
521 delete job
.starttime
;
528 delete job
['next-run'];
529 delete job
['repeat-missed'];
530 job
.all
= job
.all
=== true ? 1 : 0;
532 ['performance', 'prune-backups'].forEach(key
=> {
534 job
[key
] = PVE
.Parser
.printPropertyString(job
[key
]);
538 let allNodes
= PVE
.data
.ResourceStore
.getNodes();
539 let nodes
= allNodes
.filter(node
=> node
.status
=== 'online').map(node
=> node
.node
);
542 if (jobNode
!== undefined) {
543 if (!nodes
.includes(jobNode
)) {
544 Ext
.Msg
.alert('Error', "Node '"+ jobNode
+"' from backup job isn't online!");
549 let unkownNodes
= allNodes
.filter(node
=> node
.status
!== 'online');
550 if (unkownNodes
.length
> 0) {errors
.push(unkownNodes
.map(node
=> node
.node
+ ": " + gettext("Node is offline")));}
552 let jobTotalCount
= nodes
.length
, jobsStarted
= 0;
555 title
: gettext('Please wait...'),
558 progressText
: '0/' + jobTotalCount
,
561 let postRequest = function() {
563 Ext
.Msg
.updateProgress(jobsStarted
/ jobTotalCount
, jobsStarted
+ '/' + jobTotalCount
);
565 if (jobsStarted
=== jobTotalCount
) {
567 if (errors
.length
> 0) {
568 Ext
.Msg
.alert('Error', 'Some errors have been encountered:<br />' + errors
.join('<br />'));
573 nodes
.forEach(node
=> Proxmox
.Utils
.API2Request({
574 url
: '/nodes/' + node
+ '/vzdump',
577 failure: function(response
, opts
) {
578 errors
.push(node
+ ': ' + response
.htmlStatus
);
581 success
: postRequest
,
585 var edit_btn
= new Proxmox
.button
.Button({
586 text
: gettext('Edit'),
592 var run_btn
= new Proxmox
.button
.Button({
593 text
: gettext('Run now'),
596 handler: function() {
597 var rec
= sm
.getSelection()[0];
603 title
: gettext('Confirm'),
604 icon
: Ext
.Msg
.QUESTION
,
605 msg
: gettext('Start the selected backup job now?'),
606 buttons
: Ext
.Msg
.YESNO
,
607 callback: function(btn
) {
611 run_backup_now(rec
.data
);
617 var remove_btn
= Ext
.create('Proxmox.button.StdRemoveButton', {
619 baseurl
: '/cluster/backup',
620 callback: function() {
625 var detail_btn
= new Proxmox
.button
.Button({
626 text
: gettext('Job Detail'),
628 tooltip
: gettext('Show job details and which guests and volumes are affected by the backup job'),
633 noBackupJobInfoButton
= new Proxmox
.button
.Button({
634 text
: `${gettext('Show')}: ${gettext('Guests Without Backup Job')}`,
635 tooltip
: gettext('Some guests are not covered by any backup job.'),
636 iconCls
: 'fa fa-fw fa-exclamation-circle',
639 Ext
.create('Ext.window.Window', {
646 title
: gettext('Guests Without Backup Job'),
657 xtype
: 'pveBackedGuests',
660 store
: not_backed_store
,
669 Proxmox
.Utils
.monStoreErrors(me
, store
);
675 stateId
: 'grid-dc-backup',
681 overflowHandler
: 'scroller',
685 text
: gettext('Add'),
686 handler: function() {
687 var win
= Ext
.create('PVE.dc.BackupEdit', {});
688 win
.on('destroy', reload
);
699 noBackupJobInfoButton
,
702 xtype
: 'proxmoxButton',
704 text
: gettext('Schedule Simulator'),
706 let record
= sm
.getSelection()[0];
709 schedule
= record
.data
.schedule
;
711 Ext
.create('PVE.window.ScheduleSimulator', {
721 header
: gettext('Enabled'),
723 dataIndex
: 'enabled',
725 // TODO: switch to Proxmox.Utils.renderEnabledIcon once available
726 renderer
: enabled
=> `<i class="fa fa-${enabled ? 'check' : 'minus'}"></i>`,
730 header
: gettext('ID'),
735 header
: gettext('Node'),
739 renderer: function(value
) {
747 header
: gettext('Schedule'),
749 dataIndex
: 'schedule',
752 text
: gettext('Next Run'),
753 dataIndex
: 'next-run',
755 renderer
: PVE
.Utils
.render_next_event
,
758 header
: gettext('Storage'),
761 dataIndex
: 'storage',
764 header
: gettext('Comment'),
765 dataIndex
: 'comment',
766 renderer
: Ext
.htmlEncode
,
767 sorter
: (a
, b
) => (a
.data
.comment
|| '').localeCompare(b
.data
.comment
|| ''),
771 header
: gettext('Retention'),
772 dataIndex
: 'prune-backups',
773 renderer
: v
=> v
? PVE
.Parser
.printPropertyString(v
) : gettext('Fallback from storage config'),
777 header
: gettext('Selection'),
781 renderer
: PVE
.Utils
.render_backup_selection
,
786 itemdblclick
: run_editor
,
793 Ext
.define('pve-cluster-backup', {
794 extend
: 'Ext.data.Model',
808 { name
: 'enabled', type
: 'boolean' },
809 { name
: 'all', type
: 'boolean' },