]>
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() { |
ad74d86c DM |
8 | var me = this; |
9 | ||
d5e771ce | 10 | me.isCreate = !me.jobid; |
ad74d86c DM |
11 | |
12 | var url; | |
13 | var method; | |
14 | ||
d5e771ce | 15 | if (me.isCreate) { |
ad74d86c DM |
16 | url = '/api2/extjs/cluster/backup'; |
17 | method = 'POST'; | |
18 | } else { | |
19 | url = '/api2/extjs/cluster/backup/' + me.jobid; | |
20 | method = 'PUT'; | |
21 | } | |
22 | ||
23 | var vmidField = Ext.create('Ext.form.field.Hidden', { | |
f6710aac | 24 | name: 'vmid', |
ad74d86c DM |
25 | }); |
26 | ||
d5e771ce | 27 | // 'value' can be assigned a string or an array |
8058410f | 28 | var selModeField = Ext.create('Proxmox.form.KVComboBox', { |
09cacce7 | 29 | xtype: 'proxmoxKVComboBox', |
a26c8bf7 | 30 | comboItems: [ |
ad74d86c DM |
31 | ['include', gettext('Include selected VMs')], |
32 | ['all', gettext('All')], | |
ab648869 | 33 | ['exclude', gettext('Exclude selected VMs')], |
f6710aac | 34 | ['pool', gettext('Pool based')], |
ad74d86c DM |
35 | ], |
36 | fieldLabel: gettext('Selection mode'), | |
37 | name: 'selMode', | |
f6710aac | 38 | value: '', |
ad74d86c DM |
39 | }); |
40 | ||
ad74d86c DM |
41 | var sm = Ext.create('Ext.selection.CheckboxModel', { |
42 | mode: 'SIMPLE', | |
43 | listeners: { | |
44 | selectionchange: function(model, selected) { | |
be8b69aa TL |
45 | var sel = []; |
46 | Ext.Array.each(selected, function(record) { | |
47 | sel.push(record.data.vmid); | |
48 | }); | |
49 | ||
50 | // to avoid endless recursion suspend the vmidField change | |
51 | // event temporary as it calls us again | |
52 | vmidField.suspendEvent('change'); | |
53 | vmidField.setValue(sel); | |
54 | vmidField.resumeEvent('change'); | |
f6710aac TL |
55 | }, |
56 | }, | |
ad74d86c DM |
57 | }); |
58 | ||
59 | var storagesel = Ext.create('PVE.form.StorageSelector', { | |
60 | fieldLabel: gettext('Storage'), | |
61 | nodename: 'localhost', | |
62 | storageContent: 'backup', | |
63 | allowBlank: false, | |
fe1dd5bb DC |
64 | name: 'storage', |
65 | listeners: { | |
66 | change: function(f, v) { | |
67 | let store = f.getStore(); | |
8267aa63 | 68 | let rec = store.findRecord('storage', v, 0, false, true, true); |
fe1dd5bb DC |
69 | let compressionSelector = me.down('pveCompressionSelector'); |
70 | ||
71 | if (rec && rec.data && rec.data.type === 'pbs') { | |
72 | compressionSelector.setValue('zstd'); | |
73 | compressionSelector.setDisabled(true); | |
74 | } else if (!compressionSelector.getEditable()) { | |
75 | compressionSelector.setDisabled(false); | |
76 | } | |
f6710aac TL |
77 | }, |
78 | }, | |
ad74d86c DM |
79 | }); |
80 | ||
81 | var store = new Ext.data.Store({ | |
82 | model: 'PVEResources', | |
60e049c2 TM |
83 | sorters: { |
84 | property: 'vmid', | |
f6710aac TL |
85 | order: 'ASC', |
86 | }, | |
ad74d86c DM |
87 | }); |
88 | ||
89 | var vmgrid = Ext.createWidget('grid', { | |
90 | store: store, | |
91 | border: true, | |
92 | height: 300, | |
93 | selModel: sm, | |
94 | disabled: true, | |
95 | columns: [ | |
60e049c2 | 96 | { |
ad74d86c DM |
97 | header: 'ID', |
98 | dataIndex: 'vmid', | |
f6710aac | 99 | width: 60, |
ad74d86c | 100 | }, |
60e049c2 | 101 | { |
ad74d86c | 102 | header: gettext('Node'), |
f6710aac | 103 | dataIndex: 'node', |
ad74d86c | 104 | }, |
60e049c2 | 105 | { |
ad74d86c DM |
106 | header: gettext('Status'), |
107 | dataIndex: 'uptime', | |
108 | renderer: function(value) { | |
109 | if (value) { | |
e7ade592 | 110 | return Proxmox.Utils.runningText; |
ad74d86c | 111 | } else { |
e7ade592 | 112 | return Proxmox.Utils.stoppedText; |
ad74d86c | 113 | } |
f6710aac | 114 | }, |
ad74d86c | 115 | }, |
60e049c2 TM |
116 | { |
117 | header: gettext('Name'), | |
ad74d86c | 118 | dataIndex: 'name', |
f6710aac | 119 | flex: 1, |
ad74d86c | 120 | }, |
60e049c2 TM |
121 | { |
122 | header: gettext('Type'), | |
f6710aac TL |
123 | dataIndex: 'type', |
124 | }, | |
125 | ], | |
ad74d86c DM |
126 | }); |
127 | ||
ab648869 TM |
128 | var selectPoolMembers = function(poolid) { |
129 | if (!poolid) { | |
130 | return; | |
131 | } | |
132 | sm.deselectAll(true); | |
133 | store.filter([ | |
134 | { | |
135 | id: 'poolFilter', | |
136 | property: 'pool', | |
f6710aac TL |
137 | value: poolid, |
138 | }, | |
ab648869 TM |
139 | ]); |
140 | sm.selectAll(true); | |
141 | }; | |
142 | ||
143 | var selPool = Ext.create('PVE.form.PoolSelector', { | |
a82afef0 | 144 | fieldLabel: gettext('Pool to backup'), |
ab648869 TM |
145 | hidden: true, |
146 | allowBlank: true, | |
147 | name: 'pool', | |
148 | listeners: { | |
8058410f | 149 | change: function(selpool, newValue, oldValue) { |
ab648869 | 150 | selectPoolMembers(newValue); |
f6710aac TL |
151 | }, |
152 | }, | |
ab648869 TM |
153 | }); |
154 | ||
ad74d86c DM |
155 | var nodesel = Ext.create('PVE.form.NodeSelector', { |
156 | name: 'node', | |
157 | fieldLabel: gettext('Node'), | |
158 | allowBlank: true, | |
159 | editable: true, | |
160 | autoSelect: false, | |
161 | emptyText: '-- ' + gettext('All') + ' --', | |
162 | listeners: { | |
163 | change: function(f, value) { | |
164 | storagesel.setNodename(value || 'localhost'); | |
165 | var mode = selModeField.getValue(); | |
166 | store.clearFilter(); | |
167 | store.filterBy(function(rec) { | |
53e3ea84 | 168 | return !value || rec.get('node') === value; |
ad74d86c DM |
169 | }); |
170 | if (mode === 'all') { | |
171 | sm.selectAll(true); | |
172 | } | |
ab648869 TM |
173 | |
174 | if (mode === 'pool') { | |
175 | selectPoolMembers(selPool.value); | |
176 | } | |
f6710aac TL |
177 | }, |
178 | }, | |
ad74d86c DM |
179 | }); |
180 | ||
181 | var column1 = [ | |
182 | nodesel, | |
183 | storagesel, | |
184 | { | |
185 | xtype: 'pveDayOfWeekSelector', | |
186 | name: 'dow', | |
187 | fieldLabel: gettext('Day of week'), | |
188 | multiSelect: true, | |
189 | value: ['sat'], | |
f6710aac | 190 | allowBlank: false, |
ad74d86c DM |
191 | }, |
192 | { | |
193 | xtype: 'timefield', | |
194 | fieldLabel: gettext('Start Time'), | |
195 | name: 'starttime', | |
196 | format: 'H:i', | |
4ecd1d07 | 197 | formatText: 'HH:MM', |
ad74d86c | 198 | value: '00:00', |
f6710aac | 199 | allowBlank: false, |
ad74d86c | 200 | }, |
ab648869 | 201 | selModeField, |
f6710aac | 202 | selPool, |
ad74d86c DM |
203 | ]; |
204 | ||
205 | var column2 = [ | |
206 | { | |
207 | xtype: 'textfield', | |
208 | fieldLabel: gettext('Send email to'), | |
f6710aac | 209 | name: 'mailto', |
ad74d86c DM |
210 | }, |
211 | { | |
212 | xtype: 'pveEmailNotificationSelector', | |
213 | fieldLabel: gettext('Email notification'), | |
214 | name: 'mailnotification', | |
ef725143 | 215 | deleteEmpty: !me.isCreate, |
f6710aac | 216 | value: me.isCreate ? 'always' : '', |
ad74d86c DM |
217 | }, |
218 | { | |
219 | xtype: 'pveCompressionSelector', | |
220 | fieldLabel: gettext('Compression'), | |
221 | name: 'compress', | |
ef725143 | 222 | deleteEmpty: !me.isCreate, |
f6710aac | 223 | value: 'zstd', |
ad74d86c DM |
224 | }, |
225 | { | |
226 | xtype: 'pveBackupModeSelector', | |
227 | fieldLabel: gettext('Mode'), | |
228 | value: 'snapshot', | |
f6710aac | 229 | name: 'mode', |
ad74d86c | 230 | }, |
cfdc7ada | 231 | { |
896c0d50 | 232 | xtype: 'proxmoxcheckbox', |
cfdc7ada EK |
233 | fieldLabel: gettext('Enable'), |
234 | name: 'enabled', | |
235 | uncheckedValue: 0, | |
236 | defaultValue: 1, | |
f6710aac | 237 | checked: true, |
cfdc7ada | 238 | }, |
f6710aac | 239 | vmidField, |
ad74d86c DM |
240 | ]; |
241 | ||
ef4ef788 | 242 | var ipanel = Ext.create('Proxmox.panel.InputPanel', { |
0de33b54 | 243 | onlineHelp: 'chapter_vzdump', |
ad74d86c | 244 | column1: column1, |
8058410f | 245 | column2: column2, |
ad74d86c DM |
246 | onGetValues: function(values) { |
247 | if (!values.node) { | |
d5e771ce | 248 | if (!me.isCreate) { |
60e049c2 | 249 | Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' }); |
ad74d86c DM |
250 | } |
251 | delete values.node; | |
252 | } | |
253 | ||
254 | var selMode = values.selMode; | |
255 | delete values.selMode; | |
256 | ||
257 | if (selMode === 'all') { | |
258 | values.all = 1; | |
259 | values.exclude = ''; | |
260 | delete values.vmid; | |
261 | } else if (selMode === 'exclude') { | |
262 | values.all = 1; | |
263 | values.exclude = values.vmid; | |
264 | delete values.vmid; | |
ab648869 TM |
265 | } else if (selMode === 'pool') { |
266 | delete values.vmid; | |
267 | } | |
268 | ||
269 | if (selMode !== 'pool') { | |
270 | delete values.pool; | |
ad74d86c DM |
271 | } |
272 | return values; | |
f6710aac | 273 | }, |
ad74d86c DM |
274 | }); |
275 | ||
276 | var update_vmid_selection = function(list, mode) { | |
ab648869 | 277 | if (mode !== 'all' && mode !== 'pool') { |
ad74d86c DM |
278 | sm.deselectAll(true); |
279 | if (list) { | |
280 | Ext.Array.each(list.split(','), function(vmid) { | |
8267aa63 | 281 | var rec = store.findRecord('vmid', vmid, 0, false, true, true); |
ad74d86c DM |
282 | if (rec) { |
283 | sm.select(rec, true); | |
284 | } | |
285 | }); | |
286 | } | |
287 | } | |
ad74d86c DM |
288 | }; |
289 | ||
290 | vmidField.on('change', function(f, value) { | |
291 | var mode = selModeField.getValue(); | |
292 | update_vmid_selection(value, mode); | |
293 | }); | |
294 | ||
295 | selModeField.on('change', function(f, value, oldValue) { | |
ab648869 TM |
296 | if (oldValue === 'pool') { |
297 | store.removeFilter('poolFilter'); | |
298 | } | |
299 | ||
300 | if (oldValue === 'all') { | |
301 | sm.deselectAll(true); | |
302 | vmidField.setValue(''); | |
303 | } | |
304 | ||
ad74d86c DM |
305 | if (value === 'all') { |
306 | sm.selectAll(true); | |
307 | vmgrid.setDisabled(true); | |
308 | } else { | |
309 | vmgrid.setDisabled(false); | |
310 | } | |
ab648869 TM |
311 | |
312 | if (value === 'pool') { | |
313 | vmgrid.setDisabled(true); | |
ad74d86c | 314 | vmidField.setValue(''); |
ab648869 TM |
315 | selPool.setVisible(true); |
316 | selPool.allowBlank = false; | |
317 | selectPoolMembers(selPool.value); | |
ab648869 TM |
318 | } else { |
319 | selPool.setVisible(false); | |
320 | selPool.allowBlank = true; | |
ad74d86c DM |
321 | } |
322 | var list = vmidField.getValue(); | |
323 | update_vmid_selection(list, value); | |
324 | }); | |
60e049c2 | 325 | |
ad74d86c DM |
326 | var reload = function() { |
327 | store.load({ | |
328 | params: { type: 'vm' }, | |
329 | callback: function() { | |
330 | var node = nodesel.getValue(); | |
331 | store.clearFilter(); | |
332 | store.filterBy(function(rec) { | |
53e3ea84 | 333 | return !node || node.length === 0 || rec.get('node') === node; |
ad74d86c DM |
334 | }); |
335 | var list = vmidField.getValue(); | |
336 | var mode = selModeField.getValue(); | |
337 | if (mode === 'all') { | |
338 | sm.selectAll(true); | |
8058410f | 339 | } else if (mode === 'pool') { |
ab648869 | 340 | selectPoolMembers(selPool.value); |
ad74d86c DM |
341 | } else { |
342 | update_vmid_selection(list, mode); | |
343 | } | |
f6710aac | 344 | }, |
ad74d86c DM |
345 | }); |
346 | }; | |
347 | ||
348 | Ext.applyIf(me, { | |
349 | subject: gettext("Backup Job"), | |
350 | url: url, | |
351 | method: method, | |
8058410f | 352 | items: [ipanel, vmgrid], |
ad74d86c DM |
353 | }); |
354 | ||
355 | me.callParent(); | |
356 | ||
d5e771ce | 357 | if (me.isCreate) { |
ad74d86c DM |
358 | selModeField.setValue('include'); |
359 | } else { | |
360 | me.load({ | |
361 | success: function(response, options) { | |
362 | var data = response.result.data; | |
363 | ||
364 | data.dow = data.dow.split(','); | |
365 | ||
366 | if (data.all || data.exclude) { | |
367 | if (data.exclude) { | |
368 | data.vmid = data.exclude; | |
369 | data.selMode = 'exclude'; | |
370 | } else { | |
371 | data.vmid = ''; | |
372 | data.selMode = 'all'; | |
373 | } | |
ab648869 TM |
374 | } else if (data.pool) { |
375 | data.selMode = 'pool'; | |
376 | data.selPool = data.pool; | |
ad74d86c DM |
377 | } else { |
378 | data.selMode = 'include'; | |
379 | } | |
380 | ||
381 | me.setValues(data); | |
f6710aac | 382 | }, |
ad74d86c DM |
383 | }); |
384 | } | |
385 | ||
386 | reload(); | |
f6710aac | 387 | }, |
ad74d86c DM |
388 | }); |
389 | ||
390 | ||
01ad47af AL |
391 | Ext.define('PVE.dc.BackupDiskTree', { |
392 | extend: 'Ext.tree.Panel', | |
393 | alias: 'widget.pveBackupDiskTree', | |
394 | ||
395 | folderSort: true, | |
396 | rootVisible: false, | |
397 | ||
398 | store: { | |
399 | sorters: 'id', | |
400 | data: {}, | |
401 | }, | |
402 | ||
403 | tools: [ | |
404 | { | |
405 | type: 'expand', | |
406 | tooltip: gettext('Expand All'), | |
407 | scope: this, | |
408 | callback: function(panel) { | |
409 | panel.expandAll(); | |
410 | }, | |
411 | }, | |
412 | { | |
413 | type: 'collapse', | |
414 | tooltip: gettext('Collapse All'), | |
415 | scope: this, | |
416 | callback: function(panel) { | |
417 | panel.collapseAll(); | |
f6710aac | 418 | }, |
01ad47af AL |
419 | }, |
420 | ], | |
421 | ||
422 | columns: [ | |
423 | { | |
424 | xtype: 'treecolumn', | |
425 | text: gettext('Guest Image'), | |
426 | renderer: function(value, meta, record) { | |
427 | if (record.data.type) { | |
428 | // guest level | |
429 | let ret = value; | |
430 | if (record.data.name) { | |
431 | ret += " (" + record.data.name + ")"; | |
432 | } | |
433 | return ret; | |
434 | } else { | |
435 | // volume level | |
436 | // extJS needs unique IDs but we only want to show the | |
437 | // volumes key from "vmid:key" | |
438 | return value.split(':')[1] + " - " + record.data.name; | |
439 | } | |
440 | }, | |
441 | dataIndex: 'id', | |
442 | flex: 6, | |
443 | }, | |
444 | { | |
445 | text: gettext('Type'), | |
446 | dataIndex: 'type', | |
447 | flex: 1, | |
448 | }, | |
449 | { | |
450 | text: gettext('Backup Job'), | |
451 | renderer: PVE.Utils.render_backup_status, | |
452 | dataIndex: 'included', | |
453 | flex: 3, | |
454 | }, | |
455 | ], | |
456 | ||
457 | reload: function() { | |
458 | var me = this; | |
459 | var sm = me.getSelectionModel(); | |
460 | ||
461 | Proxmox.Utils.API2Request({ | |
462 | url: "/cluster/backup/" + me.jobid + "/included_volumes", | |
463 | waitMsgTarget: me, | |
464 | method: 'GET', | |
465 | failure: function(response, opts) { | |
466 | Proxmox.Utils.setErrorMask(me, response.htmlStatus); | |
467 | }, | |
468 | success: function(response, opts) { | |
469 | sm.deselectAll(); | |
470 | me.setRootNode(response.result.data); | |
471 | me.expandAll(); | |
472 | }, | |
473 | }); | |
474 | }, | |
475 | ||
476 | initComponent: function() { | |
477 | var me = this; | |
478 | ||
479 | if (!me.jobid) { | |
480 | throw "no job id specified"; | |
481 | } | |
482 | ||
483 | var sm = Ext.create('Ext.selection.TreeModel', {}); | |
484 | ||
485 | Ext.apply(me, { | |
486 | selModel: sm, | |
487 | fields: ['id', 'type', | |
488 | { | |
489 | type: 'string', | |
490 | name: 'iconCls', | |
491 | calculate: function(data) { | |
492 | var txt = 'fa x-fa-tree fa-'; | |
493 | if (data.leaf && !data.type) { | |
494 | return txt + 'hdd-o'; | |
495 | } else if (data.type === 'qemu') { | |
496 | return txt + 'desktop'; | |
497 | } else if (data.type === 'lxc') { | |
498 | return txt + 'cube'; | |
499 | } else { | |
500 | return txt + 'question-circle'; | |
501 | } | |
f6710aac TL |
502 | }, |
503 | }, | |
01ad47af | 504 | ], |
e4b95752 TL |
505 | header: { |
506 | items: [{ | |
01ad47af | 507 | xtype: 'textfield', |
e4b95752 TL |
508 | fieldLabel: gettext('Search'), |
509 | labelWidth: 50, | |
510 | emptyText: 'Name, VMID, Type', | |
01ad47af | 511 | width: 200, |
e4b95752 | 512 | padding: '0 5 0 0', |
01ad47af AL |
513 | enableKeyEvents: true, |
514 | listeners: { | |
515 | buffer: 500, | |
516 | keyup: function(field) { | |
517 | let searchValue = field.getValue(); | |
518 | searchValue = searchValue.toLowerCase(); | |
519 | ||
520 | me.store.clearFilter(true); | |
521 | me.store.filterBy(function(record) { | |
522 | let match = false; | |
523 | ||
524 | let data = ''; | |
525 | if (record.data.depth == 0) { | |
526 | return true; | |
527 | } else if (record.data.depth == 1) { | |
528 | data = record.data; | |
529 | } else if (record.data.depth == 2) { | |
530 | data = record.parentNode.data; | |
531 | } | |
532 | ||
533 | Ext.each(['name', 'id', 'type'], function(property) { | |
e4b95752 | 534 | if (data[property] === null) { |
01ad47af AL |
535 | return; |
536 | } | |
537 | ||
538 | let v = data[property].toString(); | |
539 | if (v !== undefined) { | |
540 | v = v.toLowerCase(); | |
541 | if (v.includes(searchValue)) { | |
542 | match = true; | |
543 | return; | |
544 | } | |
545 | } | |
546 | }); | |
547 | return match; | |
548 | }); | |
f6710aac TL |
549 | }, |
550 | }, | |
551 | }, | |
8058410f TL |
552 | ] |
553 | }, | |
01ad47af AL |
554 | }); |
555 | ||
556 | me.callParent(); | |
557 | ||
558 | me.reload(); | |
f6710aac | 559 | }, |
01ad47af AL |
560 | }); |
561 | ||
562 | Ext.define('PVE.dc.BackupInfo', { | |
563 | extend: 'Proxmox.panel.InputPanel', | |
564 | alias: 'widget.pveBackupInfo', | |
565 | ||
386c9ce5 | 566 | padding: '5 0 5 10', |
01ad47af AL |
567 | |
568 | column1: [ | |
569 | { | |
570 | name: 'node', | |
571 | fieldLabel: gettext('Node'), | |
572 | xtype: 'displayfield', | |
8058410f | 573 | renderer: function(value) { |
01ad47af AL |
574 | if (!value) { |
575 | return '-- ' + gettext('All') + ' --'; | |
576 | } else { | |
577 | return value; | |
578 | } | |
579 | }, | |
580 | }, | |
581 | { | |
582 | name: 'storage', | |
583 | fieldLabel: gettext('Storage'), | |
584 | xtype: 'displayfield', | |
585 | }, | |
586 | { | |
587 | name: 'dow', | |
588 | fieldLabel: gettext('Day of week'), | |
589 | xtype: 'displayfield', | |
590 | renderer: PVE.Utils.render_backup_days_of_week, | |
591 | }, | |
592 | { | |
593 | name: 'starttime', | |
594 | fieldLabel: gettext('Start Time'), | |
595 | xtype: 'displayfield', | |
596 | }, | |
597 | { | |
598 | name: 'selMode', | |
599 | fieldLabel: gettext('Selection mode'), | |
600 | xtype: 'displayfield', | |
601 | }, | |
602 | { | |
603 | name: 'pool', | |
604 | fieldLabel: gettext('Pool to backup'), | |
605 | xtype: 'displayfield', | |
f6710aac | 606 | }, |
01ad47af AL |
607 | ], |
608 | column2: [ | |
609 | { | |
610 | name: 'mailto', | |
611 | fieldLabel: gettext('Send email to'), | |
612 | xtype: 'displayfield', | |
613 | }, | |
614 | { | |
615 | name: 'mailnotification', | |
616 | fieldLabel: gettext('Email notification'), | |
617 | xtype: 'displayfield', | |
8058410f | 618 | renderer: function(value) { |
01ad47af AL |
619 | let msg; |
620 | switch (value) { | |
621 | case 'always': | |
622 | msg = gettext('Always'); | |
623 | break; | |
624 | case 'failure': | |
625 | msg = gettext('On failure only'); | |
626 | break; | |
627 | } | |
628 | return msg; | |
629 | }, | |
630 | }, | |
631 | { | |
632 | name: 'compress', | |
633 | fieldLabel: gettext('Compression'), | |
634 | xtype: 'displayfield', | |
635 | }, | |
636 | { | |
637 | name: 'mode', | |
638 | fieldLabel: gettext('Mode'), | |
639 | xtype: 'displayfield', | |
8058410f | 640 | renderer: function(value) { |
01ad47af AL |
641 | let msg; |
642 | switch (value) { | |
643 | case 'snapshot': | |
644 | msg = gettext('Snapshot'); | |
645 | break; | |
646 | case 'suspend': | |
647 | msg = gettext('Suspend'); | |
648 | break; | |
649 | case 'stop': | |
650 | msg = gettext('Stop'); | |
651 | break; | |
652 | } | |
653 | return msg; | |
654 | }, | |
655 | }, | |
656 | { | |
657 | name: 'enabled', | |
658 | fieldLabel: gettext('Enabled'), | |
659 | xtype: 'displayfield', | |
8058410f | 660 | renderer: function(value) { |
01ad47af AL |
661 | if (PVE.Parser.parseBoolean(value.toString())) { |
662 | return gettext('Yes'); | |
663 | } else { | |
664 | return gettext('No'); | |
665 | } | |
666 | }, | |
667 | }, | |
668 | ], | |
669 | ||
670 | setValues: function(values) { | |
671 | var me = this; | |
672 | ||
673 | Ext.iterate(values, function(fieldId, val) { | |
674 | let field = me.query('[isFormField][name=' + fieldId + ']')[0]; | |
675 | if (field) { | |
676 | field.setValue(val); | |
677 | } | |
678 | }); | |
679 | ||
680 | // selection Mode depends on the presence/absence of several keys | |
681 | let selModeField = me.query('[isFormField][name=selMode]')[0]; | |
682 | let selMode = 'none'; | |
683 | if (values.vmid) { | |
684 | selMode = gettext('Include selected VMs'); | |
685 | } | |
686 | if (values.all) { | |
687 | selMode = gettext('All'); | |
688 | } | |
689 | if (values.exclude) { | |
690 | selMode = gettext('Exclude selected VMs'); | |
691 | } | |
692 | if (values.pool) { | |
693 | selMode = gettext('Pool based'); | |
694 | } | |
695 | selModeField.setValue(selMode); | |
696 | ||
697 | if (!values.pool) { | |
698 | let poolField = me.query('[isFormField][name=pool]')[0]; | |
699 | poolField.setVisible(0); | |
700 | } | |
701 | }, | |
702 | ||
703 | initComponent: function() { | |
704 | var me = this; | |
705 | ||
706 | if (!me.record) { | |
707 | throw "no data provided"; | |
708 | } | |
709 | me.callParent(); | |
710 | ||
711 | me.setValues(me.record); | |
f6710aac | 712 | }, |
01ad47af AL |
713 | }); |
714 | ||
7d2fac4a AL |
715 | |
716 | Ext.define('PVE.dc.BackedGuests', { | |
717 | extend: 'Ext.grid.GridPanel', | |
718 | alias: 'widget.pveBackedGuests', | |
719 | ||
720 | textfilter: '', | |
721 | ||
722 | columns: [ | |
723 | { | |
724 | header: gettext('Type'), | |
725 | dataIndex: "type", | |
726 | renderer: PVE.Utils.render_resource_type, | |
727 | flex: 1, | |
728 | sortable: true, | |
729 | }, | |
730 | { | |
731 | header: gettext('VMID'), | |
732 | dataIndex: 'vmid', | |
733 | flex: 1, | |
734 | sortable: true, | |
735 | }, | |
736 | { | |
737 | header: gettext('Name'), | |
738 | dataIndex: 'name', | |
739 | flex: 2, | |
740 | sortable: true, | |
741 | }, | |
742 | ], | |
743 | ||
744 | initComponent: function() { | |
745 | let me = this; | |
746 | ||
747 | me.store.clearFilter(true); | |
748 | ||
749 | Ext.apply(me, { | |
750 | stateful: true, | |
751 | stateId: 'grid-dc-backed-guests', | |
752 | tbar: [ | |
753 | '->', | |
754 | gettext('Search') + ':', ' ', | |
755 | { | |
756 | xtype: 'textfield', | |
757 | width: 200, | |
3183a553 | 758 | emptyText: 'Name, VMID, Type', |
7d2fac4a AL |
759 | enableKeyEvents: true, |
760 | listeners: { | |
761 | buffer: 500, | |
762 | keyup: function(field) { | |
763 | let searchValue = field.getValue(); | |
764 | searchValue = searchValue.toLowerCase(); | |
765 | ||
766 | me.store.clearFilter(true); | |
767 | me.store.filterBy(function(record) { | |
768 | let match = false; | |
769 | ||
770 | Ext.each(['name', 'vmid', 'type'], function(property) { | |
771 | if (record.data[property] == null) { | |
772 | return; | |
773 | } | |
774 | ||
775 | let v = record.data[property].toString(); | |
776 | if (v !== undefined) { | |
777 | v = v.toLowerCase(); | |
778 | if (v.includes(searchValue)) { | |
779 | match = true; | |
780 | return; | |
781 | } | |
782 | } | |
783 | }); | |
784 | return match; | |
785 | }); | |
f6710aac TL |
786 | }, |
787 | }, | |
788 | }, | |
7d2fac4a AL |
789 | ], |
790 | viewConfig: { | |
791 | stripeRows: true, | |
792 | trackOver: false, | |
793 | }, | |
794 | }); | |
795 | me.callParent(); | |
796 | }, | |
797 | }); | |
798 | ||
ad74d86c DM |
799 | Ext.define('PVE.dc.BackupView', { |
800 | extend: 'Ext.grid.GridPanel', | |
801 | ||
802 | alias: ['widget.pveDcBackupView'], | |
803 | ||
ba93a9c6 DC |
804 | onlineHelp: 'chapter_vzdump', |
805 | ||
ad74d86c | 806 | allText: '-- ' + gettext('All') + ' --', |
ad74d86c | 807 | |
8058410f | 808 | initComponent: function() { |
ad74d86c DM |
809 | var me = this; |
810 | ||
811 | var store = new Ext.data.Store({ | |
812 | model: 'pve-cluster-backup', | |
813 | proxy: { | |
56a353b9 | 814 | type: 'proxmox', |
f6710aac TL |
815 | url: "/api2/json/cluster/backup", |
816 | }, | |
ad74d86c DM |
817 | }); |
818 | ||
7d2fac4a AL |
819 | var not_backed_store = new Ext.data.Store({ |
820 | sorters: 'vmid', | |
8058410f | 821 | proxy: { |
7d2fac4a AL |
822 | type: 'proxmox', |
823 | url: 'api2/json/cluster/backupinfo/not_backed_up', | |
824 | }, | |
825 | }); | |
826 | ||
ad74d86c DM |
827 | var reload = function() { |
828 | store.load(); | |
7d2fac4a AL |
829 | not_backed_store.load({ |
830 | callback: function(records, operation, success) { | |
831 | if (records.length) { | |
832 | not_backed_warning.setVisible(true); | |
833 | not_backed_btn.setVisible(true); | |
834 | } else { | |
835 | not_backed_warning.setVisible(false); | |
836 | not_backed_btn.setVisible(false); | |
837 | } | |
838 | }, | |
839 | }); | |
ad74d86c DM |
840 | }; |
841 | ||
842 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
843 | ||
844 | var run_editor = function() { | |
845 | var rec = sm.getSelection()[0]; | |
846 | if (!rec) { | |
847 | return; | |
848 | } | |
849 | ||
43b2494b | 850 | var win = Ext.create('PVE.dc.BackupEdit', { |
f6710aac | 851 | jobid: rec.data.id, |
43b2494b SR |
852 | }); |
853 | win.on('destroy', reload); | |
854 | win.show(); | |
ad74d86c DM |
855 | }; |
856 | ||
01ad47af | 857 | var run_detail = function() { |
386c9ce5 | 858 | let me = this; |
8058410f | 859 | let record = sm.getSelection()[0]; |
01ad47af AL |
860 | if (!record) { |
861 | return; | |
862 | } | |
386c9ce5 | 863 | let infoview = Ext.create('PVE.dc.BackupInfo', { |
01ad47af AL |
864 | flex: 0, |
865 | layout: 'fit', | |
866 | record: record.data, | |
867 | }); | |
386c9ce5 | 868 | let disktree = Ext.create('PVE.dc.BackupDiskTree', { |
01ad47af AL |
869 | title: gettext('Included disks'), |
870 | flex: 1, | |
871 | jobid: record.data.id, | |
872 | }); | |
873 | ||
386c9ce5 | 874 | Ext.create('Ext.window.Window', { |
01ad47af | 875 | modal: true, |
386c9ce5 TL |
876 | width: 800, |
877 | height: 600, | |
01ad47af AL |
878 | stateful: true, |
879 | stateId: 'backup-detail-view', | |
880 | resizable: true, | |
881 | layout: 'fit', | |
882 | title: gettext('Backup Details'), | |
883 | ||
8058410f | 884 | items: [{ |
01ad47af AL |
885 | xtype: 'panel', |
886 | region: 'center', | |
887 | layout: { | |
888 | type: 'vbox', | |
f6710aac | 889 | align: 'stretch', |
01ad47af AL |
890 | }, |
891 | items: [infoview, disktree], | |
f6710aac | 892 | }], |
01ad47af AL |
893 | }).show(); |
894 | }; | |
895 | ||
389d3cf1 SR |
896 | var run_backup_now = function(job) { |
897 | job = Ext.clone(job); | |
898 | ||
a0fecb88 | 899 | let jobNode = job.node; |
389d3cf1 SR |
900 | // Remove properties related to scheduling |
901 | delete job.enabled; | |
902 | delete job.starttime; | |
903 | delete job.dow; | |
904 | delete job.id; | |
905 | delete job.node; | |
906 | job.all = job.all === true ? 1 : 0; | |
907 | ||
a0fecb88 TL |
908 | let allNodes = PVE.data.ResourceStore.getNodes(); |
909 | let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node); | |
910 | let errors = []; | |
911 | ||
912 | if (jobNode !== undefined) { | |
913 | if (!nodes.includes(jobNode)) { | |
914 | Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!"); | |
915 | return; | |
916 | } | |
8058410f | 917 | nodes = [jobNode]; |
a0fecb88 TL |
918 | } else { |
919 | let unkownNodes = allNodes.filter(node => node.status !== 'online'); | |
920 | if (unkownNodes.length > 0) | |
921 | errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline"))); | |
922 | } | |
923 | let jobTotalCount = nodes.length, jobsStarted = 0; | |
389d3cf1 SR |
924 | |
925 | Ext.Msg.show({ | |
926 | title: gettext('Please wait...'), | |
927 | closable: false, | |
a0fecb88 TL |
928 | progress: true, |
929 | progressText: '0/' + jobTotalCount, | |
389d3cf1 | 930 | }); |
389d3cf1 | 931 | |
8058410f | 932 | let postRequest = function() { |
a0fecb88 TL |
933 | jobsStarted++; |
934 | Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount); | |
389d3cf1 | 935 | |
a0fecb88 | 936 | if (jobsStarted == jobTotalCount) { |
389d3cf1 | 937 | Ext.Msg.hide(); |
a0fecb88 TL |
938 | if (errors.length > 0) { |
939 | Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />')); | |
389d3cf1 SR |
940 | } |
941 | } | |
a0fecb88 TL |
942 | }; |
943 | ||
944 | nodes.forEach(node => Proxmox.Utils.API2Request({ | |
945 | url: '/nodes/' + node + '/vzdump', | |
946 | method: 'POST', | |
947 | params: job, | |
8058410f | 948 | failure: function(response, opts) { |
a0fecb88 TL |
949 | errors.push(node + ': ' + response.htmlStatus); |
950 | postRequest(); | |
951 | }, | |
f6710aac | 952 | success: postRequest, |
a0fecb88 TL |
953 | })); |
954 | }; | |
389d3cf1 | 955 | |
7d2fac4a AL |
956 | var run_show_not_backed = function() { |
957 | var me = this; | |
958 | var backedinfo = Ext.create('PVE.dc.BackedGuests', { | |
959 | flex: 1, | |
960 | layout: 'fit', | |
961 | store: not_backed_store, | |
962 | }); | |
963 | ||
964 | var win = Ext.create('Ext.window.Window', { | |
965 | modal: true, | |
966 | width: 600, | |
967 | height: 500, | |
968 | resizable: true, | |
969 | layout: 'fit', | |
970 | title: gettext('Guests without backup job'), | |
971 | ||
8058410f | 972 | items: [{ |
7d2fac4a AL |
973 | xtype: 'panel', |
974 | region: 'center', | |
975 | layout: { | |
976 | type: 'vbox', | |
f6710aac | 977 | align: 'stretch', |
7d2fac4a AL |
978 | }, |
979 | items: [backedinfo], | |
f6710aac | 980 | }], |
7d2fac4a AL |
981 | }).show(); |
982 | }; | |
983 | ||
5720fafa | 984 | var edit_btn = new Proxmox.button.Button({ |
ad74d86c DM |
985 | text: gettext('Edit'), |
986 | disabled: true, | |
987 | selModel: sm, | |
f6710aac | 988 | handler: run_editor, |
ad74d86c DM |
989 | }); |
990 | ||
389d3cf1 SR |
991 | var run_btn = new Proxmox.button.Button({ |
992 | text: gettext('Run now'), | |
993 | disabled: true, | |
994 | selModel: sm, | |
995 | handler: function() { | |
996 | var rec = sm.getSelection()[0]; | |
997 | if (!rec) { | |
998 | return; | |
999 | } | |
1000 | ||
1001 | Ext.Msg.show({ | |
1002 | title: gettext('Confirm'), | |
1003 | icon: Ext.Msg.QUESTION, | |
1004 | msg: gettext('Start the selected backup job now?'), | |
1005 | buttons: Ext.Msg.YESNO, | |
1006 | callback: function(btn) { | |
1007 | if (btn !== 'yes') { | |
1008 | return; | |
1009 | } | |
1010 | run_backup_now(rec.data); | |
f6710aac | 1011 | }, |
389d3cf1 | 1012 | }); |
f6710aac | 1013 | }, |
389d3cf1 SR |
1014 | }); |
1015 | ||
3b1ca3ff | 1016 | var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', { |
ad74d86c | 1017 | selModel: sm, |
3b1ca3ff DC |
1018 | baseurl: '/cluster/backup', |
1019 | callback: function() { | |
1020 | reload(); | |
f6710aac | 1021 | }, |
ad74d86c DM |
1022 | }); |
1023 | ||
01ad47af | 1024 | var detail_btn = new Proxmox.button.Button({ |
393b74ff | 1025 | text: gettext('Job Detail'), |
01ad47af AL |
1026 | disabled: true, |
1027 | tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'), | |
1028 | selModel: sm, | |
1029 | handler: run_detail, | |
1030 | }); | |
1031 | ||
7d2fac4a AL |
1032 | var not_backed_warning = Ext.create('Ext.toolbar.TextItem', { |
1033 | html: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'), | |
1034 | hidden: true, | |
1035 | }); | |
1036 | ||
1037 | var not_backed_btn = new Proxmox.button.Button({ | |
1038 | text: gettext('Show'), | |
1039 | hidden: true, | |
1040 | handler: run_show_not_backed, | |
1041 | }); | |
1042 | ||
e7ade592 | 1043 | Proxmox.Utils.monStoreErrors(me, store); |
ad74d86c DM |
1044 | |
1045 | Ext.apply(me, { | |
1046 | store: store, | |
1047 | selModel: sm, | |
c4bb9405 DC |
1048 | stateful: true, |
1049 | stateId: 'grid-dc-backup', | |
ad74d86c | 1050 | viewConfig: { |
f6710aac | 1051 | trackOver: false, |
ad74d86c DM |
1052 | }, |
1053 | tbar: [ | |
1054 | { | |
1055 | text: gettext('Add'), | |
1056 | handler: function() { | |
f6710aac | 1057 | var win = Ext.create('PVE.dc.BackupEdit', {}); |
ad74d86c DM |
1058 | win.on('destroy', reload); |
1059 | win.show(); | |
f6710aac | 1060 | }, |
ad74d86c | 1061 | }, |
7a04ce23 | 1062 | '-', |
ad74d86c | 1063 | remove_btn, |
389d3cf1 | 1064 | edit_btn, |
01ad47af AL |
1065 | detail_btn, |
1066 | '-', | |
1067 | run_btn, | |
7d2fac4a AL |
1068 | '->', |
1069 | not_backed_warning, | |
1070 | not_backed_btn, | |
60e049c2 | 1071 | ], |
ad74d86c | 1072 | columns: [ |
cfdc7ada EK |
1073 | { |
1074 | header: gettext('Enabled'), | |
ae9e2161 | 1075 | width: 80, |
cfdc7ada | 1076 | dataIndex: 'enabled', |
370ed4c8 | 1077 | xtype: 'checkcolumn', |
cfdc7ada | 1078 | sortable: true, |
370ed4c8 DC |
1079 | disabled: true, |
1080 | disabledCls: 'x-item-enabled', | |
f6710aac | 1081 | stopSelection: false, |
cfdc7ada | 1082 | }, |
ad74d86c DM |
1083 | { |
1084 | header: gettext('Node'), | |
1085 | width: 100, | |
1086 | sortable: true, | |
1087 | dataIndex: 'node', | |
1088 | renderer: function(value) { | |
1089 | if (value) { | |
1090 | return value; | |
1091 | } | |
1092 | return me.allText; | |
f6710aac | 1093 | }, |
ad74d86c DM |
1094 | }, |
1095 | { | |
1096 | header: gettext('Day of week'), | |
1097 | width: 200, | |
1098 | sortable: false, | |
2bdf9dd3 | 1099 | dataIndex: 'dow', |
f6710aac | 1100 | renderer: PVE.Utils.render_backup_days_of_week, |
ad74d86c DM |
1101 | }, |
1102 | { | |
1103 | header: gettext('Start Time'), | |
1104 | width: 60, | |
1105 | sortable: true, | |
f6710aac | 1106 | dataIndex: 'starttime', |
ad74d86c DM |
1107 | }, |
1108 | { | |
1109 | header: gettext('Storage'), | |
1110 | width: 100, | |
1111 | sortable: true, | |
f6710aac | 1112 | dataIndex: 'storage', |
ad74d86c DM |
1113 | }, |
1114 | { | |
1115 | header: gettext('Selection'), | |
1116 | flex: 1, | |
1117 | sortable: false, | |
1118 | dataIndex: 'vmid', | |
f6710aac TL |
1119 | renderer: PVE.Utils.render_backup_selection, |
1120 | }, | |
ad74d86c DM |
1121 | ], |
1122 | listeners: { | |
c0b3df6e | 1123 | activate: reload, |
f6710aac TL |
1124 | itemdblclick: run_editor, |
1125 | }, | |
ad74d86c | 1126 | }); |
60e049c2 | 1127 | |
ad74d86c | 1128 | me.callParent(); |
f6710aac | 1129 | }, |
ad74d86c | 1130 | }, function() { |
ad74d86c DM |
1131 | Ext.define('pve-cluster-backup', { |
1132 | extend: 'Ext.data.Model', | |
60e049c2 | 1133 | fields: [ |
ad74d86c DM |
1134 | 'id', 'starttime', 'dow', |
1135 | 'storage', 'node', 'vmid', 'exclude', | |
389d3cf1 | 1136 | 'mailto', 'pool', 'compress', 'mode', |
cfdc7ada | 1137 | { name: 'enabled', type: 'boolean' }, |
f6710aac TL |
1138 | { name: 'all', type: 'boolean' }, |
1139 | ], | |
ad74d86c | 1140 | }); |
cfdc7ada | 1141 | }); |