]> git.proxmox.com Git - proxmox-backup.git/blame - www/DataStoreContent.js
ui: sync job: group remote fields and use "Source" in labels
[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',
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
42Ext.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});