]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/dc/BackupJobDetail.js
ui: backup: adapt backup job details to new notification params
[pve-manager.git] / www / manager6 / dc / BackupJobDetail.js
CommitLineData
faee4219
TL
1Ext.define('PVE.dc.BackupDiskTree', {
2 extend: 'Ext.tree.Panel',
3 alias: 'widget.pveBackupDiskTree',
4
5 folderSort: true,
6 rootVisible: false,
7
8 store: {
9 sorters: 'id',
10 data: {},
11 },
12
13 tools: [
14 {
15 type: 'expand',
16 tooltip: gettext('Expand All'),
17 callback: panel => panel.expandAll(),
18 },
19 {
20 type: 'collapse',
21 tooltip: gettext('Collapse All'),
22 callback: panel => panel.collapseAll(),
23 },
24 ],
25
26 columns: [
27 {
28 xtype: 'treecolumn',
29 text: gettext('Guest Image'),
30 renderer: function(value, meta, record) {
31 if (record.data.type) {
32 // guest level
33 let ret = value;
34 if (record.data.name) {
35 ret += " (" + record.data.name + ")";
36 }
37 return ret;
38 } else {
39 // extJS needs unique IDs but we only want to show the volumes key from "vmid:key"
40 return value.split(':')[1] + " - " + record.data.name;
41 }
42 },
43 dataIndex: 'id',
44 flex: 6,
45 },
46 {
47 text: gettext('Type'),
48 dataIndex: 'type',
49 flex: 1,
50 },
51 {
52 text: gettext('Backup Job'),
53 renderer: PVE.Utils.render_backup_status,
54 dataIndex: 'included',
55 flex: 3,
56 },
57 ],
58
59 reload: function() {
60 let me = this;
61 let sm = me.getSelectionModel();
62
63 Proxmox.Utils.API2Request({
64 url: `/cluster/backup/${me.jobid}/included_volumes`,
65 waitMsgTarget: me,
66 method: 'GET',
67 failure: function(response, opts) {
68 Proxmox.Utils.setErrorMask(me, response.htmlStatus);
69 },
70 success: function(response, opts) {
71 sm.deselectAll();
72 me.setRootNode(response.result.data);
73 me.expandAll();
74 },
75 });
76 },
77
78 initComponent: function() {
79 var me = this;
80
81 if (!me.jobid) {
82 throw "no job id specified";
83 }
84
85 var sm = Ext.create('Ext.selection.TreeModel', {});
86
87 Ext.apply(me, {
88 selModel: sm,
89 fields: ['id', 'type',
90 {
91 type: 'string',
92 name: 'iconCls',
93 calculate: function(data) {
94 var txt = 'fa x-fa-tree fa-';
95 if (data.leaf && !data.type) {
96 return txt + 'hdd-o';
97 } else if (data.type === 'qemu') {
98 return txt + 'desktop';
99 } else if (data.type === 'lxc') {
100 return txt + 'cube';
101 } else {
102 return txt + 'question-circle';
103 }
104 },
105 },
106 ],
107 header: {
108 items: [{
109 xtype: 'textfield',
110 fieldLabel: gettext('Search'),
111 labelWidth: 50,
112 emptyText: 'Name, VMID, Type',
113 width: 200,
114 padding: '0 5 0 0',
115 enableKeyEvents: true,
116 listeners: {
117 buffer: 500,
118 keyup: function(field) {
119 let searchValue = field.getValue().toLowerCase();
120 me.store.clearFilter(true);
121 me.store.filterBy(function(record) {
122 let data = {};
123 if (record.data.depth === 0) {
124 return true;
125 } else if (record.data.depth === 1) {
126 data = record.data;
127 } else if (record.data.depth === 2) {
128 data = record.parentNode.data;
129 }
130
131 for (const property of ['name', 'id', 'type']) {
132 if (!data[property]) {
133 continue;
134 }
135 let v = data[property].toString();
136 if (v !== undefined) {
137 v = v.toLowerCase();
138 if (v.includes(searchValue)) {
139 return true;
140 }
141 }
142 }
143 return false;
144 });
145 },
146 },
147 }],
148 },
149 });
150
151 me.callParent();
152
153 me.reload();
154 },
155});
156
157Ext.define('PVE.dc.BackupInfo', {
158 extend: 'Proxmox.panel.InputPanel',
159 alias: 'widget.pveBackupInfo',
160
161 viewModel: {
162 data: {
163 retentionType: 'none',
164 },
165 formulas: {
166 hasRetention: (get) => get('retentionType') !== 'none',
167 retentionKeepAll: (get) => get('retentionType') === 'all',
168 },
169 },
170
171 padding: '5 0 5 10',
172
173 column1: [
174 {
175 xtype: 'displayfield',
176 name: 'node',
177 fieldLabel: gettext('Node'),
178 renderer: value => value || `-- ${gettext('All')} --`,
179 },
180 {
181 xtype: 'displayfield',
182 name: 'storage',
183 fieldLabel: gettext('Storage'),
184 },
185 {
186 xtype: 'displayfield',
20d15804
DC
187 name: 'schedule',
188 fieldLabel: gettext('Schedule'),
faee4219
TL
189 },
190 {
191 xtype: 'displayfield',
452bdb03
TL
192 name: 'next-run',
193 fieldLabel: gettext('Next Run'),
194 renderer: PVE.Utils.render_next_event,
195 },
196 {
197 xtype: 'displayfield',
faee4219
TL
198 name: 'selMode',
199 fieldLabel: gettext('Selection mode'),
200 },
faee4219
TL
201 ],
202 column2: [
203 {
204 xtype: 'displayfield',
3be44912 205 name: 'notification-policy',
e040ee28 206 fieldLabel: gettext('Notification'),
faee4219 207 renderer: function(value) {
3be44912
LW
208 let record = this.up('pveBackupInfo')?.record;
209
210 // Fall back to old value, in case this option is not migrated yet.
211 let policy = value || record?.mailnotification || 'always';
212
faee4219 213 let when = gettext('Always');
3be44912 214 if (policy === 'failure') {
faee4219 215 when = gettext('On failure only');
3be44912
LW
216 } else if (policy === 'never') {
217 when = gettext('Never');
faee4219 218 }
3be44912
LW
219
220 // Notification-target takes precedence
221 let target = record?.['notification-target'] ||
222 record?.mailto ||
223 gettext('No target configured');
224
225 return `${when} (${target})`;
faee4219
TL
226 },
227 },
228 {
229 xtype: 'displayfield',
230 name: 'compress',
231 fieldLabel: gettext('Compression'),
232 },
233 {
234 xtype: 'displayfield',
235 name: 'mode',
236 fieldLabel: gettext('Mode'),
237 renderer: function(value) {
3d5b3eb6
TL
238 const modeToDisplay = {
239 snapshot: gettext('Snapshot'),
240 stop: gettext('Stop'),
241 suspend: gettext('Snapshot'),
242 };
243 return modeToDisplay[value] ?? gettext('Unknown');
faee4219
TL
244 },
245 },
246 {
247 xtype: 'displayfield',
248 name: 'enabled',
249 fieldLabel: gettext('Enabled'),
250 renderer: v => PVE.Parser.parseBoolean(v.toString()) ? gettext('Yes') : gettext('No'),
251 },
64f3f346
TL
252 {
253 xtype: 'displayfield',
254 name: 'pool',
255 fieldLabel: gettext('Pool to backup'),
256 },
faee4219
TL
257 ],
258
259 columnB: [
d9e05d34
DC
260 {
261 xtype: 'displayfield',
262 name: 'comment',
263 fieldLabel: gettext('Comment'),
98cbec54 264 renderer: Ext.String.htmlEncode,
d9e05d34 265 },
faee4219 266 {
9a1678c5
TL
267 xtype: 'fieldset',
268 title: gettext('Retention Configuration'),
faee4219 269 layout: 'hbox',
9a1678c5 270 collapsible: true,
faee4219
TL
271 defaults: {
272 border: false,
273 layout: 'anchor',
274 flex: 1,
275 },
9a1678c5
TL
276 bind: {
277 hidden: '{!hasRetention}',
278 },
faee4219
TL
279 items: [
280 {
281 padding: '0 10 0 0',
282 defaults: {
283 labelWidth: 110,
284 },
285 items: [{
286 xtype: 'displayfield',
287 name: 'keep-all',
288 fieldLabel: gettext('Keep All'),
289 renderer: Proxmox.Utils.format_boolean,
290 bind: {
291 hidden: '{!retentionKeepAll}',
292 },
293 }].concat(
294 [
295 ['keep-last', gettext('Keep Last')],
296 ['keep-hourly', gettext('Keep Hourly')],
297 ].map(
298 name => ({
299 xtype: 'displayfield',
300 name: name[0],
301 fieldLabel: name[1],
302 bind: {
303 hidden: '{!hasRetention || retentionKeepAll}',
304 },
305 }),
306 ),
307 ),
308 },
309 {
310 padding: '0 0 0 10',
311 defaults: {
312 labelWidth: 110,
313 },
314 items: [
315 ['keep-daily', gettext('Keep Daily')],
316 ['keep-weekly', gettext('Keep Weekly')],
317 ].map(
318 name => ({
319 xtype: 'displayfield',
320 name: name[0],
321 fieldLabel: name[1],
322 bind: {
323 hidden: '{!hasRetention || retentionKeepAll}',
324 },
325 }),
326 ),
327 },
328 {
329 padding: '0 0 0 10',
330 defaults: {
331 labelWidth: 110,
332 },
333 items: [
334 ['keep-monthly', gettext('Keep Monthly')],
335 ['keep-yearly', gettext('Keep Yearly')],
336 ].map(
337 name => ({
338 xtype: 'displayfield',
339 name: name[0],
340 fieldLabel: name[1],
341 bind: {
342 hidden: '{!hasRetention || retentionKeepAll}',
343 },
344 }),
345 ),
346 },
347 ],
348 },
349 ],
350
351 setValues: function(values) {
352 var me = this;
353 let vm = me.getViewModel();
354
355 Ext.iterate(values, function(fieldId, val) {
356 let field = me.query('[isFormField][name=' + fieldId + ']')[0];
357 if (field) {
358 field.setValue(val);
359 }
360 });
361
362 if (values['prune-backups'] || values.maxfiles !== undefined) {
363 let keepValues;
364 if (values['prune-backups']) {
365 keepValues = values['prune-backups'];
366 } else if (values.maxfiles > 0) {
367 keepValues = { 'keep-last': values.maxfiles };
368 } else {
369 keepValues = { 'keep-all': 1 };
370 }
371
372 vm.set('retentionType', keepValues['keep-all'] ? 'all' : 'other');
373
374 // set values of all keep-X fields
375 ['all', 'last', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'].forEach(time => {
376 let name = `keep-${time}`;
377 me.query(`[isFormField][name=${name}]`)[0]?.setValue(keepValues[name]);
378 });
379 } else {
380 vm.set('retentionType', 'none');
381 }
382
383 // selection Mode depends on the presence/absence of several keys
384 let selModeField = me.query('[isFormField][name=selMode]')[0];
385 let selMode = 'none';
386 if (values.vmid) {
387 selMode = gettext('Include selected VMs');
388 }
389 if (values.all) {
390 selMode = gettext('All');
391 }
392 if (values.exclude) {
393 selMode = gettext('Exclude selected VMs');
394 }
395 if (values.pool) {
396 selMode = gettext('Pool based');
397 }
398 selModeField.setValue(selMode);
399
400 if (!values.pool) {
401 let poolField = me.query('[isFormField][name=pool]')[0];
402 poolField.setVisible(0);
403 }
404 },
405
406 initComponent: function() {
407 var me = this;
408
409 if (!me.record) {
410 throw "no data provided";
411 }
412 me.callParent();
413
414 me.setValues(me.record);
415 },
416});
417
418
419Ext.define('PVE.dc.BackedGuests', {
420 extend: 'Ext.grid.GridPanel',
421 alias: 'widget.pveBackedGuests',
422
423 stateful: true,
424 stateId: 'grid-dc-backed-guests',
425
426 textfilter: '',
427
428 columns: [
429 {
430 header: gettext('Type'),
431 dataIndex: "type",
432 renderer: PVE.Utils.render_resource_type,
433 flex: 1,
434 sortable: true,
435 },
436 {
437 header: gettext('VMID'),
438 dataIndex: 'vmid',
439 flex: 1,
440 sortable: true,
441 },
442 {
443 header: gettext('Name'),
444 dataIndex: 'name',
445 flex: 2,
446 sortable: true,
447 },
448 ],
449 viewConfig: {
450 stripeRows: true,
451 trackOver: false,
452 },
453
454 initComponent: function() {
455 let me = this;
456
457 me.store.clearFilter(true);
458
459 Ext.apply(me, {
460 tbar: [
461 '->',
462 gettext('Search') + ':',
463 ' ',
464 {
465 xtype: 'textfield',
466 width: 200,
467 emptyText: 'Name, VMID, Type',
468 enableKeyEvents: true,
469 listeners: {
470 buffer: 500,
471 keyup: function(field) {
472 let searchValue = field.getValue().toLowerCase();
473 me.store.clearFilter(true);
474 me.store.filterBy(function(record) {
475 let data = record.data;
b52d2b12 476 for (const property of ['name', 'vmid', 'type']) {
faee4219
TL
477 if (data[property] === null) {
478 continue;
479 }
480 let v = data[property].toString();
481 if (v !== undefined) {
482 if (v.toLowerCase().includes(searchValue)) {
483 return true;
484 }
485 }
486 }
487 return false;
488 });
489 },
490 },
491 },
492 ],
493 });
494 me.callParent();
495 },
496});