]> git.proxmox.com Git - proxmox-backup.git/blame - www/DataStoreContent.js
more xdg cleanup and encryption parameter improvements
[proxmox-backup.git] / www / DataStoreContent.js
CommitLineData
33839735 1Ext.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',
e8f0ad19 13 { name: 'size', type: 'int' },
e005f953
DC
14 {
15 name: 'encrypted',
676b0fde 16 type: 'boolean',
e005f953
DC
17 calculate: function(data) {
18 let encrypted = 0;
19 let files = 0;
20 data.files.forEach(file => {
21 if (file.filename === 'index.json.blob') return; // is never encrypted
22 if (file.encrypted) {
23 encrypted++;
24 }
25 files++;
26 });
27
28 if (encrypted === 0) {
676b0fde 29 return 0;
e005f953 30 } else if (encrypted < files) {
676b0fde
DC
31 return 1;
32 } else {
33 return 2;
e005f953 34 }
e005f953
DC
35 }
36 }
33839735 37 ]
ca23a97f
DM
38});
39
40Ext.define('PBS.DataStoreContent', {
e8f0ad19 41 extend: 'Ext.tree.Panel',
ca23a97f
DM
42 alias: 'widget.pbsDataStoreContent',
43
e8f0ad19 44 rootVisible: false,
507c39c5 45
c0ac2074
DC
46 title: gettext('Content'),
47
f1baa7f4
TL
48 controller: {
49 xclass: 'Ext.app.ViewController',
50
51 init: function(view) {
52 if (!view.datastore) {
53 throw "no datastore specified";
54 }
55
3f98b347 56 this.store = Ext.create('Ext.data.Store', {
33839735 57 model: 'pbs-data-store-snapshots',
e8f0ad19
DM
58 groupField: 'backup-group',
59 });
3f98b347 60 this.store.on('load', this.onLoad, this);
e8f0ad19 61
7b1e2669
DC
62 view.getStore().setSorters([
63 'backup-group',
64 'text',
65 'backup-time'
66 ]);
90779237 67 Proxmox.Utils.monStoreErrors(view, this.store);
f1baa7f4
TL
68 this.reload(); // initial load
69 },
70
71 reload: function() {
3f98b347
TL
72 let view = this.getView();
73
74 if (!view.store || !this.store) {
75 console.warn('cannot reload, no store(s)');
76 return;
77 }
f1baa7f4 78
e8f0ad19 79 let url = `/api2/json/admin/datastore/${view.datastore}/snapshots`;
3f98b347 80 this.store.setProxy({
f1baa7f4 81 type: 'proxmox',
26f499b1 82 timeout: 300*1000, // 5 minutes, we should make that api call faster
f1baa7f4
TL
83 url: url
84 });
e8f0ad19 85
3f98b347
TL
86 this.store.load();
87 },
e8f0ad19 88
3f98b347
TL
89 getRecordGroups: function(records) {
90 let groups = {};
91
92 for (const item of records) {
93 var btype = item.data["backup-type"];
94 let group = btype + "/" + item.data["backup-id"];
95
96 if (groups[group] !== undefined) {
97 continue;
98 }
99
100 var cls = '';
101 if (btype === 'vm') {
102 cls = 'fa-desktop';
103 } else if (btype === 'ct') {
104 cls = 'fa-cube';
105 } else if (btype === 'host') {
106 cls = 'fa-building';
107 } else {
add5861e 108 console.warn(`got unknown backup-type '${btype}'`);
3f98b347
TL
109 continue; // FIXME: auto render? what do?
110 }
111
112 groups[group] = {
113 text: group,
114 leaf: false,
115 iconCls: "fa " + cls,
116 expanded: false,
117 backup_type: item.data["backup-type"],
118 backup_id: item.data["backup-id"],
119 children: []
aeee4329 120 };
3f98b347 121 }
aeee4329 122
3f98b347
TL
123 return groups;
124 },
e8f0ad19 125
90779237 126 onLoad: function(store, records, success, operation) {
3f98b347
TL
127 let view = this.getView();
128
129 if (!success) {
90779237 130 Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError()));
3f98b347
TL
131 return;
132 }
133
134 let groups = this.getRecordGroups(records);
e8f0ad19 135
3f98b347
TL
136 for (const item of records) {
137 let group = item.data["backup-type"] + "/" + item.data["backup-id"];
138 let children = groups[group].children;
139
140 let data = item.data;
141
f68ae22c 142 data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]);
3f98b347
TL
143 data.leaf = true;
144 data.cls = 'no-leaf-icons';
145
146 children.push(data);
147 }
148
149 let children = [];
150 for (const [_key, group] of Object.entries(groups)) {
151 let last_backup = 0;
676b0fde 152 let encrypted = 0;
3f98b347 153 for (const item of group.children) {
676b0fde
DC
154 if (item.encrypted > 0) {
155 encrypted++;
156 }
3f98b347
TL
157 if (item["backup-time"] > last_backup) {
158 last_backup = item["backup-time"];
159 group["backup-time"] = last_backup;
160 group.files = item.files;
161 group.size = item.size;
04b0ca8b 162 group.owner = item.owner;
3f98b347 163 }
e005f953 164
3f98b347 165 }
676b0fde
DC
166 if (encrypted === 0) {
167 group.encrypted = 0;
168 } else if (encrypted < group.children.length) {
169 group.encrypted = 1;
170 } else {
171 group.encrypted = 2;
172 }
3f98b347
TL
173 group.count = group.children.length;
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 => {
299 if (file.filename === 'catalog.pcat1.didx' && file.encrypted) {
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',
346 renderer: Proxmox.Utils.format_size,
347 },
348 {
349 xtype: 'numbercolumn',
350 format: '0',
351 header: gettext("Count"),
352 sortable: true,
353 dataIndex: 'count',
354 },
355 {
356 header: gettext("Owner"),
357 sortable: true,
358 dataIndex: 'owner',
359 },
e005f953
DC
360 {
361 header: gettext('Encrypted'),
362 dataIndex: 'encrypted',
676b0fde
DC
363 renderer: function(value) {
364 switch (value) {
365 case 0: return Proxmox.Utils.noText;
366 case 1: return gettext('Mixed');
367 case 2: return Proxmox.Utils.yesText;
368 default: Proxmox.Utils.unknownText;
369 }
370 }
e005f953 371 },
04b0ca8b
DC
372 {
373 header: gettext("Files"),
374 sortable: false,
375 dataIndex: 'files',
1c090810
DC
376 renderer: function(files) {
377 return files.map((file) => {
e005f953
DC
378 let icon = '';
379 let size = '';
380 if (file.encrypted) {
381 icon = '<i class="fa fa-lock"></i> ';
382 }
383 if (file.size) {
384 size = ` (${Proxmox.Utils.format_size(file.size)})`;
385 }
386 return `${icon}${file.filename}${size}`;
1c090810
DC
387 }).join(', ');
388 },
04b0ca8b
DC
389 flex: 2
390 },
391 ],
b1127fd0 392
04b0ca8b
DC
393 tbar: [
394 {
395 text: gettext('Reload'),
396 iconCls: 'fa fa-refresh',
397 handler: 'reload',
398 },
8f6088c1
DM
399 {
400 xtype: 'proxmoxButton',
401 text: gettext('Verify'),
402 disabled: true,
403 parentXType: 'pbsDataStoreContent',
404 enableFn: function(record) { return !!record.data; },
405 handler: 'onVerify',
406 },
04b0ca8b
DC
407 {
408 xtype: 'proxmoxButton',
b1127fd0
DM
409 text: gettext('Prune'),
410 disabled: true,
f856e077 411 parentXType: 'pbsDataStoreContent',
5f448992
DM
412 enableFn: function(record) { return !record.data.leaf; },
413 handler: 'onPrune',
98425309 414 },
4ff2c9b8
DM
415 {
416 xtype: 'proxmoxButton',
417 text: gettext('Forget'),
418 disabled: true,
419 parentXType: 'pbsDataStoreContent',
420 handler: 'onForget',
421 confirmMsg: function(record) {
422 console.log(record);
423 let name = record.data.text;
424 return Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${name}'`);
425 },
426 enableFn: function(record) {
427 return !!record.data.leaf;
428 },
429 },
98425309
DC
430 {
431 xtype: 'proxmoxButton',
432 text: gettext('Download Files'),
433 disabled: true,
434 parentXType: 'pbsDataStoreContent',
435 handler: 'openBackupFileDownloader',
436 enableFn: function(record) {
437 return !!record.data.leaf;
438 },
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) {
447 return !!record.data.leaf && record.data.files.some(el => el.filename.endsWith('pxar.didx'));
448 },
04b0ca8b
DC
449 }
450 ],
ca23a97f 451});