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