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