]>
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
;
236 view
.setSelection(selection
);
237 view
.getView().focusRow(selection
);
241 Proxmox
.Utils
.setErrorMask(view
, false);
242 if (view
.getStore().getFilters().length
> 0) {
243 let searchBox
= me
.lookup("searchbox");
244 let searchvalue
= searchBox
.getValue();;
245 me
.search(searchBox
, searchvalue
);
249 onPrune: function(view
, rI
, cI
, item
, e
, rec
) {
250 var view
= this.getView();
252 if (!(rec
&& rec
.data
)) return;
254 if (rec
.parentNode
.id
!== 'root') return;
256 if (!view
.datastore
) return;
258 let win
= Ext
.create('PBS.DataStorePrune', {
259 datastore
: view
.datastore
,
260 backup_type
: data
.backup_type
,
261 backup_id
: data
.backup_id
,
263 win
.on('destroy', this.reload
, this);
267 onVerify: function(view
, rI
, cI
, item
, e
, rec
) {
268 var view
= this.getView();
270 if (!view
.datastore
) return;
272 if (!(rec
&& rec
.data
)) return;
277 if (rec
.parentNode
.id
!== 'root') {
279 "backup-type": data
["backup-type"],
280 "backup-id": data
["backup-id"],
281 "backup-time": (data
['backup-time'].getTime()/1000).toFixed(0),
285 "backup-type": data
.backup_type
,
286 "backup-id": data
.backup_id
,
290 Proxmox
.Utils
.API2Request({
292 url
: `/admin/datastore/${view.datastore}/verify`,
294 failure: function(response
) {
295 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
297 success: function(response
, options
) {
298 Ext
.create('Proxmox.window.TaskViewer', {
299 upid
: response
.result
.data
,
305 onForget: function(view
, rI
, cI
, item
, e
, rec
) {
307 var view
= this.getView();
309 if (!(rec
&& rec
.data
)) return;
311 if (!view
.datastore
) return;
314 title
: gettext('Confirm'),
315 icon
: Ext
.Msg
.WARNING
,
316 message
: Ext
.String
.format(gettext('Are you sure you want to remove snapshot {0}'), `'${data.text}'`),
317 buttons
: Ext
.Msg
.YESNO
,
319 callback: function(btn
) {
324 Proxmox
.Utils
.API2Request({
326 "backup-type": data
["backup-type"],
327 "backup-id": data
["backup-id"],
328 "backup-time": (data
['backup-time'].getTime()/1000).toFixed(0),
330 url
: `/admin/datastore/${view.datastore}/snapshots`,
333 failure: function(response
, opts
) {
334 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
336 callback
: me
.reload
.bind(me
),
342 downloadFile: function(tV
, rI
, cI
, item
, e
, rec
) {
344 let view
= me
.getView();
346 if (!(rec
&& rec
.data
)) return;
347 let data
= rec
.parentNode
.data
;
349 let file
= rec
.data
.filename
;
351 'backup-id': data
['backup-id'],
352 'backup-type': data
['backup-type'],
353 'backup-time': (data
['backup-time'].getTime()/1000).toFixed(0),
357 let idx
= file
.lastIndexOf('.');
358 let filename
= file
.slice(0, idx
);
359 let atag
= document
.createElement('a');
360 params
['file-name'] = file
;
361 atag
.download
= filename
;
362 let url
= new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, window
.location
.origin
);
363 for (const [key
, value
] of Object
.entries(params
)) {
364 url
.searchParams
.append(key
, value
);
366 atag
.href
= url
.href
;
370 openPxarBrowser: function(tv
, rI
, Ci
, item
, e
, rec
) {
372 let view
= me
.getView();
374 if (!(rec
&& rec
.data
)) return;
375 let data
= rec
.parentNode
.data
;
377 let id
= data
['backup-id'];
378 let time
= data
['backup-time'];
379 let type
= data
['backup-type'];
380 let timetext
= PBS
.Utils
.render_datetime_utc(data
["backup-time"]);
382 Ext
.create('PBS.window.FileBrowser', {
383 title
: `${type}/${id}/${timetext}`,
384 datastore
: view
.datastore
,
386 'backup-time': (time
.getTime()/1000).toFixed(0),
388 archive
: rec
.data
.filename
,
392 filter: function(item
, value
) {
393 if (item
.data
.text
.indexOf(value
) !== -1) {
397 if (item
.data
.owner
&& item
.data
.owner
.indexOf(value
) !== -1) {
404 search: function(tf
, value
) {
406 let view
= me
.getView();
407 let store
= view
.getStore();
408 if (!value
&& value
!== 0) {
410 store
.getRoot().collapseChildren(true);
411 tf
.triggers
.clear
.setVisible(false);
414 tf
.triggers
.clear
.setVisible(true);
415 if (value
.length
< 2) return;
416 Proxmox
.Utils
.setErrorMask(view
, true);
417 // we do it a little bit later for the error mask to work
418 setTimeout(function() {
420 store
.getRoot().collapseChildren(true);
423 store
.getRoot().cascadeBy({
424 before: function(item
) {
425 if(me
.filter(item
, value
)) {
426 item
.set('matchesFilter', true);
427 if (item
.parentNode
&& item
.parentNode
.id
!== 'root') {
428 item
.parentNode
.childmatches
= true;
434 after: function(item
) {
435 if (me
.filter(item
, value
) || item
.id
=== 'root' || item
.childmatches
) {
436 item
.set('matchesFilter', true);
437 if (item
.parentNode
&& item
.parentNode
.id
!== 'root') {
438 item
.parentNode
.childmatches
= true;
440 if (item
.childmatches
) {
444 item
.set('matchesFilter', false);
446 delete item
.childmatches
;
451 store
.filter((item
) => !!item
.get('matchesFilter'));
452 Proxmox
.Utils
.setErrorMask(view
, false);
460 header
: gettext("Backup Group"),
465 header
: gettext('Actions'),
466 xtype
: 'actioncolumn',
471 tooltip
: gettext('Verify'),
472 getClass
: (v
, m
, rec
) => rec
.data
.leaf
? 'pmx-hidden' : 'fa fa-search',
473 isDisabled
: (v
, r
, c
, i
, rec
) => !!rec
.data
.leaf
,
477 tooltip
: gettext('Prune'),
478 getClass
: (v
, m
, rec
) => rec
.parentNode
.id
==='root' ? 'fa fa-scissors' : 'pmx-hidden',
479 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.parentNode
.id
!=='root',
483 tooltip
: gettext('Forget Snapshot'),
484 getClass
: (v
, m
, rec
) => !rec
.data
.leaf
&& rec
.parentNode
.id
!== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden',
485 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.data
.leaf
|| rec
.parentNode
.id
=== 'root',
488 handler
: 'downloadFile',
489 tooltip
: gettext('Download'),
490 getClass
: (v
, m
, rec
) => rec
.data
.leaf
&& rec
.data
.filename
? 'fa fa-download' : 'pmx-hidden',
491 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
.leaf
|| !rec
.data
.filename
|| rec
.data
['crypt-mode'] > 2,
494 handler
: 'openPxarBrowser',
495 tooltip
: gettext('Browse'),
496 getClass
: (v
, m
, rec
) => {
498 if (data
.leaf
&& data
.filename
&& data
.filename
.endsWith('pxar.didx')) {
499 return 'fa fa-folder-open-o';
503 isDisabled
: (v
, r
, c
, i
, rec
) => {
505 return !(data
.leaf
&&
507 data
.filename
.endsWith('pxar.didx') &&
508 data
['crypt-mode'] < 2);
515 header
: gettext('Backup Time'),
517 dataIndex
: 'backup-time',
518 format
: 'Y-m-d H:i:s',
522 header
: gettext("Size"),
525 renderer
: (v
, meta
, record
) => {
526 if (record
.data
.text
=== 'client.log.blob' && v
=== undefined) {
529 if (v
=== undefined || v
=== null) {
530 meta
.tdCls
= "x-grid-row-loading";
533 return Proxmox
.Utils
.format_size(v
);
537 xtype
: 'numbercolumn',
539 header
: gettext("Count"),
544 header
: gettext("Owner"),
549 header
: gettext('Encrypted'),
550 dataIndex
: 'crypt-mode',
551 renderer
: (v
, meta
, record
) => {
555 let iconCls
= PBS
.Utils
.cryptIconCls
[v
] || '';
558 iconTxt
= `<i class="fa fa-fw fa-${iconCls}"></i> `;
560 return (iconTxt
+ PBS
.Utils
.cryptText
[v
]) || Proxmox
.Utils
.unknownText
567 text
: gettext('Reload'),
568 iconCls
: 'fa fa-refresh',
574 html
: gettext('Search'),
578 reference
: 'searchbox',
581 cls
: 'pmx-clear-trigger',
584 handler: function() {
585 this.triggers
.clear
.setVisible(false);