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