2020-03-26 11:17:15 +01:00
Ext . define ( 'pbs-data-store-snapshots' , {
2019-12-20 12:46:09 +01:00
extend : 'Ext.data.Model' ,
2019-12-20 17:04:45 +01:00
fields : [
'backup-type' ,
2019-12-22 10:43:57 +01:00
'backup-id' ,
{
2020-03-25 15:17:28 +01:00
name : 'backup-time' ,
2019-12-22 10:43:57 +01:00
type : 'date' ,
dateFormat : 'timestamp'
} ,
2019-12-20 17:04:45 +01:00
'files' ,
2020-05-29 15:09:01 +02:00
'owner' ,
2020-07-08 12:08:02 +02:00
{ name : 'size' , type : 'int' , allowNull : true , } ,
2020-06-18 13:55:25 +02:00
{
2020-07-08 13:32:20 +02:00
name : 'crypt-mode' ,
2020-06-23 12:09:46 +02:00
type : 'boolean' ,
2020-06-18 13:55:25 +02:00
calculate : function ( data ) {
let encrypted = 0 ;
2020-07-08 13:32:20 +02:00
let crypt = {
none : 0 ,
mixed : 0 ,
'sign-only' : 0 ,
encrypt : 0 ,
2020-07-09 16:50:24 +02:00
count : 0 ,
2020-07-08 13:32:20 +02:00
} ;
let signed = 0 ;
2020-06-18 13:55:25 +02:00
data . files . forEach ( file => {
if ( file . filename === 'index.json.blob' ) return ; // is never encrypted
2020-07-08 13:32:20 +02:00
let mode = PBS . Utils . cryptmap . indexOf ( file [ 'crypt-mode' ] ) ;
if ( mode !== - 1 ) {
crypt [ file [ 'crypt-mode' ] ] ++ ;
2020-06-18 13:55:25 +02:00
}
2020-07-09 16:50:24 +02:00
crypt . count ++ ;
2020-06-18 13:55:25 +02:00
} ) ;
2020-07-09 16:50:24 +02:00
return PBS . Utils . calculateCryptMode ( crypt ) ;
2020-06-18 13:55:25 +02:00
}
2020-07-23 13:03:50 +02:00
} ,
{
name : 'matchesFilter' ,
type : 'boolean' ,
defaultValue : true ,
} ,
2020-03-26 11:17:15 +01:00
]
2019-12-20 12:46:09 +01:00
} ) ;
Ext . define ( 'PBS.DataStoreContent' , {
2020-03-25 15:17:28 +01:00
extend : 'Ext.tree.Panel' ,
2019-12-20 12:46:09 +01:00
alias : 'widget.pbsDataStoreContent' ,
2020-03-25 15:17:28 +01:00
rootVisible : false ,
2019-12-22 10:43:57 +01:00
2020-05-20 12:15:38 +02:00
title : gettext ( 'Content' ) ,
2019-12-20 17:17:44 +01:00
controller : {
xclass : 'Ext.app.ViewController' ,
init : function ( view ) {
if ( ! view . datastore ) {
throw "no datastore specified" ;
}
2020-05-26 12:46:45 +02:00
this . store = Ext . create ( 'Ext.data.Store' , {
2020-03-26 11:17:15 +01:00
model : 'pbs-data-store-snapshots' ,
2020-03-25 15:17:28 +01:00
groupField : 'backup-group' ,
} ) ;
2020-05-26 12:46:45 +02:00
this . store . on ( 'load' , this . onLoad , this ) ;
2020-03-25 15:17:28 +01:00
2020-06-23 12:09:47 +02:00
view . getStore ( ) . setSorters ( [
'backup-group' ,
'text' ,
'backup-time'
] ) ;
2020-06-25 10:45:47 +02:00
Proxmox . Utils . monStoreErrors ( view , this . store ) ;
2019-12-20 17:17:44 +01:00
this . reload ( ) ; // initial load
} ,
reload : function ( ) {
2020-05-26 12:46:45 +02:00
let view = this . getView ( ) ;
if ( ! view . store || ! this . store ) {
console . warn ( 'cannot reload, no store(s)' ) ;
return ;
}
2019-12-20 17:17:44 +01:00
2020-03-25 15:17:28 +01:00
let url = ` /api2/json/admin/datastore/ ${ view . datastore } /snapshots ` ;
2020-05-26 12:46:45 +02:00
this . store . setProxy ( {
2019-12-20 17:17:44 +01:00
type : 'proxmox' ,
2020-07-01 13:46:00 +02:00
timeout : 300 * 1000 , // 5 minutes, we should make that api call faster
2019-12-20 17:17:44 +01:00
url : url
} ) ;
2020-03-25 15:17:28 +01:00
2020-05-26 12:46:45 +02:00
this . store . load ( ) ;
} ,
2020-03-25 15:17:28 +01:00
2020-05-26 12:46:45 +02:00
getRecordGroups : function ( records ) {
let groups = { } ;
for ( const item of records ) {
var btype = item . data [ "backup-type" ] ;
let group = btype + "/" + item . data [ "backup-id" ] ;
if ( groups [ group ] !== undefined ) {
continue ;
}
var cls = '' ;
if ( btype === 'vm' ) {
cls = 'fa-desktop' ;
} else if ( btype === 'ct' ) {
cls = 'fa-cube' ;
} else if ( btype === 'host' ) {
cls = 'fa-building' ;
} else {
2020-05-30 16:37:33 +02:00
console . warn ( ` got unknown backup-type ' ${ btype } ' ` ) ;
2020-05-26 12:46:45 +02:00
continue ; // FIXME: auto render? what do?
}
groups [ group ] = {
text : group ,
leaf : false ,
iconCls : "fa " + cls ,
expanded : false ,
backup _type : item . data [ "backup-type" ] ,
backup _id : item . data [ "backup-id" ] ,
children : [ ]
2020-03-26 18:01:04 +01:00
} ;
2020-05-26 12:46:45 +02:00
}
2020-03-26 18:01:04 +01:00
2020-05-26 12:46:45 +02:00
return groups ;
} ,
2020-03-25 15:17:28 +01:00
2020-06-25 10:45:47 +02:00
onLoad : function ( store , records , success , operation ) {
2020-07-23 13:03:50 +02:00
let me = this ;
2020-05-26 12:46:45 +02:00
let view = this . getView ( ) ;
if ( ! success ) {
2020-06-25 10:45:47 +02:00
Proxmox . Utils . setErrorMask ( view , Proxmox . Utils . getResponseErrorMessage ( operation . getError ( ) ) ) ;
2020-05-26 12:46:45 +02:00
return ;
}
let groups = this . getRecordGroups ( records ) ;
2020-03-25 15:17:28 +01:00
2020-07-23 13:03:51 +02:00
let selected ;
let expanded = { } ;
view . getSelection ( ) . some ( function ( item ) {
let id = item . data . text ;
if ( item . data . leaf ) {
id = item . parentNode . data . text + id ;
}
selected = id ;
return true ;
} ) ;
view . getRootNode ( ) . cascadeBy ( {
before : item => {
if ( item . isExpanded ( ) && ! item . data . leaf ) {
let id = item . data . text ;
expanded [ id ] = true ;
return true ;
}
return false ;
} ,
after : ( ) => { } ,
} ) ;
2020-05-26 12:46:45 +02:00
for ( const item of records ) {
let group = item . data [ "backup-type" ] + "/" + item . data [ "backup-id" ] ;
let children = groups [ group ] . children ;
let data = item . data ;
2020-05-26 18:16:38 +02:00
data . text = group + '/' + PBS . Utils . render _datetime _utc ( data [ "backup-time" ] ) ;
2020-07-23 13:03:49 +02:00
data . leaf = false ;
2020-05-26 12:46:45 +02:00
data . cls = 'no-leaf-icons' ;
2020-07-23 13:03:50 +02:00
data . matchesFilter = true ;
2020-05-26 12:46:45 +02:00
2020-07-23 13:03:51 +02:00
data . expanded = ! ! expanded [ data . text ] ;
2020-07-23 13:03:49 +02:00
data . children = [ ] ;
for ( const file of data . files ) {
file . text = file . filename ,
file [ 'crypt-mode' ] = PBS . Utils . cryptmap . indexOf ( file [ 'crypt-mode' ] ) ;
file . leaf = true ;
2020-07-23 13:03:50 +02:00
file . matchesFilter = true ;
2020-07-23 13:03:49 +02:00
data . children . push ( file ) ;
}
2020-05-26 12:46:45 +02:00
children . push ( data ) ;
}
let children = [ ] ;
2020-07-23 13:03:51 +02:00
for ( const [ name , group ] of Object . entries ( groups ) ) {
2020-05-26 12:46:45 +02:00
let last _backup = 0 ;
2020-07-08 13:32:20 +02:00
let crypt = {
none : 0 ,
mixed : 0 ,
'sign-only' : 0 ,
2020-07-09 16:50:24 +02:00
encrypt : 0 ,
2020-07-08 13:32:20 +02:00
} ;
2020-05-26 12:46:45 +02:00
for ( const item of group . children ) {
2020-07-08 13:32:20 +02:00
crypt [ PBS . Utils . cryptmap [ item [ 'crypt-mode' ] ] ] ++ ;
2020-07-08 12:09:09 +02:00
if ( item [ "backup-time" ] > last _backup && item . size !== null ) {
2020-05-26 12:46:45 +02:00
last _backup = item [ "backup-time" ] ;
group [ "backup-time" ] = last _backup ;
group . files = item . files ;
group . size = item . size ;
2020-05-29 15:09:01 +02:00
group . owner = item . owner ;
2020-05-26 12:46:45 +02:00
}
2020-06-18 13:55:25 +02:00
2020-05-26 12:46:45 +02:00
}
group . count = group . children . length ;
2020-07-23 13:03:50 +02:00
group . matchesFilter = true ;
2020-07-09 16:50:24 +02:00
crypt . count = group . count ;
group [ 'crypt-mode' ] = PBS . Utils . calculateCryptMode ( crypt ) ;
2020-07-23 13:03:51 +02:00
group . expanded = ! ! expanded [ name ] ;
2020-05-26 12:46:45 +02:00
children . push ( group ) ;
}
view . setRootNode ( {
expanded : true ,
children : children
} ) ;
2020-07-23 13:03:51 +02:00
if ( selected !== undefined ) {
let selection = view . getRootNode ( ) . findChildBy ( function ( item ) {
let id = item . data . text ;
if ( item . data . leaf ) {
id = item . parentNode . data . text + id ;
}
return selected === id ;
} , undefined , true ) ;
2020-07-28 14:32:10 +02:00
if ( selection ) {
view . setSelection ( selection ) ;
view . getView ( ) . focusRow ( selection ) ;
}
2020-07-23 13:03:51 +02:00
}
2020-06-25 10:45:47 +02:00
Proxmox . Utils . setErrorMask ( view , false ) ;
2020-07-23 13:03:50 +02:00
if ( view . getStore ( ) . getFilters ( ) . length > 0 ) {
let searchBox = me . lookup ( "searchbox" ) ;
let searchvalue = searchBox . getValue ( ) ; ;
me . search ( searchBox , searchvalue ) ;
}
2019-12-20 17:17:44 +01:00
} ,
2020-03-26 17:23:51 +01:00
2020-07-23 13:03:49 +02:00
onPrune : function ( view , rI , cI , item , e , rec ) {
2020-03-26 17:23:51 +01:00
var view = this . getView ( ) ;
if ( ! ( rec && rec . data ) ) return ;
let data = rec . data ;
2020-07-23 13:03:49 +02:00
if ( rec . parentNode . id !== 'root' ) return ;
2020-03-26 17:23:51 +01:00
if ( ! view . datastore ) return ;
let win = Ext . create ( 'PBS.DataStorePrune' , {
datastore : view . datastore ,
backup _type : data . backup _type ,
backup _id : data . backup _id ,
} ) ;
win . on ( 'destroy' , this . reload , this ) ;
win . show ( ) ;
2020-06-23 12:09:48 +02:00
} ,
2020-07-23 13:03:49 +02:00
onVerify : function ( view , rI , cI , item , e , rec ) {
2020-06-30 13:22:02 +02:00
var view = this . getView ( ) ;
if ( ! view . datastore ) return ;
if ( ! ( rec && rec . data ) ) return ;
let data = rec . data ;
let params ;
2020-07-23 13:03:49 +02:00
if ( rec . parentNode . id !== 'root' ) {
2020-06-30 13:22:02 +02:00
params = {
"backup-type" : data [ "backup-type" ] ,
"backup-id" : data [ "backup-id" ] ,
"backup-time" : ( data [ 'backup-time' ] . getTime ( ) / 1000 ) . toFixed ( 0 ) ,
} ;
} else {
params = {
"backup-type" : data . backup _type ,
"backup-id" : data . backup _id ,
} ;
}
Proxmox . Utils . API2Request ( {
params : params ,
url : ` /admin/datastore/ ${ view . datastore } /verify ` ,
method : 'POST' ,
failure : function ( response ) {
Ext . Msg . alert ( gettext ( 'Error' ) , response . htmlStatus ) ;
} ,
success : function ( response , options ) {
Ext . create ( 'Proxmox.window.TaskViewer' , {
upid : response . result . data ,
} ) . show ( ) ;
} ,
} ) ;
} ,
2020-07-23 13:03:49 +02:00
onForget : function ( view , rI , cI , item , e , rec ) {
let me = this ;
2020-06-26 15:58:06 +02:00
var view = this . getView ( ) ;
if ( ! ( rec && rec . data ) ) return ;
let data = rec . data ;
if ( ! view . datastore ) return ;
2020-07-23 13:03:49 +02:00
Ext . Msg . show ( {
title : gettext ( 'Confirm' ) ,
icon : Ext . Msg . WARNING ,
message : Ext . String . format ( gettext ( 'Are you sure you want to remove snapshot {0}' ) , ` ' ${ data . text } ' ` ) ,
buttons : Ext . Msg . YESNO ,
defaultFocus : 'no' ,
callback : function ( btn ) {
if ( btn !== 'yes' ) {
return ;
}
2020-06-26 15:58:06 +02:00
2020-07-23 13:03:49 +02:00
Proxmox . Utils . API2Request ( {
params : {
"backup-type" : data [ "backup-type" ] ,
"backup-id" : data [ "backup-id" ] ,
"backup-time" : ( data [ 'backup-time' ] . getTime ( ) / 1000 ) . toFixed ( 0 ) ,
} ,
url : ` /admin/datastore/ ${ view . datastore } /snapshots ` ,
method : 'DELETE' ,
waitMsgTarget : view ,
failure : function ( response , opts ) {
Ext . Msg . alert ( gettext ( 'Error' ) , response . htmlStatus ) ;
} ,
callback : me . reload . bind ( me ) ,
} ) ;
2020-06-26 15:58:06 +02:00
} ,
} ) ;
} ,
2020-07-23 13:03:49 +02:00
downloadFile : function ( tV , rI , cI , item , e , rec ) {
2020-06-23 12:09:48 +02:00
let me = this ;
let view = me . getView ( ) ;
if ( ! ( rec && rec . data ) ) return ;
2020-07-23 13:03:49 +02:00
let data = rec . parentNode . data ;
let file = rec . data . filename ;
let params = {
'backup-id' : data [ 'backup-id' ] ,
'backup-type' : data [ 'backup-type' ] ,
'backup-time' : ( data [ 'backup-time' ] . getTime ( ) / 1000 ) . toFixed ( 0 ) ,
'file-name' : file ,
} ;
let idx = file . lastIndexOf ( '.' ) ;
let filename = file . slice ( 0 , idx ) ;
let atag = document . createElement ( 'a' ) ;
params [ 'file-name' ] = file ;
atag . download = filename ;
let url = new URL ( ` /api2/json/admin/datastore/ ${ view . datastore } /download-decoded ` , window . location . origin ) ;
for ( const [ key , value ] of Object . entries ( params ) ) {
url . searchParams . append ( key , value ) ;
}
atag . href = url . href ;
atag . click ( ) ;
2020-06-23 12:09:54 +02:00
} ,
2020-07-23 13:03:49 +02:00
openPxarBrowser : function ( tv , rI , Ci , item , e , rec ) {
2020-06-23 12:09:54 +02:00
let me = this ;
let view = me . getView ( ) ;
if ( ! ( rec && rec . data ) ) return ;
2020-07-23 13:03:49 +02:00
let data = rec . parentNode . data ;
2020-06-23 12:09:54 +02:00
let id = data [ 'backup-id' ] ;
let time = data [ 'backup-time' ] ;
let type = data [ 'backup-type' ] ;
let timetext = PBS . Utils . render _datetime _utc ( data [ "backup-time" ] ) ;
Ext . create ( 'PBS.window.FileBrowser' , {
title : ` ${ type } / ${ id } / ${ timetext } ` ,
datastore : view . datastore ,
'backup-id' : id ,
'backup-time' : ( time . getTime ( ) / 1000 ) . toFixed ( 0 ) ,
'backup-type' : type ,
2020-07-23 13:03:49 +02:00
archive : rec . data . filename ,
2020-06-23 12:09:54 +02:00
} ) . show ( ) ;
2020-07-23 13:03:50 +02:00
} ,
filter : function ( item , value ) {
if ( item . data . text . indexOf ( value ) !== - 1 ) {
return true ;
}
if ( item . data . owner && item . data . owner . indexOf ( value ) !== - 1 ) {
return true ;
}
return false ;
} ,
search : function ( tf , value ) {
let me = this ;
let view = me . getView ( ) ;
let store = view . getStore ( ) ;
if ( ! value && value !== 0 ) {
store . clearFilter ( ) ;
store . getRoot ( ) . collapseChildren ( true ) ;
tf . triggers . clear . setVisible ( false ) ;
return ;
}
tf . triggers . clear . setVisible ( true ) ;
if ( value . length < 2 ) return ;
Proxmox . Utils . setErrorMask ( view , true ) ;
// we do it a little bit later for the error mask to work
setTimeout ( function ( ) {
store . clearFilter ( ) ;
store . getRoot ( ) . collapseChildren ( true ) ;
store . beginUpdate ( ) ;
store . getRoot ( ) . cascadeBy ( {
before : function ( item ) {
if ( me . filter ( item , value ) ) {
item . set ( 'matchesFilter' , true ) ;
if ( item . parentNode && item . parentNode . id !== 'root' ) {
item . parentNode . childmatches = true ;
}
return false ;
}
return true ;
} ,
after : function ( item ) {
if ( me . filter ( item , value ) || item . id === 'root' || item . childmatches ) {
item . set ( 'matchesFilter' , true ) ;
if ( item . parentNode && item . parentNode . id !== 'root' ) {
item . parentNode . childmatches = true ;
}
if ( item . childmatches ) {
item . expand ( ) ;
}
} else {
item . set ( 'matchesFilter' , false ) ;
}
delete item . childmatches ;
} ,
} ) ;
store . endUpdate ( ) ;
store . filter ( ( item ) => ! ! item . get ( 'matchesFilter' ) ) ;
Proxmox . Utils . setErrorMask ( view , false ) ;
} , 10 ) ;
} ,
2019-12-20 17:17:44 +01:00
} ,
2020-05-29 15:09:01 +02:00
columns : [
{
xtype : 'treecolumn' ,
header : gettext ( "Backup Group" ) ,
dataIndex : 'text' ,
flex : 1
} ,
2020-07-23 13:03:49 +02:00
{
header : gettext ( 'Actions' ) ,
xtype : 'actioncolumn' ,
dataIndex : 'text' ,
items : [
{
handler : 'onVerify' ,
tooltip : gettext ( 'Verify' ) ,
getClass : ( v , m , rec ) => rec . data . leaf ? 'pmx-hidden' : 'fa fa-search' ,
isDisabled : ( v , r , c , i , rec ) => ! ! rec . data . leaf ,
} ,
{
handler : 'onPrune' ,
tooltip : gettext ( 'Prune' ) ,
getClass : ( v , m , rec ) => rec . parentNode . id === 'root' ? 'fa fa-scissors' : 'pmx-hidden' ,
isDisabled : ( v , r , c , i , rec ) => rec . parentNode . id !== 'root' ,
} ,
{
handler : 'onForget' ,
tooltip : gettext ( 'Forget Snapshot' ) ,
getClass : ( v , m , rec ) => ! rec . data . leaf && rec . parentNode . id !== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden' ,
isDisabled : ( v , r , c , i , rec ) => rec . data . leaf || rec . parentNode . id === 'root' ,
} ,
{
handler : 'downloadFile' ,
tooltip : gettext ( 'Download' ) ,
getClass : ( v , m , rec ) => rec . data . leaf && rec . data . filename ? 'fa fa-download' : 'pmx-hidden' ,
isDisabled : ( v , r , c , i , rec ) => ! rec . data . leaf || ! rec . data . filename || rec . data [ 'crypt-mode' ] > 2 ,
} ,
{
handler : 'openPxarBrowser' ,
tooltip : gettext ( 'Browse' ) ,
getClass : ( v , m , rec ) => {
let data = rec . data ;
if ( data . leaf && data . filename && data . filename . endsWith ( 'pxar.didx' ) ) {
return 'fa fa-folder-open-o' ;
}
return 'pmx-hidden' ;
} ,
isDisabled : ( v , r , c , i , rec ) => {
let data = rec . data ;
return ! ( data . leaf &&
data . filename &&
data . filename . endsWith ( 'pxar.didx' ) &&
2020-08-03 14:10:46 +02:00
data [ 'crypt-mode' ] < 3 ) ;
2020-07-23 13:03:49 +02:00
}
} ,
]
} ,
2020-05-29 15:09:01 +02:00
{
xtype : 'datecolumn' ,
header : gettext ( 'Backup Time' ) ,
sortable : true ,
dataIndex : 'backup-time' ,
format : 'Y-m-d H:i:s' ,
width : 150
} ,
{
header : gettext ( "Size" ) ,
sortable : true ,
dataIndex : 'size' ,
2020-07-08 12:08:02 +02:00
renderer : ( v , meta , record ) => {
2020-07-23 13:03:49 +02:00
if ( record . data . text === 'client.log.blob' && v === undefined ) {
return '' ;
}
2020-07-08 12:08:02 +02:00
if ( v === undefined || v === null ) {
meta . tdCls = "x-grid-row-loading" ;
return '' ;
}
return Proxmox . Utils . format _size ( v ) ;
} ,
2020-05-29 15:09:01 +02:00
} ,
{
xtype : 'numbercolumn' ,
format : '0' ,
header : gettext ( "Count" ) ,
sortable : true ,
dataIndex : 'count' ,
} ,
{
header : gettext ( "Owner" ) ,
sortable : true ,
dataIndex : 'owner' ,
} ,
2020-06-18 13:55:25 +02:00
{
header : gettext ( 'Encrypted' ) ,
2020-07-08 13:32:20 +02:00
dataIndex : 'crypt-mode' ,
2020-07-23 13:03:49 +02:00
renderer : ( v , meta , record ) => {
2020-07-28 14:32:11 +02:00
if ( record . data . size === undefined || record . data . size === null ) {
return '' ;
}
2020-07-23 13:03:49 +02:00
if ( v === - 1 ) {
return '' ;
}
let iconCls = PBS . Utils . cryptIconCls [ v ] || '' ;
let iconTxt = "" ;
if ( iconCls ) {
iconTxt = ` <i class="fa fa-fw fa- ${ iconCls } "></i> ` ;
}
return ( iconTxt + PBS . Utils . cryptText [ v ] ) || Proxmox . Utils . unknownText
}
2020-05-29 15:09:01 +02:00
} ,
] ,
2020-03-26 13:23:28 +01:00
2020-05-29 15:09:01 +02:00
tbar : [
{
text : gettext ( 'Reload' ) ,
iconCls : 'fa fa-refresh' ,
handler : 'reload' ,
} ,
2020-07-23 13:03:50 +02:00
'->' ,
{
xtype : 'tbtext' ,
html : gettext ( 'Search' ) ,
} ,
{
xtype : 'textfield' ,
reference : 'searchbox' ,
triggers : {
clear : {
cls : 'pmx-clear-trigger' ,
weight : - 1 ,
hidden : true ,
handler : function ( ) {
this . triggers . clear . setVisible ( false ) ;
this . setValue ( '' ) ;
} ,
}
} ,
listeners : {
change : {
fn : 'search' ,
buffer : 500 ,
} ,
} ,
}
2020-05-29 15:09:01 +02:00
] ,
2019-12-20 12:46:09 +01:00
} ) ;