]>
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 | ||
cd92fd73 DC |
288 | verifyAll: function() { |
289 | var view = this.getView(); | |
290 | ||
291 | Proxmox.Utils.API2Request({ | |
292 | url: `/admin/datastore/${view.datastore}/verify`, | |
293 | method: 'POST', | |
294 | failure: function(response) { | |
295 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
296 | }, | |
297 | success: function(response, options) { | |
298 | Ext.create('Proxmox.window.TaskViewer', { | |
299 | upid: response.result.data, | |
300 | }).show(); | |
301 | }, | |
302 | }); | |
303 | }, | |
304 | ||
3e395378 | 305 | onVerify: function(view, rI, cI, item, e, rec) { |
484d439a TL |
306 | let me = this; |
307 | view = me.getView(); | |
8f6088c1 DM |
308 | |
309 | if (!view.datastore) return; | |
310 | ||
8f6088c1 DM |
311 | if (!(rec && rec.data)) return; |
312 | let data = rec.data; | |
313 | ||
314 | let params; | |
315 | ||
3e395378 | 316 | if (rec.parentNode.id !== 'root') { |
8f6088c1 DM |
317 | params = { |
318 | "backup-type": data["backup-type"], | |
319 | "backup-id": data["backup-id"], | |
320 | "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), | |
321 | }; | |
322 | } else { | |
323 | params = { | |
324 | "backup-type": data.backup_type, | |
325 | "backup-id": data.backup_id, | |
326 | }; | |
327 | } | |
328 | ||
329 | Proxmox.Utils.API2Request({ | |
330 | params: params, | |
331 | url: `/admin/datastore/${view.datastore}/verify`, | |
332 | method: 'POST', | |
333 | failure: function(response) { | |
334 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
335 | }, | |
336 | success: function(response, options) { | |
337 | Ext.create('Proxmox.window.TaskViewer', { | |
338 | upid: response.result.data, | |
484d439a | 339 | taskDone: () => me.reload(), |
8f6088c1 DM |
340 | }).show(); |
341 | }, | |
342 | }); | |
343 | }, | |
344 | ||
3e395378 DC |
345 | onForget: function(view, rI, cI, item, e, rec) { |
346 | let me = this; | |
c6e07769 | 347 | view = this.getView(); |
4ff2c9b8 | 348 | |
4ff2c9b8 DM |
349 | if (!(rec && rec.data)) return; |
350 | let data = rec.data; | |
4ff2c9b8 DM |
351 | if (!view.datastore) return; |
352 | ||
3e395378 DC |
353 | Ext.Msg.show({ |
354 | title: gettext('Confirm'), | |
355 | icon: Ext.Msg.WARNING, | |
356 | message: Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${data.text}'`), | |
357 | buttons: Ext.Msg.YESNO, | |
358 | defaultFocus: 'no', | |
359 | callback: function(btn) { | |
360 | if (btn !== 'yes') { | |
361 | return; | |
362 | } | |
4ff2c9b8 | 363 | |
3e395378 DC |
364 | Proxmox.Utils.API2Request({ |
365 | params: { | |
366 | "backup-type": data["backup-type"], | |
367 | "backup-id": data["backup-id"], | |
368 | "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), | |
369 | }, | |
370 | url: `/admin/datastore/${view.datastore}/snapshots`, | |
371 | method: 'DELETE', | |
372 | waitMsgTarget: view, | |
373 | failure: function(response, opts) { | |
374 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
375 | }, | |
376 | callback: me.reload.bind(me), | |
377 | }); | |
4ff2c9b8 | 378 | }, |
4ff2c9b8 DM |
379 | }); |
380 | }, | |
381 | ||
3e395378 | 382 | downloadFile: function(tV, rI, cI, item, e, rec) { |
98425309 DC |
383 | let me = this; |
384 | let view = me.getView(); | |
385 | ||
98425309 | 386 | if (!(rec && rec.data)) return; |
3e395378 DC |
387 | let data = rec.parentNode.data; |
388 | ||
389 | let file = rec.data.filename; | |
390 | let params = { | |
391 | 'backup-id': data['backup-id'], | |
392 | 'backup-type': data['backup-type'], | |
393 | 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0), | |
394 | 'file-name': file, | |
395 | }; | |
396 | ||
397 | let idx = file.lastIndexOf('.'); | |
398 | let filename = file.slice(0, idx); | |
399 | let atag = document.createElement('a'); | |
400 | params['file-name'] = file; | |
401 | atag.download = filename; | |
c6e07769 TL |
402 | let url = new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, |
403 | window.location.origin); | |
3e395378 DC |
404 | for (const [key, value] of Object.entries(params)) { |
405 | url.searchParams.append(key, value); | |
406 | } | |
407 | atag.href = url.href; | |
408 | atag.click(); | |
8567c0d2 DC |
409 | }, |
410 | ||
3e395378 | 411 | openPxarBrowser: function(tv, rI, Ci, item, e, rec) { |
8567c0d2 DC |
412 | let me = this; |
413 | let view = me.getView(); | |
414 | ||
8567c0d2 | 415 | if (!(rec && rec.data)) return; |
3e395378 | 416 | let data = rec.parentNode.data; |
8567c0d2 DC |
417 | |
418 | let id = data['backup-id']; | |
419 | let time = data['backup-time']; | |
420 | let type = data['backup-type']; | |
421 | let timetext = PBS.Utils.render_datetime_utc(data["backup-time"]); | |
422 | ||
423 | Ext.create('PBS.window.FileBrowser', { | |
424 | title: `${type}/${id}/${timetext}`, | |
425 | datastore: view.datastore, | |
426 | 'backup-id': id, | |
427 | 'backup-time': (time.getTime()/1000).toFixed(0), | |
428 | 'backup-type': type, | |
3e395378 | 429 | archive: rec.data.filename, |
8567c0d2 | 430 | }).show(); |
6d55603d DC |
431 | }, |
432 | ||
433 | filter: function(item, value) { | |
434 | if (item.data.text.indexOf(value) !== -1) { | |
435 | return true; | |
436 | } | |
437 | ||
438 | if (item.data.owner && item.data.owner.indexOf(value) !== -1) { | |
439 | return true; | |
440 | } | |
441 | ||
442 | return false; | |
443 | }, | |
444 | ||
445 | search: function(tf, value) { | |
446 | let me = this; | |
447 | let view = me.getView(); | |
448 | let store = view.getStore(); | |
449 | if (!value && value !== 0) { | |
450 | store.clearFilter(); | |
451 | store.getRoot().collapseChildren(true); | |
452 | tf.triggers.clear.setVisible(false); | |
453 | return; | |
454 | } | |
455 | tf.triggers.clear.setVisible(true); | |
456 | if (value.length < 2) return; | |
457 | Proxmox.Utils.setErrorMask(view, true); | |
458 | // we do it a little bit later for the error mask to work | |
459 | setTimeout(function() { | |
460 | store.clearFilter(); | |
461 | store.getRoot().collapseChildren(true); | |
462 | ||
463 | store.beginUpdate(); | |
464 | store.getRoot().cascadeBy({ | |
465 | before: function(item) { | |
c6e07769 | 466 | if (me.filter(item, value)) { |
6d55603d DC |
467 | item.set('matchesFilter', true); |
468 | if (item.parentNode && item.parentNode.id !== 'root') { | |
469 | item.parentNode.childmatches = true; | |
470 | } | |
471 | return false; | |
472 | } | |
473 | return true; | |
474 | }, | |
475 | after: function(item) { | |
476 | if (me.filter(item, value) || item.id === 'root' || item.childmatches) { | |
477 | item.set('matchesFilter', true); | |
478 | if (item.parentNode && item.parentNode.id !== 'root') { | |
479 | item.parentNode.childmatches = true; | |
480 | } | |
481 | if (item.childmatches) { | |
482 | item.expand(); | |
483 | } | |
484 | } else { | |
485 | item.set('matchesFilter', false); | |
486 | } | |
487 | delete item.childmatches; | |
488 | }, | |
489 | }); | |
490 | store.endUpdate(); | |
491 | ||
492 | store.filter((item) => !!item.get('matchesFilter')); | |
493 | Proxmox.Utils.setErrorMask(view, false); | |
494 | }, 10); | |
495 | }, | |
f1baa7f4 TL |
496 | }, |
497 | ||
c879e5af TL |
498 | viewConfig: { |
499 | getRowClass: function(record, index) { | |
500 | let verify = record.get('verification'); | |
501 | if (verify && verify.lastFailed) { | |
502 | return 'proxmox-invalid-row'; | |
503 | } | |
c6e07769 | 504 | return null; |
c879e5af TL |
505 | }, |
506 | }, | |
507 | ||
04b0ca8b DC |
508 | columns: [ |
509 | { | |
510 | xtype: 'treecolumn', | |
511 | header: gettext("Backup Group"), | |
512 | dataIndex: 'text', | |
c6e07769 | 513 | flex: 1, |
04b0ca8b | 514 | }, |
3e395378 DC |
515 | { |
516 | header: gettext('Actions'), | |
517 | xtype: 'actioncolumn', | |
518 | dataIndex: 'text', | |
519 | items: [ | |
520 | { | |
521 | handler: 'onVerify', | |
522 | tooltip: gettext('Verify'), | |
523 | getClass: (v, m, rec) => rec.data.leaf ? 'pmx-hidden' : 'fa fa-search', | |
524 | isDisabled: (v, r, c, i, rec) => !!rec.data.leaf, | |
525 | }, | |
526 | { | |
527 | handler: 'onPrune', | |
528 | tooltip: gettext('Prune'), | |
529 | getClass: (v, m, rec) => rec.parentNode.id ==='root' ? 'fa fa-scissors' : 'pmx-hidden', | |
530 | isDisabled: (v, r, c, i, rec) => rec.parentNode.id !=='root', | |
531 | }, | |
532 | { | |
533 | handler: 'onForget', | |
534 | tooltip: gettext('Forget Snapshot'), | |
535 | getClass: (v, m, rec) => !rec.data.leaf && rec.parentNode.id !== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden', | |
536 | isDisabled: (v, r, c, i, rec) => rec.data.leaf || rec.parentNode.id === 'root', | |
537 | }, | |
538 | { | |
539 | handler: 'downloadFile', | |
540 | tooltip: gettext('Download'), | |
541 | getClass: (v, m, rec) => rec.data.leaf && rec.data.filename ? 'fa fa-download' : 'pmx-hidden', | |
542 | isDisabled: (v, r, c, i, rec) => !rec.data.leaf || !rec.data.filename || rec.data['crypt-mode'] > 2, | |
543 | }, | |
544 | { | |
545 | handler: 'openPxarBrowser', | |
546 | tooltip: gettext('Browse'), | |
547 | getClass: (v, m, rec) => { | |
548 | let data = rec.data; | |
549 | if (data.leaf && data.filename && data.filename.endsWith('pxar.didx')) { | |
550 | return 'fa fa-folder-open-o'; | |
551 | } | |
552 | return 'pmx-hidden'; | |
553 | }, | |
554 | isDisabled: (v, r, c, i, rec) => { | |
555 | let data = rec.data; | |
556 | return !(data.leaf && | |
557 | data.filename && | |
558 | data.filename.endsWith('pxar.didx') && | |
c96b0de4 | 559 | data['crypt-mode'] < 3); |
c6e07769 | 560 | }, |
3e395378 | 561 | }, |
c6e07769 | 562 | ], |
3e395378 | 563 | }, |
04b0ca8b DC |
564 | { |
565 | xtype: 'datecolumn', | |
566 | header: gettext('Backup Time'), | |
567 | sortable: true, | |
568 | dataIndex: 'backup-time', | |
569 | format: 'Y-m-d H:i:s', | |
c6e07769 | 570 | width: 150, |
04b0ca8b DC |
571 | }, |
572 | { | |
573 | header: gettext("Size"), | |
574 | sortable: true, | |
575 | dataIndex: 'size', | |
a7a5f56d | 576 | renderer: (v, meta, record) => { |
3e395378 DC |
577 | if (record.data.text === 'client.log.blob' && v === undefined) { |
578 | return ''; | |
579 | } | |
a7a5f56d TL |
580 | if (v === undefined || v === null) { |
581 | meta.tdCls = "x-grid-row-loading"; | |
582 | return ''; | |
583 | } | |
584 | return Proxmox.Utils.format_size(v); | |
585 | }, | |
04b0ca8b DC |
586 | }, |
587 | { | |
588 | xtype: 'numbercolumn', | |
589 | format: '0', | |
590 | header: gettext("Count"), | |
591 | sortable: true, | |
46388e6a TL |
592 | width: 75, |
593 | align: 'right', | |
04b0ca8b DC |
594 | dataIndex: 'count', |
595 | }, | |
596 | { | |
597 | header: gettext("Owner"), | |
598 | sortable: true, | |
599 | dataIndex: 'owner', | |
600 | }, | |
e005f953 DC |
601 | { |
602 | header: gettext('Encrypted'), | |
2774566b | 603 | dataIndex: 'crypt-mode', |
3e395378 | 604 | renderer: (v, meta, record) => { |
71282dd9 SR |
605 | if (record.data.size === undefined || record.data.size === null) { |
606 | return ''; | |
607 | } | |
3e395378 DC |
608 | if (v === -1) { |
609 | return ''; | |
610 | } | |
611 | let iconCls = PBS.Utils.cryptIconCls[v] || ''; | |
612 | let iconTxt = ""; | |
613 | if (iconCls) { | |
614 | iconTxt = `<i class="fa fa-fw fa-${iconCls}"></i> `; | |
615 | } | |
c6e07769 TL |
616 | return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText; |
617 | }, | |
04b0ca8b | 618 | }, |
7b212c1f TL |
619 | { |
620 | header: gettext('Verify State'), | |
621 | sortable: true, | |
622 | dataIndex: 'verification', | |
423df9b1 | 623 | width: 120, |
7b212c1f | 624 | renderer: (v, meta, record) => { |
423df9b1 TL |
625 | let i = (cls, txt) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`; |
626 | if (v === undefined || v === null) { | |
627 | return record.data.leaf ? '' : i('question-circle-o warning', gettext('None')); | |
7b212c1f | 628 | } |
423df9b1 | 629 | let tip, iconCls, txt; |
7b212c1f | 630 | if (record.parentNode.id === 'root') { |
423df9b1 TL |
631 | if (v.failed === 0) { |
632 | if (v.none === 0) { | |
633 | if (v.outdated > 0) { | |
634 | tip = 'All OK, but some snapshots were not verified in last 30 days'; | |
635 | iconCls = 'check warning'; | |
636 | txt = gettext('All OK (old)'); | |
637 | } else { | |
638 | tip = 'All snapshots verified at least once in last 30 days'; | |
639 | iconCls = 'check good'; | |
640 | txt = gettext('All OK'); | |
641 | } | |
642 | } else if (v.ok === 0) { | |
643 | tip = `${v.none} not verified yet`; | |
644 | iconCls = 'question-circle-o warning'; | |
645 | txt = gettext('None'); | |
646 | } else { | |
647 | tip = `${v.ok} OK, ${v.none} not verified yet`; | |
648 | iconCls = 'check faded'; | |
649 | txt = `${v.ok} OK`; | |
650 | } | |
651 | } else { | |
652 | tip = `${v.ok} OK, ${v.failed} failed, ${v.none} not verified yet`; | |
653 | iconCls = 'times critical'; | |
654 | txt = v.ok === 0 && v.none === 0 | |
655 | ? gettext('All failed') | |
656 | : `${v.failed} failed`; | |
657 | } | |
658 | } else if (!v.state) { | |
659 | return record.data.leaf ? '' : gettext('None'); | |
660 | } else { | |
661 | let verify_time = Proxmox.Utils.render_timestamp(v.lastTime); | |
662 | tip = `Last verify task started on ${verify_time}`; | |
663 | txt = v.state; | |
664 | iconCls = 'times critical'; | |
665 | if (v.state === 'ok') { | |
666 | iconCls = 'check good'; | |
667 | let now = Date.now() / 1000; | |
668 | if (now - v.lastTime > 30 * 24 * 60 * 60) { | |
669 | tip = `Last verify task over 30 days ago: ${verify_time}`; | |
670 | iconCls = 'check warning'; | |
671 | } | |
672 | } | |
7b212c1f TL |
673 | } |
674 | return `<span data-qtip="${tip}"> | |
423df9b1 | 675 | <i class="fa fa-fw fa-${iconCls}"></i> ${txt} |
7b212c1f TL |
676 | </span>`; |
677 | }, | |
678 | listeners: { | |
679 | dblclick: function(view, el, row, col, ev, rec) { | |
680 | let data = rec.data || {}; | |
681 | let verify = data.verification; | |
682 | if (verify && verify.upid && rec.parentNode.id !== 'root') { | |
683 | let win = Ext.create('Proxmox.window.TaskViewer', { | |
684 | upid: verify.upid, | |
685 | }); | |
686 | win.show(); | |
687 | } | |
688 | }, | |
689 | }, | |
690 | }, | |
04b0ca8b | 691 | ], |
b1127fd0 | 692 | |
04b0ca8b DC |
693 | tbar: [ |
694 | { | |
695 | text: gettext('Reload'), | |
696 | iconCls: 'fa fa-refresh', | |
697 | handler: 'reload', | |
698 | }, | |
cd92fd73 DC |
699 | '-', |
700 | { | |
701 | xtype: 'proxmoxButton', | |
702 | text: gettext('Verify All'), | |
8d6b6a04 | 703 | confirmMsg: gettext('Do you want to verify all snapshots now?'), |
cd92fd73 DC |
704 | handler: 'verifyAll', |
705 | }, | |
6d55603d DC |
706 | '->', |
707 | { | |
708 | xtype: 'tbtext', | |
709 | html: gettext('Search'), | |
710 | }, | |
711 | { | |
712 | xtype: 'textfield', | |
713 | reference: 'searchbox', | |
c3b1da9e | 714 | emptyText: gettext('group, date or owner'), |
6d55603d DC |
715 | triggers: { |
716 | clear: { | |
717 | cls: 'pmx-clear-trigger', | |
718 | weight: -1, | |
719 | hidden: true, | |
720 | handler: function() { | |
721 | this.triggers.clear.setVisible(false); | |
722 | this.setValue(''); | |
723 | }, | |
c6e07769 | 724 | }, |
6d55603d DC |
725 | }, |
726 | listeners: { | |
727 | change: { | |
728 | fn: 'search', | |
729 | buffer: 500, | |
730 | }, | |
731 | }, | |
c6e07769 | 732 | }, |
04b0ca8b | 733 | ], |
ca23a97f | 734 | }); |