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