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