]> git.proxmox.com Git - proxmox-backup.git/blob - www/DataStoreContent.js
ui: show proper loadMask for DataStoreContent
[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' },
14 {
15 name: 'encrypted',
16 type: 'boolean',
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) {
29 return 0;
30 } else if (encrypted < files) {
31 return 1;
32 } else {
33 return 2;
34 }
35 }
36 }
37 ]
38 });
39
40 Ext.define('PBS.DataStoreContent', {
41 extend: 'Ext.tree.Panel',
42 alias: 'widget.pbsDataStoreContent',
43
44 rootVisible: false,
45
46 title: gettext('Content'),
47
48 controller: {
49 xclass: 'Ext.app.ViewController',
50
51 init: function(view) {
52 if (!view.datastore) {
53 throw "no datastore specified";
54 }
55
56 this.store = Ext.create('Ext.data.Store', {
57 model: 'pbs-data-store-snapshots',
58 groupField: 'backup-group',
59 });
60 this.store.on('load', this.onLoad, this);
61
62 view.getStore().setSorters([
63 'backup-group',
64 'text',
65 'backup-time'
66 ]);
67 Proxmox.Utils.monStoreErrors(view, this.store);
68 this.reload(); // initial load
69 },
70
71 reload: function() {
72 let view = this.getView();
73
74 if (!view.store || !this.store) {
75 console.warn('cannot reload, no store(s)');
76 return;
77 }
78
79 let url = `/api2/json/admin/datastore/${view.datastore}/snapshots`;
80 this.store.setProxy({
81 type: 'proxmox',
82 url: url
83 });
84
85 this.store.load();
86 },
87
88 getRecordGroups: function(records) {
89 let groups = {};
90
91 for (const item of records) {
92 var btype = item.data["backup-type"];
93 let group = btype + "/" + item.data["backup-id"];
94
95 if (groups[group] !== undefined) {
96 continue;
97 }
98
99 var cls = '';
100 if (btype === 'vm') {
101 cls = 'fa-desktop';
102 } else if (btype === 'ct') {
103 cls = 'fa-cube';
104 } else if (btype === 'host') {
105 cls = 'fa-building';
106 } else {
107 console.warn(`got unknown backup-type '${btype}'`);
108 continue; // FIXME: auto render? what do?
109 }
110
111 groups[group] = {
112 text: group,
113 leaf: false,
114 iconCls: "fa " + cls,
115 expanded: false,
116 backup_type: item.data["backup-type"],
117 backup_id: item.data["backup-id"],
118 children: []
119 };
120 }
121
122 return groups;
123 },
124
125 onLoad: function(store, records, success, operation) {
126 let view = this.getView();
127
128 if (!success) {
129 Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError()));
130 return;
131 }
132
133 let groups = this.getRecordGroups(records);
134
135 for (const item of records) {
136 let group = item.data["backup-type"] + "/" + item.data["backup-id"];
137 let children = groups[group].children;
138
139 let data = item.data;
140
141 data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]);
142 data.leaf = true;
143 data.cls = 'no-leaf-icons';
144
145 children.push(data);
146 }
147
148 let children = [];
149 for (const [_key, group] of Object.entries(groups)) {
150 let last_backup = 0;
151 let encrypted = 0;
152 for (const item of group.children) {
153 if (item.encrypted > 0) {
154 encrypted++;
155 }
156 if (item["backup-time"] > last_backup) {
157 last_backup = item["backup-time"];
158 group["backup-time"] = last_backup;
159 group.files = item.files;
160 group.size = item.size;
161 group.owner = item.owner;
162 }
163
164 }
165 if (encrypted === 0) {
166 group.encrypted = 0;
167 } else if (encrypted < group.children.length) {
168 group.encrypted = 1;
169 } else {
170 group.encrypted = 2;
171 }
172 group.count = group.children.length;
173 children.push(group);
174 }
175
176 view.setRootNode({
177 expanded: true,
178 children: children
179 });
180 Proxmox.Utils.setErrorMask(view, false);
181 },
182
183 onPrune: function() {
184 var view = this.getView();
185
186 let rec = view.selModel.getSelection()[0];
187 if (!(rec && rec.data)) return;
188 let data = rec.data;
189 if (data.leaf) return;
190
191 if (!view.datastore) return;
192
193 let win = Ext.create('PBS.DataStorePrune', {
194 datastore: view.datastore,
195 backup_type: data.backup_type,
196 backup_id: data.backup_id,
197 });
198 win.on('destroy', this.reload, this);
199 win.show();
200 },
201
202 openBackupFileDownloader: function() {
203 let me = this;
204 let view = me.getView();
205
206 let rec = view.selModel.getSelection()[0];
207 if (!(rec && rec.data)) return;
208 let data = rec.data;
209
210 Ext.create('PBS.window.BackupFileDownloader', {
211 baseurl: `/api2/json/admin/datastore/${view.datastore}`,
212 params: {
213 'backup-id': data['backup-id'],
214 'backup-type': data['backup-type'],
215 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0),
216 },
217 files: data.files,
218 }).show();
219 },
220
221 openPxarBrowser: function() {
222 let me = this;
223 let view = me.getView();
224
225 let rec = view.selModel.getSelection()[0];
226 if (!(rec && rec.data)) return;
227 let data = rec.data;
228
229 let encrypted = false;
230 data.files.forEach(file => {
231 if (file.filename === 'catalog.pcat1.didx' && file.encrypted) {
232 encrypted = true;
233 }
234 });
235
236 if (encrypted) {
237 Ext.Msg.alert(
238 gettext('Cannot open Catalog'),
239 gettext('Only unencrypted Backups can be opened on the server. Please use the client with the decryption key instead.'),
240 );
241 return;
242 }
243
244 let id = data['backup-id'];
245 let time = data['backup-time'];
246 let type = data['backup-type'];
247 let timetext = PBS.Utils.render_datetime_utc(data["backup-time"]);
248
249 Ext.create('PBS.window.FileBrowser', {
250 title: `${type}/${id}/${timetext}`,
251 datastore: view.datastore,
252 'backup-id': id,
253 'backup-time': (time.getTime()/1000).toFixed(0),
254 'backup-type': type,
255 }).show();
256 }
257 },
258
259 columns: [
260 {
261 xtype: 'treecolumn',
262 header: gettext("Backup Group"),
263 dataIndex: 'text',
264 flex: 1
265 },
266 {
267 xtype: 'datecolumn',
268 header: gettext('Backup Time'),
269 sortable: true,
270 dataIndex: 'backup-time',
271 format: 'Y-m-d H:i:s',
272 width: 150
273 },
274 {
275 header: gettext("Size"),
276 sortable: true,
277 dataIndex: 'size',
278 renderer: Proxmox.Utils.format_size,
279 },
280 {
281 xtype: 'numbercolumn',
282 format: '0',
283 header: gettext("Count"),
284 sortable: true,
285 dataIndex: 'count',
286 },
287 {
288 header: gettext("Owner"),
289 sortable: true,
290 dataIndex: 'owner',
291 },
292 {
293 header: gettext('Encrypted'),
294 dataIndex: 'encrypted',
295 renderer: function(value) {
296 switch (value) {
297 case 0: return Proxmox.Utils.noText;
298 case 1: return gettext('Mixed');
299 case 2: return Proxmox.Utils.yesText;
300 default: Proxmox.Utils.unknownText;
301 }
302 }
303 },
304 {
305 header: gettext("Files"),
306 sortable: false,
307 dataIndex: 'files',
308 renderer: function(files) {
309 return files.map((file) => {
310 let icon = '';
311 let size = '';
312 if (file.encrypted) {
313 icon = '<i class="fa fa-lock"></i> ';
314 }
315 if (file.size) {
316 size = ` (${Proxmox.Utils.format_size(file.size)})`;
317 }
318 return `${icon}${file.filename}${size}`;
319 }).join(', ');
320 },
321 flex: 2
322 },
323 ],
324
325 tbar: [
326 {
327 text: gettext('Reload'),
328 iconCls: 'fa fa-refresh',
329 handler: 'reload',
330 },
331 {
332 xtype: 'proxmoxButton',
333 text: gettext('Prune'),
334 disabled: true,
335 parentXType: 'pbsDataStoreContent',
336 enableFn: function(record) { return !record.data.leaf; },
337 handler: 'onPrune',
338 },
339 {
340 xtype: 'proxmoxButton',
341 text: gettext('Download Files'),
342 disabled: true,
343 parentXType: 'pbsDataStoreContent',
344 handler: 'openBackupFileDownloader',
345 enableFn: function(record) {
346 return !!record.data.leaf;
347 },
348 },
349 {
350 xtype: "proxmoxButton",
351 text: gettext('PXAR File Browser'),
352 disabled: true,
353 handler: 'openPxarBrowser',
354 parentXType: 'pbsDataStoreContent',
355 enableFn: function(record) {
356 return !!record.data.leaf && record.data.files.some(el => el.filename.endsWith('pxar.didx'));
357 },
358 }
359 ],
360 });