]>
git.proxmox.com Git - proxmox-backup.git/blob - www/Utils.js
3 console
. log ( "Starting Backup Server GUI" );
5 Ext
. define ( 'PBS.Utils' , {
8 missingText
: gettext ( 'missing' ),
10 updateLoginData : function ( data
) {
11 Proxmox
. Utils
. setAuthData ( data
);
14 dataStorePrefix
: 'DataStore-' ,
37 calculateCryptMode : function ( data
) {
38 let mixed
= data
. mixed
;
39 let encrypted
= data
. encrypt
;
40 let signed
= data
[ 'sign-only' ];
41 let files
= data
. count
;
43 return PBS
. Utils
. cryptmap
. indexOf ( 'mixed' );
44 } else if ( files
=== encrypted
&& encrypted
> 0 ) {
45 return PBS
. Utils
. cryptmap
. indexOf ( 'encrypt' );
46 } else if ( files
=== signed
&& signed
> 0 ) {
47 return PBS
. Utils
. cryptmap
. indexOf ( 'sign-only' );
48 } else if (( signed
+ encrypted
) === 0 ) {
49 return PBS
. Utils
. cryptmap
. indexOf ( 'none' );
51 return PBS
. Utils
. cryptmap
. indexOf ( 'mixed' );
55 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.' ,
57 getDataStoreFromPath : function ( path
) {
58 return path
. slice ( PBS
. Utils
. dataStorePrefix
. length
);
61 isDataStorePath : function ( path
) {
62 return path
. indexOf ( PBS
. Utils
. dataStorePrefix
) === 0 ;
65 parsePropertyString : function ( value
, defaultKey
) {
69 if ( typeof value
!== 'string' || value
=== '' ) {
73 Ext
. Array
. each ( value
. split ( ',' ), function ( p
) {
74 var kv
= p
. split ( '=' , 2 );
75 if ( Ext
. isDefined ( kv
[ 1 ])) {
77 } else if ( Ext
. isDefined ( defaultKey
)) {
78 if ( Ext
. isDefined ( res
[ defaultKey
])) {
79 error
= 'defaultKey may be only defined once in propertyString' ;
80 return false ; // break
82 res
[ defaultKey
] = kv
[ 0 ];
84 error
= 'invalid propertyString, not a key=value pair and no defaultKey defined' ;
85 return false ; // break
90 if ( error
!== undefined ) {
98 printPropertyString : function ( data
, defaultKey
) {
100 gotDefaultKeyVal
= false ,
103 Ext
. Object
. each ( data
, function ( key
, value
) {
104 if ( defaultKey
!== undefined && key
=== defaultKey
) {
105 gotDefaultKeyVal
= true ;
106 defaultKeyVal
= value
;
107 } else if ( value
!== '' && value
!== undefined ) {
108 stringparts
. push ( key
+ '=' + value
);
112 stringparts
= stringparts
. sort ();
113 if ( gotDefaultKeyVal
) {
114 stringparts
. unshift ( defaultKeyVal
);
117 return stringparts
. join ( ',' );
120 // helper for deleting field which are set to there default values
121 delete_if_default : function ( values
, fieldname
, default_val
, create
) {
122 if ( values
[ fieldname
] === '' || values
[ fieldname
] === default_val
) {
125 if ( Ext
. isArray ( values
. delete )) {
126 values
. delete . push ( fieldname
);
128 values
. delete += ',' + fieldname
;
131 values
. delete = [ fieldname
];
135 delete values
[ fieldname
];
140 render_datetime_utc : function ( datetime
) {
141 let pad
= ( number
) => number
< 10 ? '0' + number
: number
;
142 return datetime
. getUTCFullYear () +
143 '-' + pad ( datetime
. getUTCMonth () + 1 ) +
144 '-' + pad ( datetime
. getUTCDate ()) +
145 'T' + pad ( datetime
. getUTCHours ()) +
146 ':' + pad ( datetime
. getUTCMinutes ()) +
147 ':' + pad ( datetime
. getUTCSeconds ()) +
151 render_datastore_worker_id : function ( id
, what
) {
152 const res
= id
. match ( /^(\S+?):(\S+?)\/(\S+?)(\/(.+))?$/ );
154 let datastore
= res
[ 1 ], backupGroup
= ` ${res[2]} / ${res[3]} ` ;
155 if ( res
[ 4 ] !== undefined ) {
156 let datetime
= Ext
. Date
. parse ( parseInt ( res
[ 5 ], 16 ), 'U' );
157 let utctime
= PBS
. Utils
. render_datetime_utc ( datetime
);
158 return `Datastore ${datastore} ${what} ${backupGroup} / ${utctime} ` ;
160 return `Datastore ${datastore} ${what} ${backupGroup} ` ;
163 return `Datastore ${what} ${id} ` ;
166 render_tape_backup_id : function ( id
, what
) {
167 const res
= id
. match ( /^(\S+?):(\S+?):(\S+?)(:(.+))?$/ );
169 let datastore
= res
[ 1 ];
172 return ` ${what} ${datastore} (pool ${pool} , drive ${drive} )` ;
174 return ` ${what} ${id} ` ;
177 render_drive_load_media_id : function ( id
, what
) {
178 const res
= id
. match ( /^(\S+?):(\S+?)$/ );
182 return gettext ( 'Drive' ) + ` ${drive} - ${what} ' ${label} '` ;
185 return ` ${what} ${id} ` ;
188 // mimics Display trait in backend
189 renderKeyID : function ( fingerprint
) {
190 return fingerprint
. substring ( 0 , 23 );
193 render_task_status : function ( value
, metadata
, record
) {
194 if (! record
. data
[ 'last-run-upid' ]) {
198 if (! record
. data
[ 'last-run-endtime' ]) {
199 metadata
. tdCls
= 'x-grid-row-loading' ;
203 let parsed
= Proxmox
. Utils
. parse_task_status ( value
);
208 icon
= 'question faded' ;
209 text
= Proxmox
. Utils
. unknownText
;
212 icon
= 'times critical' ;
213 text
= Proxmox
. Utils
. errorText
+ ': ' + value
;
216 icon
= 'exclamation warning' ;
220 text
= gettext ( "OK" );
223 return `<i class="fa fa- ${icon} "></i> ${text} ` ;
226 render_next_task_run : function ( value
, metadat
, record
) {
227 if (! value
) return '-' ;
229 let now
= new Date ();
230 let next
= new Date ( value
* 1000 );
233 return gettext ( 'pending' );
235 return Proxmox
. Utils
. render_timestamp ( value
);
238 render_optional_timestamp : function ( value
, metadata
, record
) {
239 if (! value
) return '-' ;
240 return Proxmox
. Utils
. render_timestamp ( value
);
243 parse_datastore_worker_id : function ( type
, id
) {
246 if ( type
. startsWith ( 'verif' )) {
247 res
= PBS
. Utils
. VERIFICATION_JOB_ID_RE
. exec ( id
);
251 } else if ( type
. startsWith ( 'sync' )) {
252 res
= PBS
. Utils
. SYNC_JOB_ID_RE
. exec ( id
);
256 } else if ( type
=== 'backup' ) {
257 res
= PBS
. Utils
. BACKUP_JOB_ID_RE
. exec ( id
);
261 } else if ( type
=== 'garbage_collection' ) {
263 } else if ( type
=== 'prune' ) {
271 extractTokenUser : function ( tokenid
) {
272 return tokenid
. match ( /^(.+)!([^!]+)$/ )[ 1 ];
275 extractTokenName : function ( tokenid
) {
276 return tokenid
. match ( /^(.+)!([^!]+)$/ )[ 2 ];
279 render_estimate : function ( value
) {
280 if ( value
=== undefined ) {
281 return gettext ( 'Not enough data' );
284 let now
= new Date ();
285 let estimate
= new Date ( value
* 1000 );
287 let timespan
= ( estimate
- now
)/ 1000 ;
289 if ( Number ( estimate
) <= Number ( now
) || isNaN ( timespan
)) {
290 return gettext ( 'Never' );
293 let duration
= Proxmox
. Utils
. format_duration_human ( timespan
);
294 return Ext
. String
. format ( gettext ( "in {0}" ), duration
);
297 render_size_usage : function ( val
, max
) {
299 return gettext ( 'N/A' );
301 return ( val
* 100 / max
). toFixed ( 2 ) + '% (' +
302 Ext
. String
. format ( gettext ( '{0} of {1}' ),
303 Proxmox
. Utils
. format_size ( val
), Proxmox
. Utils
. format_size ( max
)) + ')' ;
306 get_help_tool : function ( blockid
) {
307 let info
= Proxmox
. Utils
. get_help_info ( blockid
);
308 if ( info
=== undefined ) {
309 info
= Proxmox
. Utils
. get_help_info ( 'pbs_documentation_index' );
311 if ( info
=== undefined ) {
312 throw "get_help_info failed" ; // should not happen
315 let docsURI
= window
. location
. origin
+ info
. link
;
316 let title
= info
. title
;
318 title
+= ' - ' + info
. subtitle
;
323 handler : function () {
324 window
. open ( docsURI
);
329 calculate_dedup_factor : function ( gcstatus
) {
331 if ( gcstatus
[ 'disk-bytes' ] > 0 ) {
332 dedup
= ( gcstatus
[ 'index-data-bytes' ] || 0 )/ gcstatus
[ 'disk-bytes' ];
337 parse_snapshot_id : function ( snapshot
) {
339 return [ undefined , undefined , undefined ];
341 let [ _match
, type
, group
, id
] = /^([^/ ]+)\ /([^/ ]+)\ /(.+)$/ . exec ( snapshot
);
343 return [ type
, group
, id
];
346 get_type_icon_cls : function ( btype
) {
348 if ( btype
. startsWith ( 'vm' )) {
350 } else if ( btype
. startsWith ( 'ct' )) {
352 } else if ( btype
. startsWith ( 'host' )) {
358 constructor : function () {
361 let PROXMOX_SAFE_ID_REGEX
= "([A-Za-z0-9_][A-Za-z0-9._-]*)" ;
362 // only anchored at beginning
363 // only parses datastore for now
364 me
. VERIFICATION_JOB_ID_RE
= new RegExp ( "^" + PROXMOX_SAFE_ID_REGEX
+ ':?' );
365 me
. SYNC_JOB_ID_RE
= new RegExp ( "^" + PROXMOX_SAFE_ID_REGEX
+ ':' +
366 PROXMOX_SAFE_ID_REGEX
+ ':' + PROXMOX_SAFE_ID_REGEX
+ ':' );
367 me
. BACKUP_JOB_ID_RE
= new RegExp ( "^" + PROXMOX_SAFE_ID_REGEX
+ ':' );
369 // do whatever you want here
370 Proxmox
. Utils
. override_task_descriptions ({
371 'acme-deactivate' : ( type
, id
) =>
372 Ext
. String
. format ( gettext ( "Deactivate {0} Account" ), 'ACME' ) + ` ' ${id || 'default'} '` ,
373 'acme-register' : ( type
, id
) =>
374 Ext
. String
. format ( gettext ( "Register {0} Account" ), 'ACME' ) + ` ' ${id || 'default'} '` ,
375 'acme-update' : ( type
, id
) =>
376 Ext
. String
. format ( gettext ( "Update {0} Account" ), 'ACME' ) + ` ' ${id || 'default'} '` ,
377 'acme-new-cert' : [ '' , gettext ( 'Order Certificate' )],
378 'acme-renew-cert' : [ '' , gettext ( 'Renew Certificate' )],
379 'acme-revoke-cert' : [ '' , gettext ( 'Revoke Certificate' )],
380 backup
: ( type
, id
) => PBS
. Utils
. render_datastore_worker_id ( id
, gettext ( 'Backup' )),
381 'barcode-label-media' : [ gettext ( 'Drive' ), gettext ( 'Barcode-Label Media' )],
382 'catalog-media' : [ gettext ( 'Drive' ), gettext ( 'Catalog Media' )],
383 'delete-datastore' : [ gettext ( 'Datastore' ), gettext ( 'Remove Datastore' )],
384 dircreate
: [ gettext ( 'Directory Storage' ), gettext ( 'Create' )],
385 dirremove
: [ gettext ( 'Directory' ), gettext ( 'Remove' )],
386 'eject-media' : [ gettext ( 'Drive' ), gettext ( 'Eject Media' )],
387 "format-media" : [ gettext ( 'Drive' ), gettext ( 'Format media' )],
388 "forget-group" : [ gettext ( 'Group' ), gettext ( 'Remove Group' )],
389 garbage_collection
: [ 'Datastore' , gettext ( 'Garbage Collect' )],
390 'inventory-update' : [ gettext ( 'Drive' ), gettext ( 'Inventory Update' )],
391 'label-media' : [ gettext ( 'Drive' ), gettext ( 'Label Media' )],
392 'load-media' : ( type
, id
) => PBS
. Utils
. render_drive_load_media_id ( id
, gettext ( 'Load Media' )),
393 logrotate
: [ null , gettext ( 'Log Rotation' )],
394 prune
: ( type
, id
) => PBS
. Utils
. render_datastore_worker_id ( id
, gettext ( 'Prune' )),
395 reader
: ( type
, id
) => PBS
. Utils
. render_datastore_worker_id ( id
, gettext ( 'Read Objects' )),
396 'rewind-media' : [ gettext ( 'Drive' ), gettext ( 'Rewind Media' )],
397 sync
: [ 'Datastore' , gettext ( 'Remote Sync' )],
398 syncjob
: [ gettext ( 'Sync Job' ), gettext ( 'Remote Sync' )],
399 'tape-backup' : ( type
, id
) => PBS
. Utils
. render_tape_backup_id ( id
, gettext ( 'Tape Backup' )),
400 'tape-backup-job' : ( type
, id
) => PBS
. Utils
. render_tape_backup_id ( id
, gettext ( 'Tape Backup Job' )),
401 'tape-restore' : [ 'Datastore' , gettext ( 'Tape Restore' )],
402 'unload-media' : [ gettext ( 'Drive' ), gettext ( 'Unload Media' )],
403 verificationjob
: [ gettext ( 'Verify Job' ), gettext ( 'Scheduled Verification' )],
404 verify
: [ 'Datastore' , gettext ( 'Verification' )],
405 verify_group
: [ 'Group' , gettext ( 'Verification' )],
406 verify_snapshot
: [ 'Snapshot' , gettext ( 'Verification' )],
407 zfscreate
: [ gettext ( 'ZFS Storage' ), gettext ( 'Create' )],
410 Proxmox
. Schema
. overrideAuthDomains ({
412 name
: 'Proxmox Backup authentication server' ,
420 // Convert an ArrayBuffer to a base64url encoded string.
421 // A `null` value will be preserved for convenience.
422 bytes_to_base64url : function ( bytes
) {
423 if ( bytes
=== null ) {
428 . from ( new Uint8Array ( bytes
))
429 . map ( val
=> String
. fromCharCode ( val
))
434 . replace ( /[=]/g , '' );
437 // Convert an a base64url string to an ArrayBuffer.
438 // A `null` value will be preserved for convenience.
439 base64url_to_bytes : function ( b64u
) {
444 return new Uint8Array (
450 .map(val => val.charCodeAt(0)),
454 driveCommand: function(driveid, command, reqOpts) {
455 let params = Ext.apply(reqOpts, {
456 url: `/api2/extjs/tape/drive/ ${driveid} / ${command} `,
458 failure: function(response) {
459 Ext.Msg.alert(gettext(' Error
'), response.htmlStatus);
463 Proxmox.Utils.API2Request(params);
466 showMediaLabelWindow: function(response) {
468 for (let [key, val] of Object.entries(response.result.data)) {
469 if (key === ' ctime
' || key === ' media
- set - ctime
') {
470 val = Proxmox.Utils.render_timestamp(val);
472 list.push({ key: key, value: val });
475 Ext.create(' Ext
. window
. Window
', {
476 title: gettext(' Label Information
'),
490 text: gettext(' Property
'),
495 text: gettext(' Value
'),
505 showCartridgeMemoryWindow: function(response) {
506 Ext.create(' Ext
. window
. Window
', {
507 title: gettext(' Cartridge Memory
'),
517 data: response.result.data,
527 text: gettext(' Name
'),
532 text: gettext(' Value
'),
542 showVolumeStatisticsWindow: function(response) {
544 for (let [key, val] of Object.entries(response.result.data)) {
545 if (key === ' total
- native - capacity
' ||
546 key === ' total
- used
- native - capacity
' ||
547 key === ' lifetime
- bytes
- read
' ||
548 key === ' lifetime
- bytes
- written
' ||
549 key === ' last
- mount
- bytes
- read
' ||
550 key === ' last
- mount
- bytes
- written
') {
551 val = Proxmox.Utils.format_size(val);
553 list.push({ key: key, value: val });
555 Ext.create(' Ext
. window
. Window
', {
556 title: gettext(' Volume Statistics
'),
570 text: gettext(' Property
'),
575 text: gettext(' Value
'),
585 showDriveStatusWindow: function(response) {
587 for (let [key, val] of Object.entries(response.result.data)) {
588 if (key === ' manufactured
') {
589 val = Proxmox.Utils.render_timestamp(val);
591 if (key === ' bytes
- read
' || key === ' bytes
- written
') {
592 val = Proxmox.Utils.format_size(val);
594 list.push({ key: key, value: val });
597 Ext.create(' Ext
. window
. Window
', {
598 title: gettext(' Status
'),
612 text: gettext(' Property
'),
617 text: gettext(' Value
'),
627 renderDriveState: function(value, md) {
629 return gettext(' Idle
');
632 let icon = ' < i
class = "fa fa-spinner fa-pulse fa-fw" ></ i
> ';
634 if (value.startsWith("UPID")) {
635 let upid = Proxmox.Utils.parse_task_upid(value);
636 md.tdCls = "pointer";
637 return ` ${icon} ${upid.desc} `;
640 return ` ${icon} ${value} `;