]>
git.proxmox.com Git - proxmox-backup.git/blob - www/DataStoreContent.js
e32c6160882188c79824fb6507cc73d064466fd8
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
);
144 for (const item
of records
) {
145 let group
= item
.data
["backup-type"] + "/" + item
.data
["backup-id"];
146 let children
= groups
[group
].children
;
148 let data
= item
.data
;
150 data
.text
= group
+ '/' + PBS
.Utils
.render_datetime_utc(data
["backup-time"]);
152 data
.cls
= 'no-leaf-icons';
153 data
.matchesFilter
= true;
156 for (const file
of data
.files
) {
157 file
.text
= file
.filename
,
158 file
['crypt-mode'] = PBS
.Utils
.cryptmap
.indexOf(file
['crypt-mode']);
160 file
.matchesFilter
= true;
162 data
.children
.push(file
);
169 for (const [_key
, group
] of Object
.entries(groups
)) {
177 for (const item
of group
.children
) {
178 crypt
[PBS
.Utils
.cryptmap
[item
['crypt-mode']]]++;
179 if (item
["backup-time"] > last_backup
&& item
.size
!== null) {
180 last_backup
= item
["backup-time"];
181 group
["backup-time"] = last_backup
;
182 group
.files
= item
.files
;
183 group
.size
= item
.size
;
184 group
.owner
= item
.owner
;
188 group
.count
= group
.children
.length
;
189 group
.matchesFilter
= true;
190 crypt
.count
= group
.count
;
191 group
['crypt-mode'] = PBS
.Utils
.calculateCryptMode(crypt
);
192 children
.push(group
);
199 Proxmox
.Utils
.setErrorMask(view
, false);
200 if (view
.getStore().getFilters().length
> 0) {
201 let searchBox
= me
.lookup("searchbox");
202 let searchvalue
= searchBox
.getValue();;
203 me
.search(searchBox
, searchvalue
);
207 onPrune: function(view
, rI
, cI
, item
, e
, rec
) {
208 var view
= this.getView();
210 if (!(rec
&& rec
.data
)) return;
212 if (rec
.parentNode
.id
!== 'root') return;
214 if (!view
.datastore
) return;
216 let win
= Ext
.create('PBS.DataStorePrune', {
217 datastore
: view
.datastore
,
218 backup_type
: data
.backup_type
,
219 backup_id
: data
.backup_id
,
221 win
.on('destroy', this.reload
, this);
225 onVerify: function(view
, rI
, cI
, item
, e
, rec
) {
226 var view
= this.getView();
228 if (!view
.datastore
) return;
230 if (!(rec
&& rec
.data
)) return;
235 if (rec
.parentNode
.id
!== 'root') {
237 "backup-type": data
["backup-type"],
238 "backup-id": data
["backup-id"],
239 "backup-time": (data
['backup-time'].getTime()/1000).toFixed(0),
243 "backup-type": data
.backup_type
,
244 "backup-id": data
.backup_id
,
248 Proxmox
.Utils
.API2Request({
250 url
: `/admin/datastore/${view.datastore}/verify`,
252 failure: function(response
) {
253 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
255 success: function(response
, options
) {
256 Ext
.create('Proxmox.window.TaskViewer', {
257 upid
: response
.result
.data
,
263 onForget: function(view
, rI
, cI
, item
, e
, rec
) {
265 var view
= this.getView();
267 if (!(rec
&& rec
.data
)) return;
269 if (!view
.datastore
) return;
272 title
: gettext('Confirm'),
273 icon
: Ext
.Msg
.WARNING
,
274 message
: Ext
.String
.format(gettext('Are you sure you want to remove snapshot {0}'), `'${data.text}'`),
275 buttons
: Ext
.Msg
.YESNO
,
277 callback: function(btn
) {
282 Proxmox
.Utils
.API2Request({
284 "backup-type": data
["backup-type"],
285 "backup-id": data
["backup-id"],
286 "backup-time": (data
['backup-time'].getTime()/1000).toFixed(0),
288 url
: `/admin/datastore/${view.datastore}/snapshots`,
291 failure: function(response
, opts
) {
292 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
294 callback
: me
.reload
.bind(me
),
300 downloadFile: function(tV
, rI
, cI
, item
, e
, rec
) {
302 let view
= me
.getView();
304 if (!(rec
&& rec
.data
)) return;
305 let data
= rec
.parentNode
.data
;
307 let file
= rec
.data
.filename
;
309 'backup-id': data
['backup-id'],
310 'backup-type': data
['backup-type'],
311 'backup-time': (data
['backup-time'].getTime()/1000).toFixed(0),
315 let idx
= file
.lastIndexOf('.');
316 let filename
= file
.slice(0, idx
);
317 let atag
= document
.createElement('a');
318 params
['file-name'] = file
;
319 atag
.download
= filename
;
320 let url
= new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, window
.location
.origin
);
321 for (const [key
, value
] of Object
.entries(params
)) {
322 url
.searchParams
.append(key
, value
);
324 atag
.href
= url
.href
;
328 openPxarBrowser: function(tv
, rI
, Ci
, item
, e
, rec
) {
330 let view
= me
.getView();
332 if (!(rec
&& rec
.data
)) return;
333 let data
= rec
.parentNode
.data
;
335 let id
= data
['backup-id'];
336 let time
= data
['backup-time'];
337 let type
= data
['backup-type'];
338 let timetext
= PBS
.Utils
.render_datetime_utc(data
["backup-time"]);
340 Ext
.create('PBS.window.FileBrowser', {
341 title
: `${type}/${id}/${timetext}`,
342 datastore
: view
.datastore
,
344 'backup-time': (time
.getTime()/1000).toFixed(0),
346 archive
: rec
.data
.filename
,
350 filter: function(item
, value
) {
351 if (item
.data
.text
.indexOf(value
) !== -1) {
355 if (item
.data
.owner
&& item
.data
.owner
.indexOf(value
) !== -1) {
362 search: function(tf
, value
) {
364 let view
= me
.getView();
365 let store
= view
.getStore();
366 if (!value
&& value
!== 0) {
368 store
.getRoot().collapseChildren(true);
369 tf
.triggers
.clear
.setVisible(false);
372 tf
.triggers
.clear
.setVisible(true);
373 if (value
.length
< 2) return;
374 Proxmox
.Utils
.setErrorMask(view
, true);
375 // we do it a little bit later for the error mask to work
376 setTimeout(function() {
378 store
.getRoot().collapseChildren(true);
381 store
.getRoot().cascadeBy({
382 before: function(item
) {
383 if(me
.filter(item
, value
)) {
384 item
.set('matchesFilter', true);
385 if (item
.parentNode
&& item
.parentNode
.id
!== 'root') {
386 item
.parentNode
.childmatches
= true;
392 after: function(item
) {
393 if (me
.filter(item
, value
) || item
.id
=== 'root' || item
.childmatches
) {
394 item
.set('matchesFilter', true);
395 if (item
.parentNode
&& item
.parentNode
.id
!== 'root') {
396 item
.parentNode
.childmatches
= true;
398 if (item
.childmatches
) {
402 item
.set('matchesFilter', false);
404 delete item
.childmatches
;
409 store
.filter((item
) => !!item
.get('matchesFilter'));
410 Proxmox
.Utils
.setErrorMask(view
, false);
418 header
: gettext("Backup Group"),
423 header
: gettext('Actions'),
424 xtype
: 'actioncolumn',
429 tooltip
: gettext('Verify'),
430 getClass
: (v
, m
, rec
) => rec
.data
.leaf
? 'pmx-hidden' : 'fa fa-search',
431 isDisabled
: (v
, r
, c
, i
, rec
) => !!rec
.data
.leaf
,
435 tooltip
: gettext('Prune'),
436 getClass
: (v
, m
, rec
) => rec
.parentNode
.id
==='root' ? 'fa fa-scissors' : 'pmx-hidden',
437 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.parentNode
.id
!=='root',
441 tooltip
: gettext('Forget Snapshot'),
442 getClass
: (v
, m
, rec
) => !rec
.data
.leaf
&& rec
.parentNode
.id
!== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden',
443 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.data
.leaf
|| rec
.parentNode
.id
=== 'root',
446 handler
: 'downloadFile',
447 tooltip
: gettext('Download'),
448 getClass
: (v
, m
, rec
) => rec
.data
.leaf
&& rec
.data
.filename
? 'fa fa-download' : 'pmx-hidden',
449 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
.leaf
|| !rec
.data
.filename
|| rec
.data
['crypt-mode'] > 2,
452 handler
: 'openPxarBrowser',
453 tooltip
: gettext('Browse'),
454 getClass
: (v
, m
, rec
) => {
456 if (data
.leaf
&& data
.filename
&& data
.filename
.endsWith('pxar.didx')) {
457 return 'fa fa-folder-open-o';
461 isDisabled
: (v
, r
, c
, i
, rec
) => {
463 return !(data
.leaf
&&
465 data
.filename
.endsWith('pxar.didx') &&
466 data
['crypt-mode'] < 2);
473 header
: gettext('Backup Time'),
475 dataIndex
: 'backup-time',
476 format
: 'Y-m-d H:i:s',
480 header
: gettext("Size"),
483 renderer
: (v
, meta
, record
) => {
484 if (record
.data
.text
=== 'client.log.blob' && v
=== undefined) {
487 if (v
=== undefined || v
=== null) {
488 meta
.tdCls
= "x-grid-row-loading";
491 return Proxmox
.Utils
.format_size(v
);
495 xtype
: 'numbercolumn',
497 header
: gettext("Count"),
502 header
: gettext("Owner"),
507 header
: gettext('Encrypted'),
508 dataIndex
: 'crypt-mode',
509 renderer
: (v
, meta
, record
) => {
513 let iconCls
= PBS
.Utils
.cryptIconCls
[v
] || '';
516 iconTxt
= `<i class="fa fa-fw fa-${iconCls}"></i> `;
518 return (iconTxt
+ PBS
.Utils
.cryptText
[v
]) || Proxmox
.Utils
.unknownText
525 text
: gettext('Reload'),
526 iconCls
: 'fa fa-refresh',
532 html
: gettext('Search'),
536 reference
: 'searchbox',
539 cls
: 'pmx-clear-trigger',
542 handler: function() {
543 this.triggers
.clear
.setVisible(false);