]> git.proxmox.com Git - proxmox-backup.git/blob - www/DataStoreContent.js
move subscription API path to /nodes
[proxmox-backup.git] / www / DataStoreContent.js
1 Ext.define('pbs-data-store-snapshots', {
2 extend: 'Ext.data.Model',
3 fields: [
4 'backup-type',
5 'backup-id',
6 {
7 name: 'backup-time',
8 type: 'date',
9 dateFormat: 'timestamp'
10 },
11 'files',
12 'owner',
13 { name: 'size', type: 'int', allowNull: true, },
14 {
15 name: 'crypt-mode',
16 type: 'boolean',
17 calculate: function(data) {
18 let encrypted = 0;
19 let crypt = {
20 none: 0,
21 mixed: 0,
22 'sign-only': 0,
23 encrypt: 0,
24 count: 0,
25 };
26 let signed = 0;
27 data.files.forEach(file => {
28 if (file.filename === 'index.json.blob') return; // is never encrypted
29 let mode = PBS.Utils.cryptmap.indexOf(file['crypt-mode']);
30 if (mode !== -1) {
31 crypt[file['crypt-mode']]++;
32 }
33 crypt.count++;
34 });
35
36 return PBS.Utils.calculateCryptMode(crypt);
37 }
38 }
39 ]
40 });
41
42 Ext.define('PBS.DataStoreContent', {
43 extend: 'Ext.tree.Panel',
44 alias: 'widget.pbsDataStoreContent',
45
46 rootVisible: false,
47
48 title: gettext('Content'),
49
50 controller: {
51 xclass: 'Ext.app.ViewController',
52
53 init: function(view) {
54 if (!view.datastore) {
55 throw "no datastore specified";
56 }
57
58 this.store = Ext.create('Ext.data.Store', {
59 model: 'pbs-data-store-snapshots',
60 groupField: 'backup-group',
61 });
62 this.store.on('load', this.onLoad, this);
63
64 view.getStore().setSorters([
65 'backup-group',
66 'text',
67 'backup-time'
68 ]);
69 Proxmox.Utils.monStoreErrors(view, this.store);
70 this.reload(); // initial load
71 },
72
73 reload: function() {
74 let view = this.getView();
75
76 if (!view.store || !this.store) {
77 console.warn('cannot reload, no store(s)');
78 return;
79 }
80
81 let url = `/api2/json/admin/datastore/${view.datastore}/snapshots`;
82 this.store.setProxy({
83 type: 'proxmox',
84 timeout: 300*1000, // 5 minutes, we should make that api call faster
85 url: url
86 });
87
88 this.store.load();
89 },
90
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 {
110 console.warn(`got unknown backup-type '${btype}'`);
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: []
122 };
123 }
124
125 return groups;
126 },
127
128 onLoad: function(store, records, success, operation) {
129 let view = this.getView();
130
131 if (!success) {
132 Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError()));
133 return;
134 }
135
136 let groups = this.getRecordGroups(records);
137
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
144 data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]);
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;
154 let crypt = {
155 none: 0,
156 mixed: 0,
157 'sign-only': 0,
158 encrypt: 0,
159 };
160 for (const item of group.children) {
161 crypt[PBS.Utils.cryptmap[item['crypt-mode']]]++;
162 if (item["backup-time"] > last_backup && item.size !== null) {
163 last_backup = item["backup-time"];
164 group["backup-time"] = last_backup;
165 group.files = item.files;
166 group.size = item.size;
167 group.owner = item.owner;
168 }
169
170 }
171 group.count = group.children.length;
172 crypt.count = group.count;
173 group['crypt-mode'] = PBS.Utils.calculateCryptMode(crypt);
174 children.push(group);
175 }
176
177 view.setRootNode({
178 expanded: true,
179 children: children
180 });
181 Proxmox.Utils.setErrorMask(view, false);
182 },
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();
201 },
202
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
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
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();
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['crypt-mode'] === 'encrypt') {
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();
324 }
325 },
326
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: (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 },
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 },
366 {
367 header: gettext('Encrypted'),
368 dataIndex: 'crypt-mode',
369 renderer: value => PBS.Utils.cryptText[value] || Proxmox.Utils.unknownText,
370 },
371 {
372 header: gettext("Files"),
373 sortable: false,
374 dataIndex: 'files',
375 renderer: function(files) {
376 return files.map((file) => {
377 let icon = '';
378 let size = '';
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> `;
383 }
384 if (file.size) {
385 size = ` (${Proxmox.Utils.format_size(file.size)})`;
386 }
387 return `${icon}${file.filename}${size}`;
388 }).join(', ');
389 },
390 flex: 2
391 },
392 ],
393
394 tbar: [
395 {
396 text: gettext('Reload'),
397 iconCls: 'fa fa-refresh',
398 handler: 'reload',
399 },
400 '-',
401 {
402 xtype: 'proxmoxButton',
403 text: gettext('Verify'),
404 disabled: true,
405 parentXType: 'pbsDataStoreContent',
406 enableFn: (rec) => !!rec.data && rec.data.size !== null,
407 handler: 'onVerify',
408 },
409 {
410 xtype: 'proxmoxButton',
411 text: gettext('Prune'),
412 disabled: true,
413 parentXType: 'pbsDataStoreContent',
414 enableFn: (rec) => !rec.data.leaf,
415 handler: 'onPrune',
416 },
417 {
418 xtype: 'proxmoxButton',
419 text: gettext('Forget'),
420 disabled: true,
421 parentXType: 'pbsDataStoreContent',
422 handler: 'onForget',
423 dangerous: true,
424 confirmMsg: function(record) {
425 //console.log(record);
426 let name = record.data.text;
427 return Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${name}'`);
428 },
429 enableFn: (rec) => !!rec.data.leaf && rec.data.size !== null,
430 },
431 '-',
432 {
433 xtype: 'proxmoxButton',
434 text: gettext('Download Files'),
435 disabled: true,
436 parentXType: 'pbsDataStoreContent',
437 handler: 'openBackupFileDownloader',
438 enableFn: (rec) => !!rec.data.leaf && rec.data.size !== null,
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.size !== null && record.data.files.some(el => el.filename.endsWith('pxar.didx'));
448 },
449 }
450 ],
451 });