]>
Commit | Line | Data |
---|---|---|
ad74d86c | 1 | Ext.define('PVE.dc.BackupEdit', { |
9fccc702 | 2 | extend: 'Proxmox.window.Edit', |
ad74d86c DM |
3 | alias: ['widget.pveDcBackupEdit'], |
4 | ||
d232c409 DC |
5 | mixins: ['Proxmox.Mixin.CBind'], |
6 | ||
fc03fcb8 TL |
7 | defaultFocus: undefined, |
8 | ||
d232c409 DC |
9 | subject: gettext("Backup Job"), |
10 | bodyPadding: 0, | |
ad74d86c | 11 | |
d232c409 DC |
12 | url: '/api2/extjs/cluster/backup', |
13 | method: 'POST', | |
14 | isCreate: true, | |
ad74d86c | 15 | |
d232c409 DC |
16 | cbindData: function() { |
17 | let me = this; | |
18 | if (me.jobid) { | |
19 | me.isCreate = false; | |
20 | me.method = 'PUT'; | |
21 | me.url += `/${me.jobid}`; | |
12809bba | 22 | } |
d232c409 DC |
23 | return {}; |
24 | }, | |
12809bba | 25 | |
d232c409 DC |
26 | controller: { |
27 | xclass: 'Ext.app.ViewController', | |
28 | ||
29 | onGetValues: function(values) { | |
30 | let me = this; | |
31 | let isCreate = me.getView().isCreate; | |
32 | if (!values.node) { | |
33 | if (!isCreate) { | |
34 | Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' }); | |
35 | } | |
36 | delete values.node; | |
37 | } | |
38 | ||
39 | if (!values.id && isCreate) { | |
40 | values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13); | |
41 | } | |
42 | ||
43 | let selMode = values.selMode; | |
44 | delete values.selMode; | |
45 | ||
46 | if (selMode === 'all') { | |
47 | values.all = 1; | |
48 | values.exclude = ''; | |
49 | delete values.vmid; | |
50 | } else if (selMode === 'exclude') { | |
51 | values.all = 1; | |
52 | values.exclude = values.vmid; | |
53 | delete values.vmid; | |
54 | } else if (selMode === 'pool') { | |
55 | delete values.vmid; | |
56 | } | |
ad74d86c | 57 | |
d232c409 DC |
58 | if (selMode !== 'pool') { |
59 | delete values.pool; | |
60 | } | |
61 | return values; | |
62 | }, | |
63 | ||
64 | nodeChange: function(f, value) { | |
65 | let me = this; | |
66 | me.lookup('storageSelector').setNodename(value); | |
67 | let vmgrid = me.lookup('vmgrid'); | |
68 | let store = vmgrid.getStore(); | |
69 | ||
70 | store.clearFilter(); | |
71 | store.filterBy(function(rec) { | |
72 | return !value || rec.get('node') === value; | |
73 | }); | |
ad74d86c | 74 | |
d232c409 DC |
75 | let mode = me.lookup('modeSelector').getValue(); |
76 | if (mode === 'all') { | |
77 | vmgrid.selModel.selectAll(true); | |
78 | } | |
79 | if (mode === 'pool') { | |
80 | me.selectPoolMembers(); | |
81 | } | |
82 | }, | |
83 | ||
84 | storageChange: function(f, v) { | |
85 | let me = this; | |
86 | let rec = f.getStore().findRecord('storage', v, 0, false, true, true); | |
87 | let compressionSelector = me.lookup('compressionSelector'); | |
88 | ||
89 | if (rec?.data?.type === 'pbs') { | |
90 | compressionSelector.setValue('zstd'); | |
91 | compressionSelector.setDisabled(true); | |
92 | } else if (!compressionSelector.getEditable()) { | |
93 | compressionSelector.setDisabled(false); | |
94 | } | |
95 | }, | |
96 | ||
97 | selectPoolMembers: function() { | |
98 | let me = this; | |
25f7446c DC |
99 | let mode = me.lookup('modeSelector').getValue(); |
100 | ||
101 | if (mode !== 'pool') { | |
102 | return; | |
103 | } | |
104 | ||
d232c409 DC |
105 | let vmgrid = me.lookup('vmgrid'); |
106 | let poolid = me.lookup('poolSelector').getValue(); | |
107 | ||
108 | vmgrid.getSelectionModel().deselectAll(true); | |
ab648869 TM |
109 | if (!poolid) { |
110 | return; | |
111 | } | |
e440f8a4 | 112 | vmgrid.getStore().filter([ |
ab648869 TM |
113 | { |
114 | id: 'poolFilter', | |
115 | property: 'pool', | |
f6710aac TL |
116 | value: poolid, |
117 | }, | |
ab648869 | 118 | ]); |
e440f8a4 | 119 | vmgrid.selModel.selectAll(true); |
d232c409 | 120 | }, |
ad74d86c | 121 | |
d232c409 DC |
122 | modeChange: function(f, value, oldValue) { |
123 | let me = this; | |
124 | let vmgrid = me.lookup('vmgrid'); | |
125 | vmgrid.getStore().removeFilter('poolFilter'); | |
ab648869 | 126 | |
d232c409 DC |
127 | if (oldValue === 'all' && value !== 'all') { |
128 | vmgrid.getSelectionModel().deselectAll(true); | |
ab648869 TM |
129 | } |
130 | ||
ad74d86c | 131 | if (value === 'all') { |
d232c409 | 132 | vmgrid.getSelectionModel().selectAll(true); |
ad74d86c | 133 | } |
ab648869 TM |
134 | |
135 | if (value === 'pool') { | |
d232c409 DC |
136 | me.selectPoolMembers(); |
137 | } | |
138 | }, | |
139 | ||
140 | init: function(view) { | |
141 | let me = this; | |
142 | if (view.isCreate) { | |
143 | me.lookup('modeSelector').setValue('include'); | |
ab648869 | 144 | } else { |
d232c409 DC |
145 | view.load({ |
146 | success: function(response, _options) { | |
147 | let data = response.result.data; | |
148 | ||
149 | if (data.exclude) { | |
150 | data.vmid = data.exclude; | |
151 | data.selMode = 'exclude'; | |
152 | } else if (data.all) { | |
153 | data.vmid = ''; | |
154 | data.selMode = 'all'; | |
155 | } else if (data.pool) { | |
156 | data.selMode = 'pool'; | |
157 | data.selPool = data.pool; | |
158 | } else { | |
159 | data.selMode = 'include'; | |
160 | } | |
161 | ||
162 | me.getViewModel().set('selMode', data.selMode); | |
163 | ||
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; | |
170 | } else { | |
171 | data['keep-all'] = 1; | |
172 | } | |
173 | delete data.maxfiles; | |
174 | } | |
175 | ||
176 | if (data['notes-template']) { | |
177 | data['notes-template'] = | |
178 | PVE.Utils.unEscapeNotesTemplate(data['notes-template']); | |
179 | } | |
180 | ||
181 | view.setValues(data); | |
182 | }, | |
183 | }); | |
ad74d86c | 184 | } |
d232c409 DC |
185 | }, |
186 | }, | |
60e049c2 | 187 | |
d232c409 DC |
188 | viewModel: { |
189 | data: { | |
190 | selMode: 'include', | |
191 | }, | |
192 | ||
193 | formulas: { | |
194 | poolMode: (get) => get('selMode') === 'pool', | |
195 | disableVMSelection: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude', | |
196 | }, | |
197 | }, | |
198 | ||
199 | items: [ | |
200 | { | |
201 | xtype: 'tabpanel', | |
202 | region: 'center', | |
203 | layout: 'fit', | |
204 | bodyPadding: 10, | |
759c752c FE |
205 | items: [ |
206 | { | |
d232c409 DC |
207 | xtype: 'container', |
208 | title: gettext('General'), | |
759c752c | 209 | region: 'center', |
d232c409 DC |
210 | layout: { |
211 | type: 'vbox', | |
212 | align: 'stretch', | |
213 | }, | |
759c752c | 214 | items: [ |
34721757 FE |
215 | { |
216 | xtype: 'inputpanel', | |
d232c409 DC |
217 | onlineHelp: 'chapter_vzdump', |
218 | column1: [ | |
219 | { | |
220 | xtype: 'pveNodeSelector', | |
221 | name: 'node', | |
222 | fieldLabel: gettext('Node'), | |
223 | allowBlank: true, | |
224 | editable: true, | |
225 | autoSelect: false, | |
226 | emptyText: '-- ' + gettext('All') + ' --', | |
227 | listeners: { | |
228 | change: 'nodeChange', | |
229 | }, | |
230 | }, | |
231 | { | |
232 | xtype: 'pveStorageSelector', | |
233 | reference: 'storageSelector', | |
234 | fieldLabel: gettext('Storage'), | |
235 | clusterView: true, | |
236 | storageContent: 'backup', | |
237 | allowBlank: false, | |
238 | name: 'storage', | |
239 | listeners: { | |
240 | change: 'storageChange', | |
241 | }, | |
242 | }, | |
34721757 | 243 | { |
d232c409 DC |
244 | xtype: 'pveCalendarEvent', |
245 | fieldLabel: gettext('Schedule'), | |
246 | allowBlank: false, | |
247 | name: 'schedule', | |
34721757 FE |
248 | }, |
249 | { | |
d232c409 DC |
250 | xtype: 'proxmoxKVComboBox', |
251 | reference: 'modeSelector', | |
252 | comboItems: [ | |
253 | ['include', gettext('Include selected VMs')], | |
254 | ['all', gettext('All')], | |
255 | ['exclude', gettext('Exclude selected VMs')], | |
256 | ['pool', gettext('Pool based')], | |
257 | ], | |
258 | fieldLabel: gettext('Selection mode'), | |
259 | name: 'selMode', | |
260 | value: '', | |
261 | bind: { | |
262 | value: '{selMode}', | |
263 | }, | |
264 | listeners: { | |
265 | change: 'modeChange', | |
266 | }, | |
267 | }, | |
268 | { | |
269 | xtype: 'pvePoolSelector', | |
270 | reference: 'poolSelector', | |
271 | fieldLabel: gettext('Pool to backup'), | |
272 | hidden: true, | |
273 | allowBlank: false, | |
274 | name: 'pool', | |
275 | listeners: { | |
276 | change: 'selectPoolMembers', | |
277 | }, | |
278 | bind: { | |
279 | hidden: '{!poolMode}', | |
280 | disabled: '{!poolMode}', | |
34721757 | 281 | }, |
34721757 FE |
282 | }, |
283 | ], | |
d232c409 DC |
284 | column2: [ |
285 | { | |
286 | xtype: 'textfield', | |
287 | fieldLabel: gettext('Send email to'), | |
288 | name: 'mailto', | |
289 | }, | |
290 | { | |
291 | xtype: 'pveEmailNotificationSelector', | |
292 | fieldLabel: gettext('Email'), | |
293 | name: 'mailnotification', | |
294 | cbind: { | |
295 | value: (get) => get('isCreate') ? 'always' : '', | |
296 | deleteEmpty: '{!isCreate}', | |
297 | }, | |
298 | }, | |
299 | { | |
300 | xtype: 'pveCompressionSelector', | |
301 | reference: 'compressionSelector', | |
302 | fieldLabel: gettext('Compression'), | |
303 | name: 'compress', | |
304 | cbind: { | |
305 | deleteEmpty: '{!isCreate}', | |
306 | }, | |
307 | value: 'zstd', | |
308 | }, | |
309 | { | |
310 | xtype: 'pveBackupModeSelector', | |
311 | fieldLabel: gettext('Mode'), | |
312 | value: 'snapshot', | |
313 | name: 'mode', | |
314 | }, | |
315 | { | |
316 | xtype: 'proxmoxcheckbox', | |
317 | fieldLabel: gettext('Enable'), | |
318 | name: 'enabled', | |
319 | uncheckedValue: 0, | |
320 | defaultValue: 1, | |
321 | checked: true, | |
322 | }, | |
323 | ], | |
324 | columnB: [ | |
325 | { | |
326 | xtype: 'proxmoxtextfield', | |
327 | name: 'comment', | |
328 | fieldLabel: gettext('Job Comment'), | |
329 | cbind: { | |
330 | deleteEmpty: '{!isCreate}', | |
331 | }, | |
332 | autoEl: { | |
333 | tag: 'div', | |
334 | 'data-qtip': gettext('Description of the job'), | |
335 | }, | |
336 | }, | |
337 | { | |
338 | xtype: 'vmselector', | |
339 | reference: 'vmgrid', | |
340 | height: 300, | |
341 | name: 'vmid', | |
342 | disabled: true, | |
343 | allowBlank: false, | |
344 | columnSelection: ['vmid', 'node', 'status', 'name', 'type'], | |
345 | bind: { | |
346 | disabled: '{disableVMSelection}', | |
347 | }, | |
348 | }, | |
349 | ], | |
350 | advancedColumn1: [ | |
351 | { | |
352 | xtype: 'proxmoxcheckbox', | |
353 | fieldLabel: gettext('Repeat missed'), | |
354 | name: 'repeat-missed', | |
355 | uncheckedValue: 0, | |
356 | defaultValue: 0, | |
357 | cbind: { | |
358 | deleteDefaultValue: '{!isCreate}', | |
359 | }, | |
360 | }, | |
361 | ], | |
362 | onGetValues: function(values) { | |
363 | return this.up('window').getController().onGetValues(values); | |
364 | }, | |
34721757 | 365 | }, |
759c752c FE |
366 | ], |
367 | }, | |
d232c409 DC |
368 | { |
369 | xtype: 'pveBackupJobPrunePanel', | |
370 | title: gettext('Retention'), | |
371 | cbind: { | |
372 | isCreate: '{isCreate}', | |
373 | }, | |
374 | keepAllDefaultForCreate: false, | |
375 | showPBSHint: false, | |
376 | fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'), | |
377 | }, | |
378 | { | |
379 | xtype: 'inputpanel', | |
380 | title: gettext('Note Template'), | |
381 | region: 'center', | |
382 | layout: { | |
383 | type: 'vbox', | |
384 | align: 'stretch', | |
385 | }, | |
386 | onGetValues: function(values) { | |
387 | if (values['notes-template']) { | |
388 | values['notes-template'] = | |
389 | PVE.Utils.escapeNotesTemplate(values['notes-template']); | |
759c752c | 390 | } |
d232c409 DC |
391 | return values; |
392 | }, | |
393 | items: [ | |
394 | { | |
395 | xtype: 'textarea', | |
396 | name: 'notes-template', | |
397 | fieldLabel: gettext('Backup Notes'), | |
398 | height: 100, | |
399 | maxLength: 512, | |
400 | cbind: { | |
401 | deleteEmpty: '{!isCreate}', | |
402 | value: (get) => get('isCreate') ? '{{guestname}}' : undefined, | |
403 | }, | |
404 | }, | |
405 | { | |
406 | xtype: 'box', | |
407 | style: { | |
408 | margin: '8px 0px', | |
409 | 'line-height': '1.5em', | |
410 | }, | |
411 | html: gettext('The notes are added to each backup created by this job.') | |
412 | + '<br>' | |
413 | + Ext.String.format( | |
414 | gettext('Possible template variables are: {0}'), | |
415 | PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '), | |
416 | ), | |
417 | }, | |
418 | ], | |
12809bba | 419 | }, |
d232c409 DC |
420 | ], |
421 | }, | |
422 | ], | |
ad74d86c DM |
423 | }); |
424 | ||
ad74d86c DM |
425 | Ext.define('PVE.dc.BackupView', { |
426 | extend: 'Ext.grid.GridPanel', | |
427 | ||
428 | alias: ['widget.pveDcBackupView'], | |
429 | ||
ba93a9c6 DC |
430 | onlineHelp: 'chapter_vzdump', |
431 | ||
ad74d86c | 432 | allText: '-- ' + gettext('All') + ' --', |
ad74d86c | 433 | |
8058410f | 434 | initComponent: function() { |
12809bba | 435 | let me = this; |
ad74d86c | 436 | |
12809bba | 437 | let store = new Ext.data.Store({ |
ad74d86c DM |
438 | model: 'pve-cluster-backup', |
439 | proxy: { | |
12809bba | 440 | type: 'proxmox', |
f6710aac TL |
441 | url: "/api2/json/cluster/backup", |
442 | }, | |
ad74d86c DM |
443 | }); |
444 | ||
12809bba | 445 | let not_backed_store = new Ext.data.Store({ |
7d2fac4a | 446 | sorters: 'vmid', |
8058410f | 447 | proxy: { |
7d2fac4a | 448 | type: 'proxmox', |
1a2e0e23 | 449 | url: 'api2/json/cluster/backup-info/not-backed-up', |
7d2fac4a AL |
450 | }, |
451 | }); | |
452 | ||
315fecea | 453 | let noBackupJobInfoButton; |
12809bba | 454 | let reload = function() { |
ad74d86c | 455 | store.load(); |
7d2fac4a | 456 | not_backed_store.load({ |
315fecea | 457 | callback: records => noBackupJobInfoButton.setVisible(records.length > 0), |
7d2fac4a | 458 | }); |
ad74d86c DM |
459 | }; |
460 | ||
12809bba | 461 | let sm = Ext.create('Ext.selection.RowModel', {}); |
ad74d86c | 462 | |
12809bba TL |
463 | let run_editor = function() { |
464 | let rec = sm.getSelection()[0]; | |
ad74d86c DM |
465 | if (!rec) { |
466 | return; | |
467 | } | |
468 | ||
12809bba | 469 | let win = Ext.create('PVE.dc.BackupEdit', { |
f6710aac | 470 | jobid: rec.data.id, |
43b2494b SR |
471 | }); |
472 | win.on('destroy', reload); | |
473 | win.show(); | |
ad74d86c DM |
474 | }; |
475 | ||
12809bba | 476 | let run_detail = function() { |
8058410f | 477 | let record = sm.getSelection()[0]; |
01ad47af AL |
478 | if (!record) { |
479 | return; | |
480 | } | |
386c9ce5 | 481 | Ext.create('Ext.window.Window', { |
01ad47af | 482 | modal: true, |
386c9ce5 | 483 | width: 800, |
a4240b80 | 484 | height: Ext.getBody().getViewSize().height > 1000 ? 800 : 600, // factor out as common infra? |
01ad47af AL |
485 | resizable: true, |
486 | layout: 'fit', | |
487 | title: gettext('Backup Details'), | |
12809bba TL |
488 | items: [ |
489 | { | |
490 | xtype: 'panel', | |
491 | region: 'center', | |
492 | layout: { | |
493 | type: 'vbox', | |
494 | align: 'stretch', | |
495 | }, | |
496 | items: [ | |
497 | { | |
498 | xtype: 'pveBackupInfo', | |
499 | flex: 0, | |
500 | layout: 'fit', | |
501 | record: record.data, | |
502 | }, | |
503 | { | |
504 | xtype: 'pveBackupDiskTree', | |
505 | title: gettext('Included disks'), | |
506 | flex: 1, | |
507 | jobid: record.data.id, | |
508 | }, | |
509 | ], | |
01ad47af | 510 | }, |
12809bba | 511 | ], |
01ad47af AL |
512 | }).show(); |
513 | }; | |
514 | ||
12809bba | 515 | let run_backup_now = function(job) { |
389d3cf1 SR |
516 | job = Ext.clone(job); |
517 | ||
a0fecb88 | 518 | let jobNode = job.node; |
389d3cf1 SR |
519 | // Remove properties related to scheduling |
520 | delete job.enabled; | |
521 | delete job.starttime; | |
522 | delete job.dow; | |
523 | delete job.id; | |
20d15804 DC |
524 | delete job.schedule; |
525 | delete job.type; | |
389d3cf1 | 526 | delete job.node; |
1a8bd901 | 527 | delete job.comment; |
31de6834 | 528 | delete job['next-run']; |
9cd2e638 | 529 | delete job['repeat-missed']; |
389d3cf1 SR |
530 | job.all = job.all === true ? 1 : 0; |
531 | ||
93880785 FE |
532 | ['performance', 'prune-backups'].forEach(key => { |
533 | if (job[key]) { | |
534 | job[key] = PVE.Parser.printPropertyString(job[key]); | |
535 | } | |
536 | }); | |
3e70f3c5 | 537 | |
a0fecb88 TL |
538 | let allNodes = PVE.data.ResourceStore.getNodes(); |
539 | let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node); | |
540 | let errors = []; | |
541 | ||
542 | if (jobNode !== undefined) { | |
543 | if (!nodes.includes(jobNode)) { | |
544 | Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!"); | |
545 | return; | |
546 | } | |
8058410f | 547 | nodes = [jobNode]; |
a0fecb88 TL |
548 | } else { |
549 | let unkownNodes = allNodes.filter(node => node.status !== 'online'); | |
12809bba | 550 | if (unkownNodes.length > 0) {errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));} |
a0fecb88 TL |
551 | } |
552 | let jobTotalCount = nodes.length, jobsStarted = 0; | |
389d3cf1 SR |
553 | |
554 | Ext.Msg.show({ | |
555 | title: gettext('Please wait...'), | |
556 | closable: false, | |
a0fecb88 TL |
557 | progress: true, |
558 | progressText: '0/' + jobTotalCount, | |
389d3cf1 | 559 | }); |
389d3cf1 | 560 | |
8058410f | 561 | let postRequest = function() { |
a0fecb88 TL |
562 | jobsStarted++; |
563 | Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount); | |
389d3cf1 | 564 | |
12809bba | 565 | if (jobsStarted === jobTotalCount) { |
389d3cf1 | 566 | Ext.Msg.hide(); |
a0fecb88 TL |
567 | if (errors.length > 0) { |
568 | Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />')); | |
389d3cf1 SR |
569 | } |
570 | } | |
a0fecb88 TL |
571 | }; |
572 | ||
573 | nodes.forEach(node => Proxmox.Utils.API2Request({ | |
574 | url: '/nodes/' + node + '/vzdump', | |
575 | method: 'POST', | |
576 | params: job, | |
8058410f | 577 | failure: function(response, opts) { |
a0fecb88 TL |
578 | errors.push(node + ': ' + response.htmlStatus); |
579 | postRequest(); | |
580 | }, | |
f6710aac | 581 | success: postRequest, |
a0fecb88 TL |
582 | })); |
583 | }; | |
389d3cf1 | 584 | |
5720fafa | 585 | var edit_btn = new Proxmox.button.Button({ |
ad74d86c DM |
586 | text: gettext('Edit'), |
587 | disabled: true, | |
588 | selModel: sm, | |
f6710aac | 589 | handler: run_editor, |
ad74d86c DM |
590 | }); |
591 | ||
389d3cf1 SR |
592 | var run_btn = new Proxmox.button.Button({ |
593 | text: gettext('Run now'), | |
594 | disabled: true, | |
595 | selModel: sm, | |
596 | handler: function() { | |
597 | var rec = sm.getSelection()[0]; | |
598 | if (!rec) { | |
599 | return; | |
600 | } | |
601 | ||
602 | Ext.Msg.show({ | |
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) { | |
608 | if (btn !== 'yes') { | |
609 | return; | |
610 | } | |
611 | run_backup_now(rec.data); | |
f6710aac | 612 | }, |
389d3cf1 | 613 | }); |
f6710aac | 614 | }, |
389d3cf1 SR |
615 | }); |
616 | ||
3b1ca3ff | 617 | var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', { |
ad74d86c | 618 | selModel: sm, |
3b1ca3ff DC |
619 | baseurl: '/cluster/backup', |
620 | callback: function() { | |
621 | reload(); | |
f6710aac | 622 | }, |
ad74d86c DM |
623 | }); |
624 | ||
01ad47af | 625 | var detail_btn = new Proxmox.button.Button({ |
393b74ff | 626 | text: gettext('Job Detail'), |
01ad47af AL |
627 | disabled: true, |
628 | tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'), | |
629 | selModel: sm, | |
630 | handler: run_detail, | |
631 | }); | |
632 | ||
12809bba | 633 | noBackupJobInfoButton = new Proxmox.button.Button({ |
315fecea TL |
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', | |
7d2fac4a | 637 | hidden: true, |
38e79438 TL |
638 | handler: () => { |
639 | Ext.create('Ext.window.Window', { | |
640 | autoShow: true, | |
641 | modal: true, | |
642 | width: 600, | |
643 | height: 500, | |
644 | resizable: true, | |
645 | layout: 'fit', | |
646 | title: gettext('Guests Without Backup Job'), | |
647 | items: [ | |
648 | { | |
649 | xtype: 'panel', | |
650 | region: 'center', | |
651 | layout: { | |
652 | type: 'vbox', | |
653 | align: 'stretch', | |
654 | }, | |
655 | items: [ | |
656 | { | |
657 | xtype: 'pveBackedGuests', | |
658 | flex: 1, | |
659 | layout: 'fit', | |
660 | store: not_backed_store, | |
661 | }, | |
662 | ], | |
663 | }, | |
664 | ], | |
665 | }); | |
666 | }, | |
7d2fac4a AL |
667 | }); |
668 | ||
e7ade592 | 669 | Proxmox.Utils.monStoreErrors(me, store); |
ad74d86c DM |
670 | |
671 | Ext.apply(me, { | |
672 | store: store, | |
673 | selModel: sm, | |
c4bb9405 DC |
674 | stateful: true, |
675 | stateId: 'grid-dc-backup', | |
ad74d86c | 676 | viewConfig: { |
f6710aac | 677 | trackOver: false, |
ad74d86c | 678 | }, |
cc911f63 DC |
679 | dockedItems: [{ |
680 | xtype: 'toolbar', | |
681 | overflowHandler: 'scroller', | |
682 | dock: 'top', | |
683 | items: [ | |
684 | { | |
685 | text: gettext('Add'), | |
686 | handler: function() { | |
687 | var win = Ext.create('PVE.dc.BackupEdit', {}); | |
688 | win.on('destroy', reload); | |
689 | win.show(); | |
690 | }, | |
f6710aac | 691 | }, |
cc911f63 DC |
692 | '-', |
693 | remove_btn, | |
694 | edit_btn, | |
695 | detail_btn, | |
696 | '-', | |
697 | run_btn, | |
698 | '->', | |
699 | noBackupJobInfoButton, | |
700 | '-', | |
701 | { | |
702 | xtype: 'proxmoxButton', | |
703 | selModel: null, | |
704 | text: gettext('Schedule Simulator'), | |
705 | handler: () => { | |
706 | let record = sm.getSelection()[0]; | |
707 | let schedule; | |
708 | if (record) { | |
709 | schedule = record.data.schedule; | |
710 | } | |
711 | Ext.create('PVE.window.ScheduleSimulator', { | |
712 | autoShow: true, | |
713 | schedule, | |
714 | }); | |
715 | }, | |
4d6215bc | 716 | }, |
cc911f63 DC |
717 | ], |
718 | }], | |
ad74d86c | 719 | columns: [ |
cfdc7ada EK |
720 | { |
721 | header: gettext('Enabled'), | |
ae9e2161 | 722 | width: 80, |
cfdc7ada | 723 | dataIndex: 'enabled', |
b98ffc0d | 724 | align: 'center', |
de422b3e TL |
725 | // TODO: switch to Proxmox.Utils.renderEnabledIcon once available |
726 | renderer: enabled => `<i class="fa fa-${enabled ? 'check' : 'minus'}"></i>`, | |
cfdc7ada EK |
727 | sortable: true, |
728 | }, | |
20d15804 DC |
729 | { |
730 | header: gettext('ID'), | |
731 | dataIndex: 'id', | |
d9e05d34 | 732 | hidden: true, |
20d15804 | 733 | }, |
ad74d86c DM |
734 | { |
735 | header: gettext('Node'), | |
736 | width: 100, | |
737 | sortable: true, | |
738 | dataIndex: 'node', | |
739 | renderer: function(value) { | |
740 | if (value) { | |
741 | return value; | |
742 | } | |
743 | return me.allText; | |
f6710aac | 744 | }, |
ad74d86c DM |
745 | }, |
746 | { | |
20d15804 DC |
747 | header: gettext('Schedule'), |
748 | width: 150, | |
749 | dataIndex: 'schedule', | |
ad74d86c | 750 | }, |
8aeb7146 TL |
751 | { |
752 | text: gettext('Next Run'), | |
753 | dataIndex: 'next-run', | |
754 | width: 150, | |
755 | renderer: PVE.Utils.render_next_event, | |
756 | }, | |
ad74d86c DM |
757 | { |
758 | header: gettext('Storage'), | |
759 | width: 100, | |
760 | sortable: true, | |
f6710aac | 761 | dataIndex: 'storage', |
ad74d86c DM |
762 | }, |
763 | { | |
d9e05d34 DC |
764 | header: gettext('Comment'), |
765 | dataIndex: 'comment', | |
766 | renderer: Ext.htmlEncode, | |
611ef475 | 767 | sorter: (a, b) => (a.data.comment || '').localeCompare(b.data.comment || ''), |
ad74d86c | 768 | flex: 1, |
d9e05d34 DC |
769 | }, |
770 | { | |
fcb267e3 TL |
771 | header: gettext('Retention'), |
772 | dataIndex: 'prune-backups', | |
773 | renderer: v => v ? PVE.Parser.printPropertyString(v) : gettext('Fallback from storage config'), | |
d9e05d34 | 774 | flex: 2, |
fcb267e3 TL |
775 | }, |
776 | { | |
777 | header: gettext('Selection'), | |
778 | flex: 4, | |
ad74d86c DM |
779 | sortable: false, |
780 | dataIndex: 'vmid', | |
f6710aac TL |
781 | renderer: PVE.Utils.render_backup_selection, |
782 | }, | |
ad74d86c DM |
783 | ], |
784 | listeners: { | |
c0b3df6e | 785 | activate: reload, |
f6710aac TL |
786 | itemdblclick: run_editor, |
787 | }, | |
ad74d86c | 788 | }); |
60e049c2 | 789 | |
ad74d86c | 790 | me.callParent(); |
f6710aac | 791 | }, |
ad74d86c | 792 | }, function() { |
ad74d86c DM |
793 | Ext.define('pve-cluster-backup', { |
794 | extend: 'Ext.data.Model', | |
60e049c2 | 795 | fields: [ |
12809bba TL |
796 | 'id', |
797 | 'compress', | |
798 | 'dow', | |
799 | 'exclude', | |
800 | 'mailto', | |
801 | 'mode', | |
802 | 'node', | |
803 | 'pool', | |
c6801352 | 804 | 'prune-backups', |
12809bba TL |
805 | 'starttime', |
806 | 'storage', | |
807 | 'vmid', | |
cfdc7ada | 808 | { name: 'enabled', type: 'boolean' }, |
f6710aac TL |
809 | { name: 'all', type: 'boolean' }, |
810 | ], | |
ad74d86c | 811 | }); |
cfdc7ada | 812 | }); |