]>
Commit | Line | Data |
---|---|---|
33839735 | 1 | Ext.define('pbs-data-store-snapshots', { |
ca23a97f | 2 | extend: 'Ext.data.Model', |
d9c38ddc | 3 | fields: [ |
d9c38ddc | 4 | 'backup-type', |
507c39c5 DM |
5 | 'backup-id', |
6 | { | |
e8f0ad19 | 7 | name: 'backup-time', |
507c39c5 | 8 | type: 'date', |
c6e07769 | 9 | dateFormat: 'timestamp', |
507c39c5 | 10 | }, |
d9c38ddc | 11 | 'files', |
04b0ca8b | 12 | 'owner', |
7b212c1f | 13 | 'verification', |
c6e07769 | 14 | { name: 'size', type: 'int', allowNull: true }, |
e005f953 | 15 | { |
2774566b | 16 | name: 'crypt-mode', |
676b0fde | 17 | type: 'boolean', |
e005f953 | 18 | calculate: function(data) { |
2774566b DC |
19 | let crypt = { |
20 | none: 0, | |
21 | mixed: 0, | |
22 | 'sign-only': 0, | |
23 | encrypt: 0, | |
106603c5 | 24 | count: 0, |
2774566b | 25 | }; |
e005f953 DC |
26 | data.files.forEach(file => { |
27 | if (file.filename === 'index.json.blob') return; // is never encrypted | |
2774566b DC |
28 | let mode = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); |
29 | if (mode !== -1) { | |
30 | crypt[file['crypt-mode']]++; | |
1bfdae79 | 31 | crypt.count++; |
e005f953 | 32 | } |
e005f953 DC |
33 | }); |
34 | ||
106603c5 | 35 | return PBS.Utils.calculateCryptMode(crypt); |
c6e07769 | 36 | }, |
6d55603d DC |
37 | }, |
38 | { | |
39 | name: 'matchesFilter', | |
40 | type: 'boolean', | |
41 | defaultValue: true, | |
42 | }, | |
c6e07769 | 43 | ], |
ca23a97f DM |
44 | }); |
45 | ||
46 | Ext.define('PBS.DataStoreContent', { | |
e8f0ad19 | 47 | extend: 'Ext.tree.Panel', |
ca23a97f DM |
48 | alias: 'widget.pbsDataStoreContent', |
49 | ||
e8f0ad19 | 50 | rootVisible: false, |
507c39c5 | 51 | |
c0ac2074 DC |
52 | title: gettext('Content'), |
53 | ||
f1baa7f4 TL |
54 | controller: { |
55 | xclass: 'Ext.app.ViewController', | |
56 | ||
57 | init: function(view) { | |
58 | if (!view.datastore) { | |
59 | throw "no datastore specified"; | |
60 | } | |
61 | ||
3f98b347 | 62 | this.store = Ext.create('Ext.data.Store', { |
33839735 | 63 | model: 'pbs-data-store-snapshots', |
e8f0ad19 DM |
64 | groupField: 'backup-group', |
65 | }); | |
3f98b347 | 66 | this.store.on('load', this.onLoad, this); |
e8f0ad19 | 67 | |
7b1e2669 DC |
68 | view.getStore().setSorters([ |
69 | 'backup-group', | |
70 | 'text', | |
c6e07769 | 71 | 'backup-time', |
7b1e2669 | 72 | ]); |
90779237 | 73 | Proxmox.Utils.monStoreErrors(view, this.store); |
f1baa7f4 TL |
74 | this.reload(); // initial load |
75 | }, | |
76 | ||
77 | reload: function() { | |
3f98b347 TL |
78 | let view = this.getView(); |
79 | ||
80 | if (!view.store || !this.store) { | |
81 | console.warn('cannot reload, no store(s)'); | |
82 | return; | |
83 | } | |
f1baa7f4 | 84 | |
e8f0ad19 | 85 | let url = `/api2/json/admin/datastore/${view.datastore}/snapshots`; |
3f98b347 | 86 | this.store.setProxy({ |
f1baa7f4 | 87 | type: 'proxmox', |
26f499b1 | 88 | timeout: 300*1000, // 5 minutes, we should make that api call faster |
c6e07769 | 89 | url: url, |
f1baa7f4 | 90 | }); |
e8f0ad19 | 91 | |
3f98b347 TL |
92 | this.store.load(); |
93 | }, | |
e8f0ad19 | 94 | |
3f98b347 TL |
95 | getRecordGroups: function(records) { |
96 | let groups = {}; | |
97 | ||
98 | for (const item of records) { | |
99 | var btype = item.data["backup-type"]; | |
100 | let group = btype + "/" + item.data["backup-id"]; | |
101 | ||
102 | if (groups[group] !== undefined) { | |
103 | continue; | |
104 | } | |
105 | ||
106 | var cls = ''; | |
107 | if (btype === 'vm') { | |
108 | cls = 'fa-desktop'; | |
109 | } else if (btype === 'ct') { | |
110 | cls = 'fa-cube'; | |
111 | } else if (btype === 'host') { | |
112 | cls = 'fa-building'; | |
113 | } else { | |
add5861e | 114 | console.warn(`got unknown backup-type '${btype}'`); |
3f98b347 TL |
115 | continue; // FIXME: auto render? what do? |
116 | } | |
117 | ||
118 | groups[group] = { | |
119 | text: group, | |
120 | leaf: false, | |
121 | iconCls: "fa " + cls, | |
122 | expanded: false, | |
123 | backup_type: item.data["backup-type"], | |
124 | backup_id: item.data["backup-id"], | |
c6e07769 | 125 | children: [], |
aeee4329 | 126 | }; |
3f98b347 | 127 | } |
aeee4329 | 128 | |
3f98b347 TL |
129 | return groups; |
130 | }, | |
e8f0ad19 | 131 | |
90779237 | 132 | onLoad: function(store, records, success, operation) { |
6d55603d | 133 | let me = this; |
3f98b347 TL |
134 | let view = this.getView(); |
135 | ||
136 | if (!success) { | |
90779237 | 137 | Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError())); |
3f98b347 TL |
138 | return; |
139 | } | |
140 | ||
141 | let groups = this.getRecordGroups(records); | |
e8f0ad19 | 142 | |
69d970a6 DC |
143 | let selected; |
144 | let expanded = {}; | |
145 | ||
146 | view.getSelection().some(function(item) { | |
147 | let id = item.data.text; | |
148 | if (item.data.leaf) { | |
149 | id = item.parentNode.data.text + id; | |
150 | } | |
151 | selected = id; | |
152 | return true; | |
153 | }); | |
154 | ||
155 | view.getRootNode().cascadeBy({ | |
156 | before: item => { | |
157 | if (item.isExpanded() && !item.data.leaf) { | |
158 | let id = item.data.text; | |
159 | expanded[id] = true; | |
160 | return true; | |
161 | } | |
162 | return false; | |
163 | }, | |
c6e07769 | 164 | after: Ext.emptyFn, |
69d970a6 DC |
165 | }); |
166 | ||
3f98b347 TL |
167 | for (const item of records) { |
168 | let group = item.data["backup-type"] + "/" + item.data["backup-id"]; | |
169 | let children = groups[group].children; | |
170 | ||
171 | let data = item.data; | |
172 | ||
f68ae22c | 173 | data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]); |
3e395378 | 174 | data.leaf = false; |
3f98b347 | 175 | data.cls = 'no-leaf-icons'; |
6d55603d | 176 | data.matchesFilter = true; |
3f98b347 | 177 | |
69d970a6 DC |
178 | data.expanded = !!expanded[data.text]; |
179 | ||
3e395378 DC |
180 | data.children = []; |
181 | for (const file of data.files) { | |
b1149ebb | 182 | file.text = file.filename; |
3e395378 DC |
183 | file['crypt-mode'] = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); |
184 | file.leaf = true; | |
6d55603d | 185 | file.matchesFilter = true; |
3e395378 DC |
186 | |
187 | data.children.push(file); | |
188 | } | |
189 | ||
3f98b347 TL |
190 | children.push(data); |
191 | } | |
192 | ||
423df9b1 | 193 | let nowSeconds = Date.now() / 1000; |
3f98b347 | 194 | let children = []; |
69d970a6 | 195 | for (const [name, group] of Object.entries(groups)) { |
3f98b347 | 196 | let last_backup = 0; |
2774566b DC |
197 | let crypt = { |
198 | none: 0, | |
199 | mixed: 0, | |
200 | 'sign-only': 0, | |
106603c5 | 201 | encrypt: 0, |
2774566b | 202 | }; |
423df9b1 TL |
203 | let verify = { |
204 | outdated: 0, | |
205 | none: 0, | |
206 | failed: 0, | |
207 | ok: 0, | |
208 | }; | |
209 | for (let item of group.children) { | |
2774566b | 210 | crypt[PBS.Utils.cryptmap[item['crypt-mode']]]++; |
48e22a89 | 211 | if (item["backup-time"] > last_backup && item.size !== null) { |
3f98b347 TL |
212 | last_backup = item["backup-time"]; |
213 | group["backup-time"] = last_backup; | |
214 | group.files = item.files; | |
215 | group.size = item.size; | |
04b0ca8b | 216 | group.owner = item.owner; |
c879e5af | 217 | verify.lastFailed = item.verification && item.verification.state !== 'ok'; |
3f98b347 | 218 | } |
423df9b1 TL |
219 | if (!item.verification) { |
220 | verify.none++; | |
221 | } else { | |
222 | if (item.verification.state === 'ok') { | |
223 | verify.ok++; | |
224 | } else { | |
225 | verify.failed++; | |
226 | } | |
227 | let task = Proxmox.Utils.parse_task_upid(item.verification.upid); | |
228 | item.verification.lastTime = task.starttime; | |
229 | if (nowSeconds - task.starttime > 30 * 24 * 60 * 60) { | |
230 | verify.outdated++; | |
231 | } | |
7b212c1f | 232 | } |
3f98b347 | 233 | } |
423df9b1 | 234 | group.verification = verify; |
3f98b347 | 235 | group.count = group.children.length; |
6d55603d | 236 | group.matchesFilter = true; |
106603c5 DC |
237 | crypt.count = group.count; |
238 | group['crypt-mode'] = PBS.Utils.calculateCryptMode(crypt); | |
69d970a6 | 239 | group.expanded = !!expanded[name]; |
3f98b347 TL |
240 | children.push(group); |
241 | } | |
242 | ||
243 | view.setRootNode({ | |
244 | expanded: true, | |
c6e07769 | 245 | children: children, |
3f98b347 | 246 | }); |
69d970a6 DC |
247 | |
248 | if (selected !== undefined) { | |
249 | let selection = view.getRootNode().findChildBy(function(item) { | |
250 | let id = item.data.text; | |
251 | if (item.data.leaf) { | |
252 | id = item.parentNode.data.text + id; | |
253 | } | |
254 | return selected === id; | |
255 | }, undefined, true); | |
80db161e SR |
256 | if (selection) { |
257 | view.setSelection(selection); | |
258 | view.getView().focusRow(selection); | |
259 | } | |
69d970a6 DC |
260 | } |
261 | ||
90779237 | 262 | Proxmox.Utils.setErrorMask(view, false); |
6d55603d DC |
263 | if (view.getStore().getFilters().length > 0) { |
264 | let searchBox = me.lookup("searchbox"); | |
c6e07769 | 265 | let searchvalue = searchBox.getValue(); |
6d55603d DC |
266 | me.search(searchBox, searchvalue); |
267 | } | |
f1baa7f4 | 268 | }, |
5f448992 | 269 | |
3e395378 | 270 | onPrune: function(view, rI, cI, item, e, rec) { |
c6e07769 | 271 | view = this.getView(); |
5f448992 | 272 | |
5f448992 DM |
273 | if (!(rec && rec.data)) return; |
274 | let data = rec.data; | |
3e395378 | 275 | if (rec.parentNode.id !== 'root') return; |
5f448992 DM |
276 | |
277 | if (!view.datastore) return; | |
278 | ||
279 | let win = Ext.create('PBS.DataStorePrune', { | |
280 | datastore: view.datastore, | |
281 | backup_type: data.backup_type, | |
282 | backup_id: data.backup_id, | |
283 | }); | |
284 | win.on('destroy', this.reload, this); | |
285 | win.show(); | |
98425309 DC |
286 | }, |
287 | ||
3e395378 | 288 | onVerify: function(view, rI, cI, item, e, rec) { |
484d439a TL |
289 | let me = this; |
290 | view = me.getView(); | |
8f6088c1 DM |
291 | |
292 | if (!view.datastore) return; | |
293 | ||
8f6088c1 DM |
294 | if (!(rec && rec.data)) return; |
295 | let data = rec.data; | |
296 | ||
297 | let params; | |
298 | ||
3e395378 | 299 | if (rec.parentNode.id !== 'root') { |
8f6088c1 DM |
300 | params = { |
301 | "backup-type": data["backup-type"], | |
302 | "backup-id": data["backup-id"], | |
303 | "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), | |
304 | }; | |
305 | } else { | |
306 | params = { | |
307 | "backup-type": data.backup_type, | |
308 | "backup-id": data.backup_id, | |
309 | }; | |
310 | } | |
311 | ||
312 | Proxmox.Utils.API2Request({ | |
313 | params: params, | |
314 | url: `/admin/datastore/${view.datastore}/verify`, | |
315 | method: 'POST', | |
316 | failure: function(response) { | |
317 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
318 | }, | |
319 | success: function(response, options) { | |
320 | Ext.create('Proxmox.window.TaskViewer', { | |
321 | upid: response.result.data, | |
484d439a | 322 | taskDone: () => me.reload(), |
8f6088c1 DM |
323 | }).show(); |
324 | }, | |
325 | }); | |
326 | }, | |
327 | ||
3e395378 DC |
328 | onForget: function(view, rI, cI, item, e, rec) { |
329 | let me = this; | |
c6e07769 | 330 | view = this.getView(); |
4ff2c9b8 | 331 | |
4ff2c9b8 DM |
332 | if (!(rec && rec.data)) return; |
333 | let data = rec.data; | |
4ff2c9b8 DM |
334 | if (!view.datastore) return; |
335 | ||
3e395378 DC |
336 | Ext.Msg.show({ |
337 | title: gettext('Confirm'), | |
338 | icon: Ext.Msg.WARNING, | |
339 | message: Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${data.text}'`), | |
340 | buttons: Ext.Msg.YESNO, | |
341 | defaultFocus: 'no', | |
342 | callback: function(btn) { | |
343 | if (btn !== 'yes') { | |
344 | return; | |
345 | } | |
4ff2c9b8 | 346 | |
3e395378 DC |
347 | Proxmox.Utils.API2Request({ |
348 | params: { | |
349 | "backup-type": data["backup-type"], | |
350 | "backup-id": data["backup-id"], | |
351 | "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), | |
352 | }, | |
353 | url: `/admin/datastore/${view.datastore}/snapshots`, | |
354 | method: 'DELETE', | |
355 | waitMsgTarget: view, | |
356 | failure: function(response, opts) { | |
357 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
358 | }, | |
359 | callback: me.reload.bind(me), | |
360 | }); | |
4ff2c9b8 | 361 | }, |
4ff2c9b8 DM |
362 | }); |
363 | }, | |
364 | ||
3e395378 | 365 | downloadFile: function(tV, rI, cI, item, e, rec) { |
98425309 DC |
366 | let me = this; |
367 | let view = me.getView(); | |
368 | ||
98425309 | 369 | if (!(rec && rec.data)) return; |
3e395378 DC |
370 | let data = rec.parentNode.data; |
371 | ||
372 | let file = rec.data.filename; | |
373 | let params = { | |
374 | 'backup-id': data['backup-id'], | |
375 | 'backup-type': data['backup-type'], | |
376 | 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0), | |
377 | 'file-name': file, | |
378 | }; | |
379 | ||
380 | let idx = file.lastIndexOf('.'); | |
381 | let filename = file.slice(0, idx); | |
382 | let atag = document.createElement('a'); | |
383 | params['file-name'] = file; | |
384 | atag.download = filename; | |
c6e07769 TL |
385 | let url = new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, |
386 | window.location.origin); | |
3e395378 DC |
387 | for (const [key, value] of Object.entries(params)) { |
388 | url.searchParams.append(key, value); | |
389 | } | |
390 | atag.href = url.href; | |
391 | atag.click(); | |
8567c0d2 DC |
392 | }, |
393 | ||
3e395378 | 394 | openPxarBrowser: function(tv, rI, Ci, item, e, rec) { |
8567c0d2 DC |
395 | let me = this; |
396 | let view = me.getView(); | |
397 | ||
8567c0d2 | 398 | if (!(rec && rec.data)) return; |
3e395378 | 399 | let data = rec.parentNode.data; |
8567c0d2 DC |
400 | |
401 | let id = data['backup-id']; | |
402 | let time = data['backup-time']; | |
403 | let type = data['backup-type']; | |
404 | let timetext = PBS.Utils.render_datetime_utc(data["backup-time"]); | |
405 | ||
406 | Ext.create('PBS.window.FileBrowser', { | |
407 | title: `${type}/${id}/${timetext}`, | |
408 | datastore: view.datastore, | |
409 | 'backup-id': id, | |
410 | 'backup-time': (time.getTime()/1000).toFixed(0), | |
411 | 'backup-type': type, | |
3e395378 | 412 | archive: rec.data.filename, |
8567c0d2 | 413 | }).show(); |
6d55603d DC |
414 | }, |
415 | ||
416 | filter: function(item, value) { | |
417 | if (item.data.text.indexOf(value) !== -1) { | |
418 | return true; | |
419 | } | |
420 | ||
421 | if (item.data.owner && item.data.owner.indexOf(value) !== -1) { | |
422 | return true; | |
423 | } | |
424 | ||
425 | return false; | |
426 | }, | |
427 | ||
428 | search: function(tf, value) { | |
429 | let me = this; | |
430 | let view = me.getView(); | |
431 | let store = view.getStore(); | |
432 | if (!value && value !== 0) { | |
433 | store.clearFilter(); | |
434 | store.getRoot().collapseChildren(true); | |
435 | tf.triggers.clear.setVisible(false); | |
436 | return; | |
437 | } | |
438 | tf.triggers.clear.setVisible(true); | |
439 | if (value.length < 2) return; | |
440 | Proxmox.Utils.setErrorMask(view, true); | |
441 | // we do it a little bit later for the error mask to work | |
442 | setTimeout(function() { | |
443 | store.clearFilter(); | |
444 | store.getRoot().collapseChildren(true); | |
445 | ||
446 | store.beginUpdate(); | |
447 | store.getRoot().cascadeBy({ | |
448 | before: function(item) { | |
c6e07769 | 449 | if (me.filter(item, value)) { |
6d55603d DC |
450 | item.set('matchesFilter', true); |
451 | if (item.parentNode && item.parentNode.id !== 'root') { | |
452 | item.parentNode.childmatches = true; | |
453 | } | |
454 | return false; | |
455 | } | |
456 | return true; | |
457 | }, | |
458 | after: function(item) { | |
459 | if (me.filter(item, value) || item.id === 'root' || item.childmatches) { | |
460 | item.set('matchesFilter', true); | |
461 | if (item.parentNode && item.parentNode.id !== 'root') { | |
462 | item.parentNode.childmatches = true; | |
463 | } | |
464 | if (item.childmatches) { | |
465 | item.expand(); | |
466 | } | |
467 | } else { | |
468 | item.set('matchesFilter', false); | |
469 | } | |
470 | delete item.childmatches; | |
471 | }, | |
472 | }); | |
473 | store.endUpdate(); | |
474 | ||
475 | store.filter((item) => !!item.get('matchesFilter')); | |
476 | Proxmox.Utils.setErrorMask(view, false); | |
477 | }, 10); | |
478 | }, | |
f1baa7f4 TL |
479 | }, |
480 | ||
c879e5af TL |
481 | viewConfig: { |
482 | getRowClass: function(record, index) { | |
483 | let verify = record.get('verification'); | |
484 | if (verify && verify.lastFailed) { | |
485 | return 'proxmox-invalid-row'; | |
486 | } | |
c6e07769 | 487 | return null; |
c879e5af TL |
488 | }, |
489 | }, | |
490 | ||
04b0ca8b DC |
491 | columns: [ |
492 | { | |
493 | xtype: 'treecolumn', | |
494 | header: gettext("Backup Group"), | |
495 | dataIndex: 'text', | |
c6e07769 | 496 | flex: 1, |
04b0ca8b | 497 | }, |
3e395378 DC |
498 | { |
499 | header: gettext('Actions'), | |
500 | xtype: 'actioncolumn', | |
501 | dataIndex: 'text', | |
502 | items: [ | |
503 | { | |
504 | handler: 'onVerify', | |
505 | tooltip: gettext('Verify'), | |
506 | getClass: (v, m, rec) => rec.data.leaf ? 'pmx-hidden' : 'fa fa-search', | |
507 | isDisabled: (v, r, c, i, rec) => !!rec.data.leaf, | |
508 | }, | |
509 | { | |
510 | handler: 'onPrune', | |
511 | tooltip: gettext('Prune'), | |
512 | getClass: (v, m, rec) => rec.parentNode.id ==='root' ? 'fa fa-scissors' : 'pmx-hidden', | |
513 | isDisabled: (v, r, c, i, rec) => rec.parentNode.id !=='root', | |
514 | }, | |
515 | { | |
516 | handler: 'onForget', | |
517 | tooltip: gettext('Forget Snapshot'), | |
518 | getClass: (v, m, rec) => !rec.data.leaf && rec.parentNode.id !== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden', | |
519 | isDisabled: (v, r, c, i, rec) => rec.data.leaf || rec.parentNode.id === 'root', | |
520 | }, | |
521 | { | |
522 | handler: 'downloadFile', | |
523 | tooltip: gettext('Download'), | |
524 | getClass: (v, m, rec) => rec.data.leaf && rec.data.filename ? 'fa fa-download' : 'pmx-hidden', | |
525 | isDisabled: (v, r, c, i, rec) => !rec.data.leaf || !rec.data.filename || rec.data['crypt-mode'] > 2, | |
526 | }, | |
527 | { | |
528 | handler: 'openPxarBrowser', | |
529 | tooltip: gettext('Browse'), | |
530 | getClass: (v, m, rec) => { | |
531 | let data = rec.data; | |
532 | if (data.leaf && data.filename && data.filename.endsWith('pxar.didx')) { | |
533 | return 'fa fa-folder-open-o'; | |
534 | } | |
535 | return 'pmx-hidden'; | |
536 | }, | |
537 | isDisabled: (v, r, c, i, rec) => { | |
538 | let data = rec.data; | |
539 | return !(data.leaf && | |
540 | data.filename && | |
541 | data.filename.endsWith('pxar.didx') && | |
c96b0de4 | 542 | data['crypt-mode'] < 3); |
c6e07769 | 543 | }, |
3e395378 | 544 | }, |
c6e07769 | 545 | ], |
3e395378 | 546 | }, |
04b0ca8b DC |
547 | { |
548 | xtype: 'datecolumn', | |
549 | header: gettext('Backup Time'), | |
550 | sortable: true, | |
551 | dataIndex: 'backup-time', | |
552 | format: 'Y-m-d H:i:s', | |
c6e07769 | 553 | width: 150, |
04b0ca8b DC |
554 | }, |
555 | { | |
556 | header: gettext("Size"), | |
557 | sortable: true, | |
558 | dataIndex: 'size', | |
a7a5f56d | 559 | renderer: (v, meta, record) => { |
3e395378 DC |
560 | if (record.data.text === 'client.log.blob' && v === undefined) { |
561 | return ''; | |
562 | } | |
a7a5f56d TL |
563 | if (v === undefined || v === null) { |
564 | meta.tdCls = "x-grid-row-loading"; | |
565 | return ''; | |
566 | } | |
567 | return Proxmox.Utils.format_size(v); | |
568 | }, | |
04b0ca8b DC |
569 | }, |
570 | { | |
571 | xtype: 'numbercolumn', | |
572 | format: '0', | |
573 | header: gettext("Count"), | |
574 | sortable: true, | |
46388e6a TL |
575 | width: 75, |
576 | align: 'right', | |
04b0ca8b DC |
577 | dataIndex: 'count', |
578 | }, | |
579 | { | |
580 | header: gettext("Owner"), | |
581 | sortable: true, | |
582 | dataIndex: 'owner', | |
583 | }, | |
e005f953 DC |
584 | { |
585 | header: gettext('Encrypted'), | |
2774566b | 586 | dataIndex: 'crypt-mode', |
3e395378 | 587 | renderer: (v, meta, record) => { |
71282dd9 SR |
588 | if (record.data.size === undefined || record.data.size === null) { |
589 | return ''; | |
590 | } | |
3e395378 DC |
591 | if (v === -1) { |
592 | return ''; | |
593 | } | |
594 | let iconCls = PBS.Utils.cryptIconCls[v] || ''; | |
595 | let iconTxt = ""; | |
596 | if (iconCls) { | |
597 | iconTxt = `<i class="fa fa-fw fa-${iconCls}"></i> `; | |
598 | } | |
c6e07769 TL |
599 | return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText; |
600 | }, | |
04b0ca8b | 601 | }, |
7b212c1f TL |
602 | { |
603 | header: gettext('Verify State'), | |
604 | sortable: true, | |
605 | dataIndex: 'verification', | |
423df9b1 | 606 | width: 120, |
7b212c1f | 607 | renderer: (v, meta, record) => { |
423df9b1 TL |
608 | let i = (cls, txt) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`; |
609 | if (v === undefined || v === null) { | |
610 | return record.data.leaf ? '' : i('question-circle-o warning', gettext('None')); | |
7b212c1f | 611 | } |
423df9b1 | 612 | let tip, iconCls, txt; |
7b212c1f | 613 | if (record.parentNode.id === 'root') { |
423df9b1 TL |
614 | if (v.failed === 0) { |
615 | if (v.none === 0) { | |
616 | if (v.outdated > 0) { | |
617 | tip = 'All OK, but some snapshots were not verified in last 30 days'; | |
618 | iconCls = 'check warning'; | |
619 | txt = gettext('All OK (old)'); | |
620 | } else { | |
621 | tip = 'All snapshots verified at least once in last 30 days'; | |
622 | iconCls = 'check good'; | |
623 | txt = gettext('All OK'); | |
624 | } | |
625 | } else if (v.ok === 0) { | |
626 | tip = `${v.none} not verified yet`; | |
627 | iconCls = 'question-circle-o warning'; | |
628 | txt = gettext('None'); | |
629 | } else { | |
630 | tip = `${v.ok} OK, ${v.none} not verified yet`; | |
631 | iconCls = 'check faded'; | |
632 | txt = `${v.ok} OK`; | |
633 | } | |
634 | } else { | |
635 | tip = `${v.ok} OK, ${v.failed} failed, ${v.none} not verified yet`; | |
636 | iconCls = 'times critical'; | |
637 | txt = v.ok === 0 && v.none === 0 | |
638 | ? gettext('All failed') | |
639 | : `${v.failed} failed`; | |
640 | } | |
641 | } else if (!v.state) { | |
642 | return record.data.leaf ? '' : gettext('None'); | |
643 | } else { | |
644 | let verify_time = Proxmox.Utils.render_timestamp(v.lastTime); | |
645 | tip = `Last verify task started on ${verify_time}`; | |
646 | txt = v.state; | |
647 | iconCls = 'times critical'; | |
648 | if (v.state === 'ok') { | |
649 | iconCls = 'check good'; | |
650 | let now = Date.now() / 1000; | |
651 | if (now - v.lastTime > 30 * 24 * 60 * 60) { | |
652 | tip = `Last verify task over 30 days ago: ${verify_time}`; | |
653 | iconCls = 'check warning'; | |
654 | } | |
655 | } | |
7b212c1f TL |
656 | } |
657 | return `<span data-qtip="${tip}"> | |
423df9b1 | 658 | <i class="fa fa-fw fa-${iconCls}"></i> ${txt} |
7b212c1f TL |
659 | </span>`; |
660 | }, | |
661 | listeners: { | |
662 | dblclick: function(view, el, row, col, ev, rec) { | |
663 | let data = rec.data || {}; | |
664 | let verify = data.verification; | |
665 | if (verify && verify.upid && rec.parentNode.id !== 'root') { | |
666 | let win = Ext.create('Proxmox.window.TaskViewer', { | |
667 | upid: verify.upid, | |
668 | }); | |
669 | win.show(); | |
670 | } | |
671 | }, | |
672 | }, | |
673 | }, | |
04b0ca8b | 674 | ], |
b1127fd0 | 675 | |
04b0ca8b DC |
676 | tbar: [ |
677 | { | |
678 | text: gettext('Reload'), | |
679 | iconCls: 'fa fa-refresh', | |
680 | handler: 'reload', | |
681 | }, | |
6d55603d DC |
682 | '->', |
683 | { | |
684 | xtype: 'tbtext', | |
685 | html: gettext('Search'), | |
686 | }, | |
687 | { | |
688 | xtype: 'textfield', | |
689 | reference: 'searchbox', | |
c3b1da9e | 690 | emptyText: gettext('group, date or owner'), |
6d55603d DC |
691 | triggers: { |
692 | clear: { | |
693 | cls: 'pmx-clear-trigger', | |
694 | weight: -1, | |
695 | hidden: true, | |
696 | handler: function() { | |
697 | this.triggers.clear.setVisible(false); | |
698 | this.setValue(''); | |
699 | }, | |
c6e07769 | 700 | }, |
6d55603d DC |
701 | }, |
702 | listeners: { | |
703 | change: { | |
704 | fn: 'search', | |
705 | buffer: 500, | |
706 | }, | |
707 | }, | |
c6e07769 | 708 | }, |
04b0ca8b | 709 | ], |
ca23a97f | 710 | }); |