]>
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', |
a7a5f56d | 13 | { name: 'size', type: 'int', allowNull: true, }, |
e005f953 | 14 | { |
2774566b | 15 | name: 'crypt-mode', |
676b0fde | 16 | type: 'boolean', |
e005f953 DC |
17 | calculate: function(data) { |
18 | let encrypted = 0; | |
2774566b DC |
19 | let crypt = { |
20 | none: 0, | |
21 | mixed: 0, | |
22 | 'sign-only': 0, | |
23 | encrypt: 0, | |
106603c5 | 24 | count: 0, |
2774566b DC |
25 | }; |
26 | let signed = 0; | |
e005f953 DC |
27 | data.files.forEach(file => { |
28 | if (file.filename === 'index.json.blob') return; // is never encrypted | |
2774566b DC |
29 | let mode = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); |
30 | if (mode !== -1) { | |
31 | crypt[file['crypt-mode']]++; | |
e005f953 | 32 | } |
106603c5 | 33 | crypt.count++; |
e005f953 DC |
34 | }); |
35 | ||
106603c5 | 36 | return PBS.Utils.calculateCryptMode(crypt); |
e005f953 DC |
37 | } |
38 | } | |
33839735 | 39 | ] |
ca23a97f DM |
40 | }); |
41 | ||
42 | Ext.define('PBS.DataStoreContent', { | |
e8f0ad19 | 43 | extend: 'Ext.tree.Panel', |
ca23a97f DM |
44 | alias: 'widget.pbsDataStoreContent', |
45 | ||
e8f0ad19 | 46 | rootVisible: false, |
507c39c5 | 47 | |
c0ac2074 DC |
48 | title: gettext('Content'), |
49 | ||
f1baa7f4 TL |
50 | controller: { |
51 | xclass: 'Ext.app.ViewController', | |
52 | ||
53 | init: function(view) { | |
54 | if (!view.datastore) { | |
55 | throw "no datastore specified"; | |
56 | } | |
57 | ||
3f98b347 | 58 | this.store = Ext.create('Ext.data.Store', { |
33839735 | 59 | model: 'pbs-data-store-snapshots', |
e8f0ad19 DM |
60 | groupField: 'backup-group', |
61 | }); | |
3f98b347 | 62 | this.store.on('load', this.onLoad, this); |
e8f0ad19 | 63 | |
7b1e2669 DC |
64 | view.getStore().setSorters([ |
65 | 'backup-group', | |
66 | 'text', | |
67 | 'backup-time' | |
68 | ]); | |
90779237 | 69 | Proxmox.Utils.monStoreErrors(view, this.store); |
f1baa7f4 TL |
70 | this.reload(); // initial load |
71 | }, | |
72 | ||
73 | reload: function() { | |
3f98b347 TL |
74 | let view = this.getView(); |
75 | ||
76 | if (!view.store || !this.store) { | |
77 | console.warn('cannot reload, no store(s)'); | |
78 | return; | |
79 | } | |
f1baa7f4 | 80 | |
e8f0ad19 | 81 | let url = `/api2/json/admin/datastore/${view.datastore}/snapshots`; |
3f98b347 | 82 | this.store.setProxy({ |
f1baa7f4 | 83 | type: 'proxmox', |
26f499b1 | 84 | timeout: 300*1000, // 5 minutes, we should make that api call faster |
f1baa7f4 TL |
85 | url: url |
86 | }); | |
e8f0ad19 | 87 | |
3f98b347 TL |
88 | this.store.load(); |
89 | }, | |
e8f0ad19 | 90 | |
3f98b347 TL |
91 | getRecordGroups: function(records) { |
92 | let groups = {}; | |
93 | ||
94 | for (const item of records) { | |
95 | var btype = item.data["backup-type"]; | |
96 | let group = btype + "/" + item.data["backup-id"]; | |
97 | ||
98 | if (groups[group] !== undefined) { | |
99 | continue; | |
100 | } | |
101 | ||
102 | var cls = ''; | |
103 | if (btype === 'vm') { | |
104 | cls = 'fa-desktop'; | |
105 | } else if (btype === 'ct') { | |
106 | cls = 'fa-cube'; | |
107 | } else if (btype === 'host') { | |
108 | cls = 'fa-building'; | |
109 | } else { | |
add5861e | 110 | console.warn(`got unknown backup-type '${btype}'`); |
3f98b347 TL |
111 | continue; // FIXME: auto render? what do? |
112 | } | |
113 | ||
114 | groups[group] = { | |
115 | text: group, | |
116 | leaf: false, | |
117 | iconCls: "fa " + cls, | |
118 | expanded: false, | |
119 | backup_type: item.data["backup-type"], | |
120 | backup_id: item.data["backup-id"], | |
121 | children: [] | |
aeee4329 | 122 | }; |
3f98b347 | 123 | } |
aeee4329 | 124 | |
3f98b347 TL |
125 | return groups; |
126 | }, | |
e8f0ad19 | 127 | |
90779237 | 128 | onLoad: function(store, records, success, operation) { |
3f98b347 TL |
129 | let view = this.getView(); |
130 | ||
131 | if (!success) { | |
90779237 | 132 | Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError())); |
3f98b347 TL |
133 | return; |
134 | } | |
135 | ||
136 | let groups = this.getRecordGroups(records); | |
e8f0ad19 | 137 | |
3f98b347 TL |
138 | for (const item of records) { |
139 | let group = item.data["backup-type"] + "/" + item.data["backup-id"]; | |
140 | let children = groups[group].children; | |
141 | ||
142 | let data = item.data; | |
143 | ||
f68ae22c | 144 | data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]); |
3f98b347 TL |
145 | data.leaf = true; |
146 | data.cls = 'no-leaf-icons'; | |
147 | ||
148 | children.push(data); | |
149 | } | |
150 | ||
151 | let children = []; | |
152 | for (const [_key, group] of Object.entries(groups)) { | |
153 | let last_backup = 0; | |
2774566b DC |
154 | let crypt = { |
155 | none: 0, | |
156 | mixed: 0, | |
157 | 'sign-only': 0, | |
106603c5 | 158 | encrypt: 0, |
2774566b | 159 | }; |
3f98b347 | 160 | for (const item of group.children) { |
2774566b | 161 | crypt[PBS.Utils.cryptmap[item['crypt-mode']]]++; |
48e22a89 | 162 | if (item["backup-time"] > last_backup && item.size !== null) { |
3f98b347 TL |
163 | last_backup = item["backup-time"]; |
164 | group["backup-time"] = last_backup; | |
165 | group.files = item.files; | |
166 | group.size = item.size; | |
04b0ca8b | 167 | group.owner = item.owner; |
3f98b347 | 168 | } |
e005f953 | 169 | |
3f98b347 TL |
170 | } |
171 | group.count = group.children.length; | |
106603c5 DC |
172 | crypt.count = group.count; |
173 | group['crypt-mode'] = PBS.Utils.calculateCryptMode(crypt); | |
3f98b347 TL |
174 | children.push(group); |
175 | } | |
176 | ||
177 | view.setRootNode({ | |
178 | expanded: true, | |
179 | children: children | |
180 | }); | |
90779237 | 181 | Proxmox.Utils.setErrorMask(view, false); |
f1baa7f4 | 182 | }, |
5f448992 DM |
183 | |
184 | onPrune: function() { | |
185 | var view = this.getView(); | |
186 | ||
187 | let rec = view.selModel.getSelection()[0]; | |
188 | if (!(rec && rec.data)) return; | |
189 | let data = rec.data; | |
190 | if (data.leaf) return; | |
191 | ||
192 | if (!view.datastore) return; | |
193 | ||
194 | let win = Ext.create('PBS.DataStorePrune', { | |
195 | datastore: view.datastore, | |
196 | backup_type: data.backup_type, | |
197 | backup_id: data.backup_id, | |
198 | }); | |
199 | win.on('destroy', this.reload, this); | |
200 | win.show(); | |
98425309 DC |
201 | }, |
202 | ||
8f6088c1 DM |
203 | onVerify: function() { |
204 | var view = this.getView(); | |
205 | ||
206 | if (!view.datastore) return; | |
207 | ||
208 | let rec = view.selModel.getSelection()[0]; | |
209 | if (!(rec && rec.data)) return; | |
210 | let data = rec.data; | |
211 | ||
212 | let params; | |
213 | ||
214 | if (data.leaf) { | |
215 | params = { | |
216 | "backup-type": data["backup-type"], | |
217 | "backup-id": data["backup-id"], | |
218 | "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), | |
219 | }; | |
220 | } else { | |
221 | params = { | |
222 | "backup-type": data.backup_type, | |
223 | "backup-id": data.backup_id, | |
224 | }; | |
225 | } | |
226 | ||
227 | Proxmox.Utils.API2Request({ | |
228 | params: params, | |
229 | url: `/admin/datastore/${view.datastore}/verify`, | |
230 | method: 'POST', | |
231 | failure: function(response) { | |
232 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
233 | }, | |
234 | success: function(response, options) { | |
235 | Ext.create('Proxmox.window.TaskViewer', { | |
236 | upid: response.result.data, | |
237 | }).show(); | |
238 | }, | |
239 | }); | |
240 | }, | |
241 | ||
4ff2c9b8 DM |
242 | onForget: function() { |
243 | var view = this.getView(); | |
244 | ||
245 | let rec = view.selModel.getSelection()[0]; | |
246 | if (!(rec && rec.data)) return; | |
247 | let data = rec.data; | |
248 | if (!data.leaf) return; | |
249 | ||
250 | if (!view.datastore) return; | |
251 | ||
252 | console.log(data); | |
253 | ||
254 | Proxmox.Utils.API2Request({ | |
255 | params: { | |
256 | "backup-type": data["backup-type"], | |
257 | "backup-id": data["backup-id"], | |
258 | "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), | |
259 | }, | |
260 | url: `/admin/datastore/${view.datastore}/snapshots`, | |
261 | method: 'DELETE', | |
262 | waitMsgTarget: view, | |
263 | failure: function(response, opts) { | |
264 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
265 | }, | |
266 | callback: this.reload.bind(this), | |
267 | }); | |
268 | }, | |
269 | ||
98425309 DC |
270 | openBackupFileDownloader: function() { |
271 | let me = this; | |
272 | let view = me.getView(); | |
273 | ||
274 | let rec = view.selModel.getSelection()[0]; | |
275 | if (!(rec && rec.data)) return; | |
276 | let data = rec.data; | |
277 | ||
278 | Ext.create('PBS.window.BackupFileDownloader', { | |
279 | baseurl: `/api2/json/admin/datastore/${view.datastore}`, | |
280 | params: { | |
281 | 'backup-id': data['backup-id'], | |
282 | 'backup-type': data['backup-type'], | |
283 | 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0), | |
284 | }, | |
285 | files: data.files, | |
286 | }).show(); | |
8567c0d2 DC |
287 | }, |
288 | ||
289 | openPxarBrowser: function() { | |
290 | let me = this; | |
291 | let view = me.getView(); | |
292 | ||
293 | let rec = view.selModel.getSelection()[0]; | |
294 | if (!(rec && rec.data)) return; | |
295 | let data = rec.data; | |
296 | ||
297 | let encrypted = false; | |
298 | data.files.forEach(file => { | |
2774566b | 299 | if (file.filename === 'catalog.pcat1.didx' && file['crypt-mode'] === 'encrypt') { |
8567c0d2 DC |
300 | encrypted = true; |
301 | } | |
302 | }); | |
303 | ||
304 | if (encrypted) { | |
305 | Ext.Msg.alert( | |
306 | gettext('Cannot open Catalog'), | |
307 | gettext('Only unencrypted Backups can be opened on the server. Please use the client with the decryption key instead.'), | |
308 | ); | |
309 | return; | |
310 | } | |
311 | ||
312 | let id = data['backup-id']; | |
313 | let time = data['backup-time']; | |
314 | let type = data['backup-type']; | |
315 | let timetext = PBS.Utils.render_datetime_utc(data["backup-time"]); | |
316 | ||
317 | Ext.create('PBS.window.FileBrowser', { | |
318 | title: `${type}/${id}/${timetext}`, | |
319 | datastore: view.datastore, | |
320 | 'backup-id': id, | |
321 | 'backup-time': (time.getTime()/1000).toFixed(0), | |
322 | 'backup-type': type, | |
323 | }).show(); | |
5f448992 | 324 | } |
f1baa7f4 TL |
325 | }, |
326 | ||
04b0ca8b DC |
327 | columns: [ |
328 | { | |
329 | xtype: 'treecolumn', | |
330 | header: gettext("Backup Group"), | |
331 | dataIndex: 'text', | |
332 | flex: 1 | |
333 | }, | |
334 | { | |
335 | xtype: 'datecolumn', | |
336 | header: gettext('Backup Time'), | |
337 | sortable: true, | |
338 | dataIndex: 'backup-time', | |
339 | format: 'Y-m-d H:i:s', | |
340 | width: 150 | |
341 | }, | |
342 | { | |
343 | header: gettext("Size"), | |
344 | sortable: true, | |
345 | dataIndex: 'size', | |
a7a5f56d TL |
346 | renderer: (v, meta, record) => { |
347 | if (v === undefined || v === null) { | |
348 | meta.tdCls = "x-grid-row-loading"; | |
349 | return ''; | |
350 | } | |
351 | return Proxmox.Utils.format_size(v); | |
352 | }, | |
04b0ca8b DC |
353 | }, |
354 | { | |
355 | xtype: 'numbercolumn', | |
356 | format: '0', | |
357 | header: gettext("Count"), | |
358 | sortable: true, | |
359 | dataIndex: 'count', | |
360 | }, | |
361 | { | |
362 | header: gettext("Owner"), | |
363 | sortable: true, | |
364 | dataIndex: 'owner', | |
365 | }, | |
e005f953 DC |
366 | { |
367 | header: gettext('Encrypted'), | |
2774566b DC |
368 | dataIndex: 'crypt-mode', |
369 | renderer: value => PBS.Utils.cryptText[value] || Proxmox.Utils.unknownText, | |
e005f953 | 370 | }, |
04b0ca8b DC |
371 | { |
372 | header: gettext("Files"), | |
373 | sortable: false, | |
374 | dataIndex: 'files', | |
1c090810 DC |
375 | renderer: function(files) { |
376 | return files.map((file) => { | |
e005f953 DC |
377 | let icon = ''; |
378 | let size = ''; | |
2774566b DC |
379 | let mode = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); |
380 | let iconCls = PBS.Utils.cryptIconCls[mode] || ''; | |
381 | if (iconCls !== '') { | |
382 | icon = `<i class="fa fa-${iconCls}"></i> `; | |
e005f953 DC |
383 | } |
384 | if (file.size) { | |
385 | size = ` (${Proxmox.Utils.format_size(file.size)})`; | |
386 | } | |
387 | return `${icon}${file.filename}${size}`; | |
1c090810 DC |
388 | }).join(', '); |
389 | }, | |
04b0ca8b DC |
390 | flex: 2 |
391 | }, | |
392 | ], | |
b1127fd0 | 393 | |
04b0ca8b DC |
394 | tbar: [ |
395 | { | |
396 | text: gettext('Reload'), | |
397 | iconCls: 'fa fa-refresh', | |
398 | handler: 'reload', | |
399 | }, | |
69e5d719 | 400 | '-', |
8f6088c1 DM |
401 | { |
402 | xtype: 'proxmoxButton', | |
403 | text: gettext('Verify'), | |
404 | disabled: true, | |
405 | parentXType: 'pbsDataStoreContent', | |
69e5d719 | 406 | enableFn: (rec) => !!rec.data && rec.data.size !== null, |
8f6088c1 DM |
407 | handler: 'onVerify', |
408 | }, | |
04b0ca8b DC |
409 | { |
410 | xtype: 'proxmoxButton', | |
b1127fd0 DM |
411 | text: gettext('Prune'), |
412 | disabled: true, | |
f856e077 | 413 | parentXType: 'pbsDataStoreContent', |
69e5d719 | 414 | enableFn: (rec) => !rec.data.leaf, |
5f448992 | 415 | handler: 'onPrune', |
98425309 | 416 | }, |
4ff2c9b8 DM |
417 | { |
418 | xtype: 'proxmoxButton', | |
419 | text: gettext('Forget'), | |
420 | disabled: true, | |
421 | parentXType: 'pbsDataStoreContent', | |
422 | handler: 'onForget', | |
69e5d719 | 423 | dangerous: true, |
4ff2c9b8 | 424 | confirmMsg: function(record) { |
69e5d719 | 425 | //console.log(record); |
4ff2c9b8 DM |
426 | let name = record.data.text; |
427 | return Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${name}'`); | |
428 | }, | |
69e5d719 | 429 | enableFn: (rec) => !!rec.data.leaf && rec.data.size !== null, |
4ff2c9b8 | 430 | }, |
69e5d719 | 431 | '-', |
98425309 DC |
432 | { |
433 | xtype: 'proxmoxButton', | |
434 | text: gettext('Download Files'), | |
435 | disabled: true, | |
436 | parentXType: 'pbsDataStoreContent', | |
437 | handler: 'openBackupFileDownloader', | |
69e5d719 | 438 | enableFn: (rec) => !!rec.data.leaf && rec.data.size !== null, |
8567c0d2 DC |
439 | }, |
440 | { | |
441 | xtype: "proxmoxButton", | |
442 | text: gettext('PXAR File Browser'), | |
443 | disabled: true, | |
444 | handler: 'openPxarBrowser', | |
445 | parentXType: 'pbsDataStoreContent', | |
446 | enableFn: function(record) { | |
69e5d719 | 447 | return !!record.data.leaf && record.size !== null && record.data.files.some(el => el.filename.endsWith('pxar.didx')); |
8567c0d2 | 448 | }, |
04b0ca8b DC |
449 | } |
450 | ], | |
ca23a97f | 451 | }); |