]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/BackupJobDetail.js
ui: backup info: drop "Email" from "Notification" label
[pve-manager.git] / www / manager6 / dc / BackupJobDetail.js
1 Ext.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
157 Ext.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',
187 name: 'schedule',
188 fieldLabel: gettext('Schedule'),
189 },
190 {
191 xtype: 'displayfield',
192 name: 'next-run',
193 fieldLabel: gettext('Next Run'),
194 renderer: PVE.Utils.render_next_event,
195 },
196 {
197 xtype: 'displayfield',
198 name: 'selMode',
199 fieldLabel: gettext('Selection mode'),
200 },
201 {
202 xtype: 'displayfield',
203 name: 'pool',
204 fieldLabel: gettext('Pool to backup'),
205 },
206 ],
207 column2: [
208 {
209 xtype: 'displayfield',
210 name: 'mailnotification',
211 fieldLabel: gettext('Notification'),
212 renderer: function(value) {
213 let mailto = this.up('pveBackupInfo')?.record?.mailto || 'root@localhost';
214 let when = gettext('Always');
215 if (value === 'failure') {
216 when = gettext('On failure only');
217 }
218 return `${when} (${mailto})`;
219 },
220 },
221 {
222 xtype: 'displayfield',
223 name: 'compress',
224 fieldLabel: gettext('Compression'),
225 },
226 {
227 xtype: 'displayfield',
228 name: 'mode',
229 fieldLabel: gettext('Mode'),
230 renderer: function(value) {
231 const modeToDisplay = {
232 snapshot: gettext('Snapshot'),
233 stop: gettext('Stop'),
234 suspend: gettext('Snapshot'),
235 };
236 return modeToDisplay[value] ?? gettext('Unknown');
237 },
238 },
239 {
240 xtype: 'displayfield',
241 name: 'enabled',
242 fieldLabel: gettext('Enabled'),
243 renderer: v => PVE.Parser.parseBoolean(v.toString()) ? gettext('Yes') : gettext('No'),
244 },
245 ],
246
247 columnB: [
248 {
249 xtype: 'displayfield',
250 name: 'comment',
251 fieldLabel: gettext('Comment'),
252 },
253 {
254 xtype: 'label',
255 name: 'pruneLabel',
256 text: gettext('Retention Configuration') + ':',
257 bind: {
258 hidden: '{!hasRetention}',
259 },
260 },
261 {
262 layout: 'hbox',
263 border: false,
264 defaults: {
265 border: false,
266 layout: 'anchor',
267 flex: 1,
268 },
269 items: [
270 {
271 padding: '0 10 0 0',
272 defaults: {
273 labelWidth: 110,
274 },
275 items: [{
276 xtype: 'displayfield',
277 name: 'keep-all',
278 fieldLabel: gettext('Keep All'),
279 renderer: Proxmox.Utils.format_boolean,
280 bind: {
281 hidden: '{!retentionKeepAll}',
282 },
283 }].concat(
284 [
285 ['keep-last', gettext('Keep Last')],
286 ['keep-hourly', gettext('Keep Hourly')],
287 ].map(
288 name => ({
289 xtype: 'displayfield',
290 name: name[0],
291 fieldLabel: name[1],
292 bind: {
293 hidden: '{!hasRetention || retentionKeepAll}',
294 },
295 }),
296 ),
297 ),
298 },
299 {
300 padding: '0 0 0 10',
301 defaults: {
302 labelWidth: 110,
303 },
304 items: [
305 ['keep-daily', gettext('Keep Daily')],
306 ['keep-weekly', gettext('Keep Weekly')],
307 ].map(
308 name => ({
309 xtype: 'displayfield',
310 name: name[0],
311 fieldLabel: name[1],
312 bind: {
313 hidden: '{!hasRetention || retentionKeepAll}',
314 },
315 }),
316 ),
317 },
318 {
319 padding: '0 0 0 10',
320 defaults: {
321 labelWidth: 110,
322 },
323 items: [
324 ['keep-monthly', gettext('Keep Monthly')],
325 ['keep-yearly', gettext('Keep Yearly')],
326 ].map(
327 name => ({
328 xtype: 'displayfield',
329 name: name[0],
330 fieldLabel: name[1],
331 bind: {
332 hidden: '{!hasRetention || retentionKeepAll}',
333 },
334 }),
335 ),
336 },
337 ],
338 },
339 ],
340
341 setValues: function(values) {
342 var me = this;
343 let vm = me.getViewModel();
344
345 Ext.iterate(values, function(fieldId, val) {
346 let field = me.query('[isFormField][name=' + fieldId + ']')[0];
347 if (field) {
348 field.setValue(val);
349 }
350 });
351
352 if (values['prune-backups'] || values.maxfiles !== undefined) {
353 let keepValues;
354 if (values['prune-backups']) {
355 keepValues = values['prune-backups'];
356 } else if (values.maxfiles > 0) {
357 keepValues = { 'keep-last': values.maxfiles };
358 } else {
359 keepValues = { 'keep-all': 1 };
360 }
361
362 vm.set('retentionType', keepValues['keep-all'] ? 'all' : 'other');
363
364 // set values of all keep-X fields
365 ['all', 'last', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'].forEach(time => {
366 let name = `keep-${time}`;
367 me.query(`[isFormField][name=${name}]`)[0]?.setValue(keepValues[name]);
368 });
369 } else {
370 vm.set('retentionType', 'none');
371 }
372
373 // selection Mode depends on the presence/absence of several keys
374 let selModeField = me.query('[isFormField][name=selMode]')[0];
375 let selMode = 'none';
376 if (values.vmid) {
377 selMode = gettext('Include selected VMs');
378 }
379 if (values.all) {
380 selMode = gettext('All');
381 }
382 if (values.exclude) {
383 selMode = gettext('Exclude selected VMs');
384 }
385 if (values.pool) {
386 selMode = gettext('Pool based');
387 }
388 selModeField.setValue(selMode);
389
390 if (!values.pool) {
391 let poolField = me.query('[isFormField][name=pool]')[0];
392 poolField.setVisible(0);
393 }
394 },
395
396 initComponent: function() {
397 var me = this;
398
399 if (!me.record) {
400 throw "no data provided";
401 }
402 me.callParent();
403
404 me.setValues(me.record);
405 },
406 });
407
408
409 Ext.define('PVE.dc.BackedGuests', {
410 extend: 'Ext.grid.GridPanel',
411 alias: 'widget.pveBackedGuests',
412
413 stateful: true,
414 stateId: 'grid-dc-backed-guests',
415
416 textfilter: '',
417
418 columns: [
419 {
420 header: gettext('Type'),
421 dataIndex: "type",
422 renderer: PVE.Utils.render_resource_type,
423 flex: 1,
424 sortable: true,
425 },
426 {
427 header: gettext('VMID'),
428 dataIndex: 'vmid',
429 flex: 1,
430 sortable: true,
431 },
432 {
433 header: gettext('Name'),
434 dataIndex: 'name',
435 flex: 2,
436 sortable: true,
437 },
438 ],
439 viewConfig: {
440 stripeRows: true,
441 trackOver: false,
442 },
443
444 initComponent: function() {
445 let me = this;
446
447 me.store.clearFilter(true);
448
449 Ext.apply(me, {
450 tbar: [
451 '->',
452 gettext('Search') + ':',
453 ' ',
454 {
455 xtype: 'textfield',
456 width: 200,
457 emptyText: 'Name, VMID, Type',
458 enableKeyEvents: true,
459 listeners: {
460 buffer: 500,
461 keyup: function(field) {
462 let searchValue = field.getValue().toLowerCase();
463 me.store.clearFilter(true);
464 me.store.filterBy(function(record) {
465 let data = record.data;
466 for (const property in ['name', 'id', 'type']) {
467 if (data[property] === null) {
468 continue;
469 }
470 let v = data[property].toString();
471 if (v !== undefined) {
472 if (v.toLowerCase().includes(searchValue)) {
473 return true;
474 }
475 }
476 }
477 return false;
478 });
479 },
480 },
481 },
482 ],
483 });
484 me.callParent();
485 },
486 });