2018-12-05 11:39:40 +00:00
Ext . ns ( 'PBS' ) ;
console . log ( "Starting Backup Server GUI" ) ;
Ext . define ( 'PBS.Utils' , {
singleton : true ,
2021-02-26 06:48:13 +00:00
missingText : gettext ( 'missing' ) ,
2019-01-30 14:14:20 +00:00
updateLoginData : function ( data ) {
2020-05-29 14:22:14 +00:00
Proxmox . Utils . setAuthData ( data ) ;
2019-01-30 14:14:20 +00:00
} ,
2020-05-20 10:15:38 +00:00
dataStorePrefix : 'DataStore-' ,
2020-07-08 11:32:20 +00:00
cryptmap : [
'none' ,
'mixed' ,
'sign-only' ,
'encrypt' ,
] ,
cryptText : [
Proxmox . Utils . noText ,
gettext ( 'Mixed' ) ,
gettext ( 'Signed' ) ,
gettext ( 'Encrypted' ) ,
] ,
cryptIconCls : [
'' ,
'' ,
2020-07-23 11:03:49 +00:00
'lock faded' ,
'lock good' ,
2020-07-08 11:32:20 +00:00
] ,
2020-07-09 14:50:24 +00:00
calculateCryptMode : function ( data ) {
let mixed = data . mixed ;
let encrypted = data . encrypt ;
let signed = data [ 'sign-only' ] ;
let files = data . count ;
if ( mixed > 0 ) {
return PBS . Utils . cryptmap . indexOf ( 'mixed' ) ;
2020-09-09 14:18:11 +00:00
} else if ( files === encrypted && encrypted > 0 ) {
2020-07-08 11:32:20 +00:00
return PBS . Utils . cryptmap . indexOf ( 'encrypt' ) ;
2020-09-09 14:18:11 +00:00
} else if ( files === signed && signed > 0 ) {
2020-07-08 11:32:20 +00:00
return PBS . Utils . cryptmap . indexOf ( 'sign-only' ) ;
} else if ( ( signed + encrypted ) === 0 ) {
return PBS . Utils . cryptmap . indexOf ( 'none' ) ;
} else {
return PBS . Utils . cryptmap . indexOf ( 'mixed' ) ;
}
} ,
2020-11-10 07:07:49 +00:00
noSubKeyHtml : 'You do not have a valid subscription for this server. Please visit <a target="_blank" href="https://www.proxmox.com/proxmox-backup-server/pricing">www.proxmox.com</a> to get a list of available options.' ,
2020-05-20 10:15:38 +00:00
getDataStoreFromPath : function ( path ) {
return path . slice ( PBS . Utils . dataStorePrefix . length ) ;
} ,
isDataStorePath : function ( path ) {
return path . indexOf ( PBS . Utils . dataStorePrefix ) === 0 ;
} ,
2020-11-06 16:49:42 +00:00
parsePropertyString : function ( value , defaultKey ) {
var res = { } ,
error ;
if ( typeof value !== 'string' || value === '' ) {
return res ;
}
Ext . Array . each ( value . split ( ',' ) , function ( p ) {
var kv = p . split ( '=' , 2 ) ;
if ( Ext . isDefined ( kv [ 1 ] ) ) {
res [ kv [ 0 ] ] = kv [ 1 ] ;
} else if ( Ext . isDefined ( defaultKey ) ) {
if ( Ext . isDefined ( res [ defaultKey ] ) ) {
error = 'defaultKey may be only defined once in propertyString' ;
return false ; // break
}
res [ defaultKey ] = kv [ 0 ] ;
} else {
error = 'invalid propertyString, not a key=value pair and no defaultKey defined' ;
return false ; // break
}
return true ;
} ) ;
if ( error !== undefined ) {
console . error ( error ) ;
return null ;
}
return res ;
} ,
printPropertyString : function ( data , defaultKey ) {
var stringparts = [ ] ,
gotDefaultKeyVal = false ,
defaultKeyVal ;
Ext . Object . each ( data , function ( key , value ) {
if ( defaultKey !== undefined && key === defaultKey ) {
gotDefaultKeyVal = true ;
defaultKeyVal = value ;
} else if ( value !== '' && value !== undefined ) {
stringparts . push ( key + '=' + value ) ;
}
} ) ;
stringparts = stringparts . sort ( ) ;
if ( gotDefaultKeyVal ) {
stringparts . unshift ( defaultKeyVal ) ;
}
return stringparts . join ( ',' ) ;
} ,
// helper for deleting field which are set to there default values
delete _if _default : function ( values , fieldname , default _val , create ) {
if ( values [ fieldname ] === '' || values [ fieldname ] === default _val ) {
if ( ! create ) {
if ( values . delete ) {
if ( Ext . isArray ( values . delete ) ) {
values . delete . push ( fieldname ) ;
} else {
values . delete += ',' + fieldname ;
}
} else {
values . delete = [ fieldname ] ;
}
}
delete values [ fieldname ] ;
}
} ,
2020-05-26 16:16:38 +00:00
render _datetime _utc : function ( datetime ) {
let pad = ( number ) => number < 10 ? '0' + number : number ;
return datetime . getUTCFullYear ( ) +
'-' + pad ( datetime . getUTCMonth ( ) + 1 ) +
'-' + pad ( datetime . getUTCDate ( ) ) +
'T' + pad ( datetime . getUTCHours ( ) ) +
':' + pad ( datetime . getUTCMinutes ( ) ) +
':' + pad ( datetime . getUTCSeconds ( ) ) +
'Z' ;
} ,
2020-05-26 11:37:57 +00:00
render _datastore _worker _id : function ( id , what ) {
2020-11-10 10:53:09 +00:00
const res = id . match ( /^(\S+?):(\S+?)\/(\S+?)(\/(.+))?$/ ) ;
2020-05-26 16:17:01 +00:00
if ( res ) {
2020-09-25 16:40:03 +00:00
let datastore = res [ 1 ] , backupGroup = ` ${ res [ 2 ] } / ${ res [ 3 ] } ` ;
2020-06-30 11:11:22 +00:00
if ( res [ 4 ] !== undefined ) {
let datetime = Ext . Date . parse ( parseInt ( res [ 5 ] , 16 ) , 'U' ) ;
let utctime = PBS . Utils . render _datetime _utc ( datetime ) ;
2020-09-25 16:40:03 +00:00
return ` Datastore ${ datastore } ${ what } ${ backupGroup } / ${ utctime } ` ;
2020-06-30 11:11:22 +00:00
} else {
2020-09-25 16:40:03 +00:00
return ` Datastore ${ datastore } ${ what } ${ backupGroup } ` ;
2020-06-30 11:11:22 +00:00
}
2020-05-26 16:17:01 +00:00
}
2020-06-30 11:11:22 +00:00
return ` Datastore ${ what } ${ id } ` ;
2020-05-26 16:17:01 +00:00
} ,
2020-05-26 11:37:57 +00:00
2021-02-15 06:55:13 +00:00
render _tape _backup _id : function ( id , what ) {
const res = id . match ( /^(\S+?):(\S+?):(\S+?)(:(.+))?$/ ) ;
if ( res ) {
let datastore = res [ 1 ] ;
let pool = res [ 2 ] ;
let drive = res [ 3 ] ;
return ` ${ what } ${ datastore } (pool ${ pool } , drive ${ drive } ) ` ;
}
return ` ${ what } ${ id } ` ;
} ,
2021-02-18 08:23:50 +00:00
render _drive _load _media _id : function ( id , what ) {
const res = id . match ( /^(\S+?):(\S+?)$/ ) ;
if ( res ) {
let drive = res [ 1 ] ;
let label = res [ 2 ] ;
return gettext ( 'Drive' ) + ` ${ drive } - ${ what } ' ${ label } ' ` ;
}
return ` ${ what } ${ id } ` ;
} ,
2020-11-20 16:38:43 +00:00
// mimics Display trait in backend
renderKeyID : function ( fingerprint ) {
return fingerprint . substring ( 0 , 23 ) ;
} ,
2021-02-19 08:08:00 +00:00
render _task _status : function ( value , metadata , record ) {
if ( ! record . data [ 'last-run-upid' ] ) {
return '-' ;
}
if ( ! record . data [ 'last-run-endtime' ] ) {
metadata . tdCls = 'x-grid-row-loading' ;
return '' ;
}
let parsed = Proxmox . Utils . parse _task _status ( value ) ;
let text = value ;
let icon = '' ;
switch ( parsed ) {
case 'unknown' :
icon = 'question faded' ;
text = Proxmox . Utils . unknownText ;
break ;
case 'error' :
icon = 'times critical' ;
text = Proxmox . Utils . errorText + ': ' + value ;
break ;
case 'warning' :
icon = 'exclamation warning' ;
break ;
case 'ok' :
icon = 'check good' ;
text = gettext ( "OK" ) ;
}
return ` <i class="fa fa- ${ icon } "></i> ${ text } ` ;
} ,
render _next _task _run : function ( value , metadat , record ) {
if ( ! value ) return '-' ;
let now = new Date ( ) ;
let next = new Date ( value * 1000 ) ;
if ( next < now ) {
return gettext ( 'pending' ) ;
}
return Proxmox . Utils . render _timestamp ( value ) ;
} ,
render _optional _timestamp : function ( value , metadata , record ) {
if ( ! value ) return '-' ;
return Proxmox . Utils . render _timestamp ( value ) ;
} ,
2020-11-09 15:01:24 +00:00
parse _datastore _worker _id : function ( type , id ) {
let result ;
let res ;
if ( type . startsWith ( 'verif' ) ) {
res = PBS . Utils . VERIFICATION _JOB _ID _RE . exec ( id ) ;
if ( res ) {
result = res [ 1 ] ;
}
} else if ( type . startsWith ( 'sync' ) ) {
res = PBS . Utils . SYNC _JOB _ID _RE . exec ( id ) ;
if ( res ) {
result = res [ 3 ] ;
}
} else if ( type === 'backup' ) {
res = PBS . Utils . BACKUP _JOB _ID _RE . exec ( id ) ;
if ( res ) {
result = res [ 1 ] ;
}
} else if ( type === 'garbage_collection' ) {
return id ;
} else if ( type === 'prune' ) {
return id ;
}
return result ;
} ,
2020-10-22 09:40:43 +00:00
extractTokenUser : function ( tokenid ) {
return tokenid . match ( /^(.+)!([^!]+)$/ ) [ 1 ] ;
} ,
extractTokenName : function ( tokenid ) {
return tokenid . match ( /^(.+)!([^!]+)$/ ) [ 2 ] ;
} ,
2020-11-09 15:01:22 +00:00
render _estimate : function ( value ) {
2021-03-02 13:38:49 +00:00
if ( value === undefined ) {
2020-11-09 15:01:22 +00:00
return gettext ( 'Not enough data' ) ;
}
let now = new Date ( ) ;
let estimate = new Date ( value * 1000 ) ;
let timespan = ( estimate - now ) / 1000 ;
if ( Number ( estimate ) <= Number ( now ) || isNaN ( timespan ) ) {
return gettext ( 'Never' ) ;
}
let duration = Proxmox . Utils . format _duration _human ( timespan ) ;
return Ext . String . format ( gettext ( "in {0}" ) , duration ) ;
} ,
2020-11-09 15:01:23 +00:00
render _size _usage : function ( val , max ) {
if ( max === 0 ) {
return gettext ( 'N/A' ) ;
}
return ( val * 100 / max ) . toFixed ( 2 ) + '% (' +
Ext . String . format ( gettext ( '{0} of {1}' ) ,
Proxmox . Utils . format _size ( val ) , Proxmox . Utils . format _size ( max ) ) + ')' ;
} ,
2020-11-10 08:15:12 +00:00
get _help _tool : function ( blockid ) {
let info = Proxmox . Utils . get _help _info ( blockid ) ;
if ( info === undefined ) {
info = Proxmox . Utils . get _help _info ( 'pbs_documentation_index' ) ;
}
if ( info === undefined ) {
throw "get_help_info failed" ; // should not happen
}
let docsURI = window . location . origin + info . link ;
let title = info . title ;
if ( info . subtitle ) {
title += ' - ' + info . subtitle ;
}
return {
type : 'help' ,
tooltip : title ,
handler : function ( ) {
window . open ( docsURI ) ;
2020-11-10 08:24:35 +00:00
} ,
2020-11-10 08:15:12 +00:00
} ;
} ,
2020-11-10 09:18:07 +00:00
calculate _dedup _factor : function ( gcstatus ) {
let dedup = 1.0 ;
if ( gcstatus [ 'disk-bytes' ] > 0 ) {
dedup = ( gcstatus [ 'index-data-bytes' ] || 0 ) / gcstatus [ 'disk-bytes' ] ;
}
return dedup ;
} ,
2021-02-02 13:00:36 +00:00
parse _snapshot _id : function ( snapshot ) {
if ( ! snapshot ) {
return [ undefined , undefined , undefined ] ;
}
let [ _match , type , group , id ] = /^([^/]+)\/([^/]+)\/(.+)$/ . exec ( snapshot ) ;
return [ type , group , id ] ;
} ,
2021-02-02 13:00:35 +00:00
get _type _icon _cls : function ( btype ) {
var cls = '' ;
if ( btype . startsWith ( 'vm' ) ) {
cls = 'fa-desktop' ;
} else if ( btype . startsWith ( 'ct' ) ) {
cls = 'fa-cube' ;
} else if ( btype . startsWith ( 'host' ) ) {
cls = 'fa-building' ;
}
return cls ;
} ,
2018-12-05 11:39:40 +00:00
constructor : function ( ) {
var me = this ;
2020-11-09 15:01:24 +00:00
let PROXMOX _SAFE _ID _REGEX = "([A-Za-z0-9_][A-Za-z0-9._-]*)" ;
// only anchored at beginning
// only parses datastore for now
me . VERIFICATION _JOB _ID _RE = new RegExp ( "^" + PROXMOX _SAFE _ID _REGEX + ':?' ) ;
me . SYNC _JOB _ID _RE = new RegExp ( "^" + PROXMOX _SAFE _ID _REGEX + ':' +
PROXMOX _SAFE _ID _REGEX + ':' + PROXMOX _SAFE _ID _REGEX + ':' ) ;
me . BACKUP _JOB _ID _RE = new RegExp ( "^" + PROXMOX _SAFE _ID _REGEX + ':' ) ;
2018-12-05 11:39:40 +00:00
// do whatever you want here
2020-05-25 17:06:47 +00:00
Proxmox . Utils . override _task _descriptions ( {
2020-10-30 13:02:58 +00:00
backup : ( type , id ) => PBS . Utils . render _datastore _worker _id ( id , gettext ( 'Backup' ) ) ,
2021-03-28 14:45:23 +00:00
'barcode-label-media' : [ gettext ( 'Drive' ) , gettext ( 'Barcode-Label Media' ) ] ,
'catalog-media' : [ gettext ( 'Drive' ) , gettext ( 'Catalog Media' ) ] ,
2020-10-30 13:02:58 +00:00
dircreate : [ gettext ( 'Directory Storage' ) , gettext ( 'Create' ) ] ,
dirremove : [ gettext ( 'Directory' ) , gettext ( 'Remove' ) ] ,
2021-03-28 14:45:23 +00:00
'eject-media' : [ gettext ( 'Drive' ) , gettext ( 'Eject Media' ) ] ,
'erase-media' : [ gettext ( 'Drive' ) , gettext ( 'Erase Media' ) ] ,
2021-03-28 11:50:50 +00:00
garbage _collection : [ 'Datastore' , gettext ( 'Garbage Collect' ) ] ,
2021-03-28 14:45:23 +00:00
'inventory-update' : [ gettext ( 'Drive' ) , gettext ( 'Inventory Update' ) ] ,
'label-media' : [ gettext ( 'Drive' ) , gettext ( 'Label Media' ) ] ,
'load-media' : ( type , id ) => PBS . Utils . render _drive _load _media _id ( id , gettext ( 'Load Media' ) ) ,
2020-11-04 13:20:44 +00:00
logrotate : [ null , gettext ( 'Log Rotation' ) ] ,
2020-10-30 13:02:58 +00:00
prune : ( type , id ) => PBS . Utils . render _datastore _worker _id ( id , gettext ( 'Prune' ) ) ,
2021-03-28 11:50:50 +00:00
reader : ( type , id ) => PBS . Utils . render _datastore _worker _id ( id , gettext ( 'Read Objects' ) ) ,
2021-03-28 14:45:23 +00:00
'rewind-media' : [ gettext ( 'Drive' ) , gettext ( 'Rewind Media' ) ] ,
2020-09-25 16:40:03 +00:00
sync : [ 'Datastore' , gettext ( 'Remote Sync' ) ] ,
2020-10-30 13:02:58 +00:00
syncjob : [ gettext ( 'Sync Job' ) , gettext ( 'Remote Sync' ) ] ,
2021-03-28 14:45:23 +00:00
'tape-backup' : ( type , id ) => PBS . Utils . render _tape _backup _id ( id , gettext ( 'Tape Backup' ) ) ,
'tape-backup-job' : ( type , id ) => PBS . Utils . render _tape _backup _id ( id , gettext ( 'Tape Backup Job' ) ) ,
'tape-restore' : [ 'Datastore' , gettext ( 'Tape Restore' ) ] ,
'unload-media' : [ gettext ( 'Drive' ) , gettext ( 'Unload Media' ) ] ,
verificationjob : [ gettext ( 'Verify Job' ) , gettext ( 'Scheduled Verification' ) ] ,
2020-10-20 09:10:10 +00:00
verify : [ 'Datastore' , gettext ( 'Verification' ) ] ,
verify _group : [ 'Group' , gettext ( 'Verification' ) ] ,
verify _snapshot : [ 'Snapshot' , gettext ( 'Verification' ) ] ,
2020-10-30 13:02:58 +00:00
zfscreate : [ gettext ( 'ZFS Storage' ) , gettext ( 'Create' ) ] ,
2020-05-25 17:06:47 +00:00
} ) ;
2020-09-25 16:40:03 +00:00
} ,
2020-11-02 13:36:10 +00:00
// Convert an ArrayBuffer to a base64url encoded string.
// A `null` value will be preserved for convenience.
bytes _to _base64url : function ( bytes ) {
if ( bytes === null ) {
return null ;
}
return btoa ( Array
. from ( new Uint8Array ( bytes ) )
. map ( val => String . fromCharCode ( val ) )
. join ( '' ) ,
)
. replace ( /\+/g , '-' )
. replace ( /\//g , '_' )
. replace ( /[=]/g , '' ) ;
} ,
// Convert an a base64url string to an ArrayBuffer.
// A `null` value will be preserved for convenience.
base64url _to _bytes : function ( b64u ) {
if ( b64u === null ) {
return null ;
}
return new Uint8Array (
atob ( b64u
. replace ( /-/g , '+' )
. replace ( /_/g , '/' ) ,
)
. split ( '' )
. map ( val => val . charCodeAt ( 0 ) ) ,
) ;
} ,
2021-02-25 10:52:42 +00:00
driveCommand : function ( driveid , command , reqOpts ) {
let params = Ext . apply ( reqOpts , {
url : ` /api2/extjs/tape/drive/ ${ driveid } / ${ command } ` ,
timeout : 5 * 60 * 1000 ,
failure : function ( response ) {
Ext . Msg . alert ( gettext ( 'Error' ) , response . htmlStatus ) ;
} ,
} ) ;
Proxmox . Utils . API2Request ( params ) ;
} ,
showMediaLabelWindow : function ( response ) {
let list = [ ] ;
for ( let [ key , val ] of Object . entries ( response . result . data ) ) {
if ( key === 'ctime' || key === 'media-set-ctime' ) {
val = Proxmox . Utils . render _timestamp ( val ) ;
}
list . push ( { key : key , value : val } ) ;
}
Ext . create ( 'Ext.window.Window' , {
title : gettext ( 'Label Information' ) ,
modal : true ,
width : 600 ,
height : 450 ,
layout : 'fit' ,
scrollable : true ,
items : [
{
xtype : 'grid' ,
store : {
data : list ,
} ,
columns : [
{
text : gettext ( 'Property' ) ,
dataIndex : 'key' ,
width : 120 ,
} ,
{
text : gettext ( 'Value' ) ,
dataIndex : 'value' ,
flex : 1 ,
} ,
] ,
} ,
] ,
} ) . show ( ) ;
} ,
showCartridgeMemoryWindow : function ( response ) {
Ext . create ( 'Ext.window.Window' , {
title : gettext ( 'Cartridge Memory' ) ,
modal : true ,
width : 600 ,
height : 450 ,
layout : 'fit' ,
scrollable : true ,
items : [
{
xtype : 'grid' ,
store : {
data : response . result . data ,
} ,
columns : [
{
text : gettext ( 'ID' ) ,
2021-03-08 11:52:06 +00:00
hidden : true ,
2021-02-25 10:52:42 +00:00
dataIndex : 'id' ,
width : 60 ,
} ,
{
text : gettext ( 'Name' ) ,
dataIndex : 'name' ,
flex : 2 ,
} ,
{
text : gettext ( 'Value' ) ,
dataIndex : 'value' ,
flex : 1 ,
} ,
] ,
} ,
] ,
} ) . show ( ) ;
} ,
showVolumeStatisticsWindow : function ( response ) {
let list = [ ] ;
for ( let [ key , val ] of Object . entries ( response . result . data ) ) {
if ( key === 'total-native-capacity' ||
key === 'total-used-native-capacity' ||
key === 'lifetime-bytes-read' ||
key === 'lifetime-bytes-written' ||
key === 'last-mount-bytes-read' ||
key === 'last-mount-bytes-written' ) {
val = Proxmox . Utils . format _size ( val ) ;
}
list . push ( { key : key , value : val } ) ;
}
Ext . create ( 'Ext.window.Window' , {
title : gettext ( 'Volume Statistics' ) ,
modal : true ,
width : 600 ,
height : 450 ,
layout : 'fit' ,
scrollable : true ,
items : [
{
xtype : 'grid' ,
store : {
data : list ,
} ,
columns : [
{
text : gettext ( 'Property' ) ,
dataIndex : 'key' ,
flex : 1 ,
} ,
{
text : gettext ( 'Value' ) ,
dataIndex : 'value' ,
flex : 1 ,
} ,
] ,
} ,
] ,
} ) . show ( ) ;
} ,
showDriveStatusWindow : function ( response ) {
let list = [ ] ;
for ( let [ key , val ] of Object . entries ( response . result . data ) ) {
if ( key === 'manufactured' ) {
val = Proxmox . Utils . render _timestamp ( val ) ;
}
if ( key === 'bytes-read' || key === 'bytes-written' ) {
val = Proxmox . Utils . format _size ( val ) ;
}
list . push ( { key : key , value : val } ) ;
}
Ext . create ( 'Ext.window.Window' , {
title : gettext ( 'Status' ) ,
modal : true ,
width : 600 ,
height : 450 ,
layout : 'fit' ,
scrollable : true ,
items : [
{
xtype : 'grid' ,
store : {
data : list ,
} ,
columns : [
{
text : gettext ( 'Property' ) ,
dataIndex : 'key' ,
width : 120 ,
} ,
{
text : gettext ( 'Value' ) ,
dataIndex : 'value' ,
flex : 1 ,
} ,
] ,
} ,
] ,
} ) . show ( ) ;
} ,
2021-03-02 11:19:37 +00:00
renderDriveState : function ( value , md ) {
if ( ! value ) {
return gettext ( 'Idle' ) ;
}
let icon = '<i class="fa fa-spinner fa-pulse fa-fw"></i>' ;
if ( value . startsWith ( "UPID" ) ) {
let upid = Proxmox . Utils . parse _task _upid ( value ) ;
md . tdCls = "pointer" ;
return ` ${ icon } ${ upid . desc } ` ;
}
return ` ${ icon } ${ value } ` ;
} ,
2020-11-02 13:36:10 +00:00
} ) ;
Ext . define ( 'PBS.Async' , {
singleton : true ,
// Returns a Promise resolving to the result of an `API2Request`.
api2 : function ( reqOpts ) {
return new Promise ( ( resolve , reject ) => {
delete reqOpts . callback ; // not allowed in this api
reqOpts . success = response => resolve ( response ) ;
reqOpts . failure = response => {
if ( response . result && response . result . message ) {
reject ( response . result . message ) ;
} else {
reject ( "api call failed" ) ;
}
} ;
Proxmox . Utils . API2Request ( reqOpts ) ;
} ) ;
} ,
// Delay for a number of milliseconds.
sleep : function ( millis ) {
return new Promise ( ( resolve , _reject ) => setTimeout ( resolve , millis ) ) ;
} ,
2018-12-05 11:39:40 +00:00
} ) ;