]>
git.proxmox.com Git - proxmox-backup.git/blob - www/DataStoreContent.js
1 Ext
.define('pbs-data-store-snapshots', {
2 extend
: 'Ext.data.Model',
9 dateFormat
: 'timestamp'
13 { name
: 'size', type
: 'int', allowNull
: true, },
17 calculate: function(data
) {
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']);
31 crypt
[file
['crypt-mode']]++;
36 return PBS
.Utils
.calculateCryptMode(crypt
);
40 name
: 'matchesFilter',
47 Ext
.define('PBS.DataStoreContent', {
48 extend
: 'Ext.tree.Panel',
49 alias
: 'widget.pbsDataStoreContent',
53 title
: gettext('Content'),
56 xclass
: 'Ext.app.ViewController',
58 init: function(view
) {
59 if (!view
.datastore
) {
60 throw "no datastore specified";
63 this.store
= Ext
.create('Ext.data.Store', {
64 model
: 'pbs-data-store-snapshots',
65 groupField
: 'backup-group',
67 this.store
.on('load', this.onLoad
, this);
69 view
.getStore().setSorters([
74 Proxmox
.Utils
.monStoreErrors(view
, this.store
);
75 this.reload(); // initial load
79 let view
= this.getView();
81 if (!view
.store
|| !this.store
) {
82 console
.warn('cannot reload, no store(s)');
86 let url
= `/api2/json/admin/datastore/${view.datastore}/snapshots`;
89 timeout
: 300*1000, // 5 minutes, we should make that api call faster
96 getRecordGroups: function(records
) {
99 for (const item
of records
) {
100 var btype
= item
.data
["backup-type"];
101 let group
= btype
+ "/" + item
.data
["backup-id"];
103 if (groups
[group
] !== undefined) {
108 if (btype
=== 'vm') {
110 } else if (btype
=== 'ct') {
112 } else if (btype
=== 'host') {
115 console
.warn(`got unknown backup-type '${btype}'`);
116 continue; // FIXME: auto render? what do?
122 iconCls
: "fa " + cls
,
124 backup_type
: item
.data
["backup-type"],
125 backup_id
: item
.data
["backup-id"],
133 onLoad: function(store
, records
, success
, operation
) {
135 let view
= this.getView();
138 Proxmox
.Utils
.setErrorMask(view
, Proxmox
.Utils
.getResponseErrorMessage(operation
.getError()));
142 let groups
= this.getRecordGroups(records
);
147 view
.getSelection().some(function(item
) {
148 let id
= item
.data
.text
;
149 if (item
.data
.leaf
) {
150 id
= item
.parentNode
.data
.text
+ id
;
156 view
.getRootNode().cascadeBy({
158 if (item
.isExpanded() && !item
.data
.leaf
) {
159 let id
= item
.data
.text
;
168 for (const item
of records
) {
169 let group
= item
.data
["backup-type"] + "/" + item
.data
["backup-id"];
170 let children
= groups
[group
].children
;
172 let data
= item
.data
;
174 data
.text
= group
+ '/' + PBS
.Utils
.render_datetime_utc(data
["backup-time"]);
176 data
.cls
= 'no-leaf-icons';
177 data
.matchesFilter
= true;
179 data
.expanded
= !!expanded
[data
.text
];
182 for (const file
of data
.files
) {
183 file
.text
= file
.filename
,
184 file
['crypt-mode'] = PBS
.Utils
.cryptmap
.indexOf(file
['crypt-mode']);
186 file
.matchesFilter
= true;
188 data
.children
.push(file
);
195 for (const [name
, group
] of Object
.entries(groups
)) {
203 for (const item
of group
.children
) {
204 crypt
[PBS
.Utils
.cryptmap
[item
['crypt-mode']]]++;
205 if (item
["backup-time"] > last_backup
&& item
.size
!== null) {
206 last_backup
= item
["backup-time"];
207 group
["backup-time"] = last_backup
;
208 group
.files
= item
.files
;
209 group
.size
= item
.size
;
210 group
.owner
= item
.owner
;
214 group
.count
= group
.children
.length
;
215 group
.matchesFilter
= true;
216 crypt
.count
= group
.count
;
217 group
['crypt-mode'] = PBS
.Utils
.calculateCryptMode(crypt
);
218 group
.expanded
= !!expanded
[name
];
219 children
.push(group
);
227 if (selected
!== undefined) {
228 let selection
= view
.getRootNode().findChildBy(function(item
) {
229 let id
= item
.data
.text
;
230 if (item
.data
.leaf
) {
231 id
= item
.parentNode
.data
.text
+ id
;
233 return selected
=== id
;
235 view
.setSelection(selection
);
236 view
.getView().focusRow(selection
);
239 Proxmox
.Utils
.setErrorMask(view
, false);
240 if (view
.getStore().getFilters().length
> 0) {
241 let searchBox
= me
.lookup("searchbox");
242 let searchvalue
= searchBox
.getValue();;
243 me
.search(searchBox
, searchvalue
);
247 onPrune: function(view
, rI
, cI
, item
, e
, rec
) {
248 var view
= this.getView();
250 if (!(rec
&& rec
.data
)) return;
252 if (rec
.parentNode
.id
!== 'root') return;
254 if (!view
.datastore
) return;
256 let win
= Ext
.create('PBS.DataStorePrune', {
257 datastore
: view
.datastore
,
258 backup_type
: data
.backup_type
,
259 backup_id
: data
.backup_id
,
261 win
.on('destroy', this.reload
, this);
265 onVerify: function(view
, rI
, cI
, item
, e
, rec
) {
266 var view
= this.getView();
268 if (!view
.datastore
) return;
270 if (!(rec
&& rec
.data
)) return;
275 if (rec
.parentNode
.id
!== 'root') {
277 "backup-type": data
["backup-type"],
278 "backup-id": data
["backup-id"],
279 "backup-time": (data
['backup-time'].getTime()/1000).toFixed(0),
283 "backup-type": data
.backup_type
,
284 "backup-id": data
.backup_id
,
288 Proxmox
.Utils
.API2Request({
290 url
: `/admin/datastore/${view.datastore}/verify`,
292 failure: function(response
) {
293 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
295 success: function(response
, options
) {
296 Ext
.create('Proxmox.window.TaskViewer', {
297 upid
: response
.result
.data
,
303 onForget: function(view
, rI
, cI
, item
, e
, rec
) {
305 var view
= this.getView();
307 if (!(rec
&& rec
.data
)) return;
309 if (!view
.datastore
) return;
312 title
: gettext('Confirm'),
313 icon
: Ext
.Msg
.WARNING
,
314 message
: Ext
.String
.format(gettext('Are you sure you want to remove snapshot {0}'), `'${data.text}'`),
315 buttons
: Ext
.Msg
.YESNO
,
317 callback: function(btn
) {
322 Proxmox
.Utils
.API2Request({
324 "backup-type": data
["backup-type"],
325 "backup-id": data
["backup-id"],
326 "backup-time": (data
['backup-time'].getTime()/1000).toFixed(0),
328 url
: `/admin/datastore/${view.datastore}/snapshots`,
331 failure: function(response
, opts
) {
332 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
334 callback
: me
.reload
.bind(me
),
340 downloadFile: function(tV
, rI
, cI
, item
, e
, rec
) {
342 let view
= me
.getView();
344 if (!(rec
&& rec
.data
)) return;
345 let data
= rec
.parentNode
.data
;
347 let file
= rec
.data
.filename
;
349 'backup-id': data
['backup-id'],
350 'backup-type': data
['backup-type'],
351 'backup-time': (data
['backup-time'].getTime()/1000).toFixed(0),
355 let idx
= file
.lastIndexOf('.');
356 let filename
= file
.slice(0, idx
);
357 let atag
= document
.createElement('a');
358 params
['file-name'] = file
;
359 atag
.download
= filename
;
360 let url
= new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, window
.location
.origin
);
361 for (const [key
, value
] of Object
.entries(params
)) {
362 url
.searchParams
.append(key
, value
);
364 atag
.href
= url
.href
;
368 openPxarBrowser: function(tv
, rI
, Ci
, item
, e
, rec
) {
370 let view
= me
.getView();
372 if (!(rec
&& rec
.data
)) return;
373 let data
= rec
.parentNode
.data
;
375 let id
= data
['backup-id'];
376 let time
= data
['backup-time'];
377 let type
= data
['backup-type'];
378 let timetext
= PBS
.Utils
.render_datetime_utc(data
["backup-time"]);
380 Ext
.create('PBS.window.FileBrowser', {
381 title
: `${type}/${id}/${timetext}`,
382 datastore
: view
.datastore
,
384 'backup-time': (time
.getTime()/1000).toFixed(0),
386 archive
: rec
.data
.filename
,
390 filter: function(item
, value
) {
391 if (item
.data
.text
.indexOf(value
) !== -1) {
395 if (item
.data
.owner
&& item
.data
.owner
.indexOf(value
) !== -1) {
402 search: function(tf
, value
) {
404 let view
= me
.getView();
405 let store
= view
.getStore();
406 if (!value
&& value
!== 0) {
408 store
.getRoot().collapseChildren(true);
409 tf
.triggers
.clear
.setVisible(false);
412 tf
.triggers
.clear
.setVisible(true);
413 if (value
.length
< 2) return;
414 Proxmox
.Utils
.setErrorMask(view
, true);
415 // we do it a little bit later for the error mask to work
416 setTimeout(function() {
418 store
.getRoot().collapseChildren(true);
421 store
.getRoot().cascadeBy({
422 before: function(item
) {
423 if(me
.filter(item
, value
)) {
424 item
.set('matchesFilter', true);
425 if (item
.parentNode
&& item
.parentNode
.id
!== 'root') {
426 item
.parentNode
.childmatches
= true;
432 after: function(item
) {
433 if (me
.filter(item
, value
) || item
.id
=== 'root' || item
.childmatches
) {
434 item
.set('matchesFilter', true);
435 if (item
.parentNode
&& item
.parentNode
.id
!== 'root') {
436 item
.parentNode
.childmatches
= true;
438 if (item
.childmatches
) {
442 item
.set('matchesFilter', false);
444 delete item
.childmatches
;
449 store
.filter((item
) => !!item
.get('matchesFilter'));
450 Proxmox
.Utils
.setErrorMask(view
, false);
458 header
: gettext("Backup Group"),
463 header
: gettext('Actions'),
464 xtype
: 'actioncolumn',
469 tooltip
: gettext('Verify'),
470 getClass
: (v
, m
, rec
) => rec
.data
.leaf
? 'pmx-hidden' : 'fa fa-search',
471 isDisabled
: (v
, r
, c
, i
, rec
) => !!rec
.data
.leaf
,
475 tooltip
: gettext('Prune'),
476 getClass
: (v
, m
, rec
) => rec
.parentNode
.id
==='root' ? 'fa fa-scissors' : 'pmx-hidden',
477 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.parentNode
.id
!=='root',
481 tooltip
: gettext('Forget Snapshot'),
482 getClass
: (v
, m
, rec
) => !rec
.data
.leaf
&& rec
.parentNode
.id
!== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden',
483 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.data
.leaf
|| rec
.parentNode
.id
=== 'root',
486 handler
: 'downloadFile',
487 tooltip
: gettext('Download'),
488 getClass
: (v
, m
, rec
) => rec
.data
.leaf
&& rec
.data
.filename
? 'fa fa-download' : 'pmx-hidden',
489 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
.leaf
|| !rec
.data
.filename
|| rec
.data
['crypt-mode'] > 2,
492 handler
: 'openPxarBrowser',
493 tooltip
: gettext('Browse'),
494 getClass
: (v
, m
, rec
) => {
496 if (data
.leaf
&& data
.filename
&& data
.filename
.endsWith('pxar.didx')) {
497 return 'fa fa-folder-open-o';
501 isDisabled
: (v
, r
, c
, i
, rec
) => {
503 return !(data
.leaf
&&
505 data
.filename
.endsWith('pxar.didx') &&
506 data
['crypt-mode'] < 2);
513 header
: gettext('Backup Time'),
515 dataIndex
: 'backup-time',
516 format
: 'Y-m-d H:i:s',
520 header
: gettext("Size"),
523 renderer
: (v
, meta
, record
) => {
524 if (record
.data
.text
=== 'client.log.blob' && v
=== undefined) {
527 if (v
=== undefined || v
=== null) {
528 meta
.tdCls
= "x-grid-row-loading";
531 return Proxmox
.Utils
.format_size(v
);
535 xtype
: 'numbercolumn',
537 header
: gettext("Count"),
542 header
: gettext("Owner"),
547 header
: gettext('Encrypted'),
548 dataIndex
: 'crypt-mode',
549 renderer
: (v
, meta
, record
) => {
553 let iconCls
= PBS
.Utils
.cryptIconCls
[v
] || '';
556 iconTxt
= `<i class="fa fa-fw fa-${iconCls}"></i> `;
558 return (iconTxt
+ PBS
.Utils
.cryptText
[v
]) || Proxmox
.Utils
.unknownText
565 text
: gettext('Reload'),
566 iconCls
: 'fa fa-refresh',
572 html
: gettext('Search'),
576 reference
: 'searchbox',
579 cls
: 'pmx-clear-trigger',
582 handler: function() {
583 this.triggers
.clear
.setVisible(false);