define ( [
'jquery' ,
'/api/config' ,
'/customize/application_config.js' ,
'/bower_components/chainpad-crypto/crypto.js' ,
'/common/toolbar.js' ,
'/bower_components/nthen/index.js' ,
'/common/sframe-common.js' ,
'/common/hyperscript.js' ,
'/customize/messages.js' ,
'/common/common-interface.js' ,
'/common/common-ui-elements.js' ,
'/common/common-util.js' ,
'/common/common-hash.js' ,
'/common/common-signing-keys.js' ,
'/support/ui.js' ,
'/lib/datepicker/flatpickr.js' ,
'/bower_components/tweetnacl/nacl-fast.min.js' ,
'css!/lib/datepicker/flatpickr.min.css' ,
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css' ,
'css!/bower_components/components-font-awesome/css/font-awesome.min.css' ,
'less!/admin/app-admin.less' ,
] , function (
$ ,
ApiConfig ,
AppConfig ,
Crypto ,
Toolbar ,
nThen ,
SFCommon ,
h ,
Messages ,
UI ,
UIElements ,
Util ,
Hash ,
Keys ,
Support ,
Flatpickr
)
{
var APP = {
'instanceStatus' : { }
} ;
var Nacl = window . nacl ;
var common ;
var sFrameChan ;
var categories = {
'general' : [ // Msg.admin_cat_general
'cp-admin-flush-cache' ,
'cp-admin-update-limit' ,
'cp-admin-registration' ,
'cp-admin-email' ,
'cp-admin-instance-info-notice' ,
'cp-admin-name' ,
'cp-admin-description' ,
'cp-admin-jurisdiction' ,
] ,
'quota' : [ // Msg.admin_cat_quota
'cp-admin-defaultlimit' ,
'cp-admin-setlimit' ,
'cp-admin-archive' ,
'cp-admin-unarchive' ,
'cp-admin-getquota' ,
'cp-admin-getlimits' ,
] ,
'stats' : [ // Msg.admin_cat_stats
'cp-admin-refresh-stats' ,
'cp-admin-uptime' ,
'cp-admin-active-sessions' ,
'cp-admin-active-pads' ,
'cp-admin-open-files' ,
'cp-admin-registered' ,
'cp-admin-disk-usage' ,
] ,
'support' : [ // Msg.admin_cat_support
'cp-admin-support-list' ,
'cp-admin-support-init' ,
'cp-admin-support-priv' ,
] ,
'broadcast' : [ // Msg.admin_cat_broadcast
'cp-admin-maintenance' ,
'cp-admin-survey' ,
'cp-admin-broadcast' ,
] ,
'performance' : [ // Msg.admin_cat_performance
'cp-admin-refresh-performance' ,
'cp-admin-performance-profiling' ,
'cp-admin-enable-disk-measurements' ,
'cp-admin-bytes-written' ,
] ,
'network' : [ // Msg.admin_cat_network
'cp-admin-update-available' ,
'cp-admin-checkup' ,
'cp-admin-block-daily-check' ,
//'cp-admin-provide-aggregate-statistics',
'cp-admin-list-my-instance' ,
'cp-admin-consent-to-contact' ,
'cp-admin-remove-donate-button' ,
'cp-admin-instance-purpose' ,
] ,
} ;
var create = { } ;
var keyToCamlCase = function ( key ) {
return key . replace ( /-([a-z])/g , function ( g ) { return g [ 1 ] . toUpperCase ( ) ; } ) ;
} ;
var makeBlock = function ( key , addButton ) { // Title, Hint, maybeButton
// Convert to camlCase for translation keys
var safeKey = keyToCamlCase ( key ) ;
var $div = $ ( '<div>' , { 'class' : 'cp-admin-' + key + ' cp-sidebarlayout-element' } ) ;
$ ( '<label>' ) . text ( Messages [ 'admin_' + safeKey + 'Title' ] || key ) . appendTo ( $div ) ;
$ ( '<span>' , { 'class' : 'cp-sidebarlayout-description' } )
. text ( Messages [ 'admin_' + safeKey + 'Hint' ] || 'Coming soon...' ) . appendTo ( $div ) ;
if ( addButton ) {
$ ( '<button>' , {
'class' : 'btn btn-primary'
} ) . text ( Messages [ 'admin_' + safeKey + 'Button' ] || safeKey ) . appendTo ( $div ) ;
}
return $div ;
} ;
create [ 'update-limit' ] = function ( ) {
var key = 'update-limit' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_updateLimitHint, .admin_updateLimitTitle, .admin_updateLimitButton
$div . find ( 'button' ) . click ( function ( ) {
sFrameChan . query ( 'Q_UPDATE_LIMIT' , null , function ( e , res ) {
if ( e || ( res && res . error ) ) { return void console . error ( e || res . error ) ; }
UI . alert ( Messages . admin _updateLimitDone || 'done' ) ;
} ) ;
} ) ;
return $div ;
} ;
create [ 'flush-cache' ] = function ( ) {
var key = 'flush-cache' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_flushCacheHint, .admin_flushCacheTitle, .admin_flushCacheButton
var called = false ;
$div . find ( 'button' ) . click ( function ( ) {
if ( called ) { return ; }
called = true ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'FLUSH_CACHE' ,
} , function ( e , data ) {
called = false ;
UI . alert ( data ? Messages . admin _flushCacheDone || 'done' : 'error' + e ) ;
} ) ;
} ) ;
return $div ;
} ;
var archiveForm = function ( archive , $div , $button ) {
var label = h ( 'label' , { for : 'cp-admin-archive' } , Messages . admin _archiveInput ) ;
var input = h ( 'input#cp-admin-archive' , {
type : 'text'
} ) ;
var label2 = h ( 'label.cp-admin-pw' , {
for : 'cp-admin-archive-pw'
} , Messages . admin _archiveInput2 ) ;
var input2 = UI . passwordInput ( {
id : 'cp-admin-archive-pw' ,
placeholder : Messages . login _password
} ) ;
var input3 = h ( 'input' , {
id : 'cp-admin-archive-reason' ,
} ) ;
var label3 = h ( 'label' , {
for : 'cp-admin-archive-reason' ,
} , Messages . admin _archiveNote ) ;
var $pw = $ ( input2 ) ;
$pw . addClass ( 'cp-admin-pw' ) ;
var $pwInput = $pw . find ( 'input' ) ;
$button . before ( h ( 'div.cp-admin-setlimit-form' , [
label ,
input ,
label2 ,
input2 ,
label3 ,
input3 ,
] ) ) ;
$div . addClass ( 'cp-admin-nopassword' ) ;
var parsed ;
var $input = $ ( input ) . on ( 'keypress change paste' , function ( ) {
setTimeout ( function ( ) {
$input . removeClass ( 'cp-admin-inval' ) ;
var val = $input . val ( ) . trim ( ) ;
if ( ! val ) {
$div . toggleClass ( 'cp-admin-nopassword' , true ) ;
return ;
}
parsed = Hash . isValidHref ( val ) ;
$pwInput . val ( '' ) ;
if ( ! parsed || ! parsed . hashData ) {
$div . toggleClass ( 'cp-admin-nopassword' , true ) ;
return void $input . addClass ( 'cp-admin-inval' ) ;
}
var pw = parsed . hashData . version !== 3 && parsed . hashData . password ;
$div . toggleClass ( 'cp-admin-nopassword' , ! pw ) ;
} ) ;
} ) ;
$pw . on ( 'keypress change' , function ( ) {
setTimeout ( function ( ) {
$pw . toggleClass ( 'cp-admin-inval' , ! $pwInput . val ( ) ) ;
} ) ;
} ) ;
var clicked = false ;
$button . click ( function ( ) {
if ( ! parsed || ! parsed . hashData ) {
UI . warn ( Messages . admin _archiveInval ) ;
return ;
}
var pw = parsed . hashData . password ? $pwInput . val ( ) : undefined ;
var channel ;
if ( parsed . hashData . version === 3 ) {
channel = parsed . hashData . channel ;
} else {
var secret = Hash . getSecrets ( parsed . type , parsed . hash , pw ) ;
channel = secret && secret . channel ;
}
if ( ! channel ) {
UI . warn ( Messages . admin _archiveInval ) ;
return ;
}
if ( clicked ) { return ; }
clicked = true ;
nThen ( function ( waitFor ) {
if ( ! archive ) { return ; }
common . getFileSize ( channel , waitFor ( function ( err , size ) {
if ( ! err && size === 0 ) {
clicked = false ;
waitFor . abort ( ) ;
return void UI . warn ( Messages . admin _archiveInval ) ;
}
} ) , true ) ;
} ) . nThen ( function ( ) {
var $reason = $ ( input3 ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : archive ? 'ARCHIVE_DOCUMENT' : 'RESTORE_ARCHIVED_DOCUMENT' ,
data : {
id : channel ,
reason : $reason . val ( ) ,
} ,
} , function ( err , obj ) {
var e = err || ( obj && obj . error ) ;
clicked = false ;
if ( e ) {
UI . warn ( Messages . error ) ;
console . error ( e ) ;
return ;
}
UI . log ( archive ? Messages . archivedFromServer : Messages . restoredFromServer ) ;
$input . val ( '' ) ;
$pwInput . val ( '' ) ;
// disabled because it's actually pretty annoying to re-enter this each time if you are archiving many files
//$reason.val('');
} ) ;
} ) ;
} ) ;
} ;
create [ 'archive' ] = function ( ) {
var key = 'archive' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_archiveHint, .admin_archiveTitle, .admin_archiveButton
var $button = $div . find ( 'button' ) ;
archiveForm ( true , $div , $button ) ;
return $div ;
} ;
create [ 'unarchive' ] = function ( ) {
var key = 'unarchive' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_unarchiveHint, .admin_unarchiveTitle, .admin_unarchiveButton
var $button = $div . find ( 'button' ) ;
archiveForm ( false , $div , $button ) ;
return $div ;
} ;
create [ 'registration' ] = function ( ) {
var key = 'registration' ;
var $div = makeBlock ( key ) ; // Msg.admin_registrationHint, .admin_registrationTitle, .admin_registrationButton
var state = APP . instanceStatus . restrictRegistration ;
var $cbox = $ ( UI . createCheckbox ( 'cp-settings-' + key ,
Messages . admin _registrationTitle ,
state , { label : { class : 'noTitle' } } ) ) ;
var spinner = UI . makeSpinner ( $cbox ) ;
var $checkbox = $cbox . find ( 'input' ) . on ( 'change' , function ( ) {
spinner . spin ( ) ;
var val = $checkbox . is ( ':checked' ) || false ;
$checkbox . attr ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'RESTRICT_REGISTRATION' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
spinner . done ( ) ;
state = APP . instanceStatus . restrictRegistration ;
$checkbox [ 0 ] . checked = state ;
$checkbox . removeAttr ( 'disabled' ) ;
} ) ;
} ) ;
} ) ;
$cbox . appendTo ( $div ) ;
return $div ;
} ;
var makeAdminCheckbox = function ( data ) {
return function ( ) {
var state = data . getState ( ) ;
var key = data . key ;
var $div = makeBlock ( key ) ;
var labelKey = 'admin_' + keyToCamlCase ( key ) + 'Label' ;
var titleKey = 'admin_' + keyToCamlCase ( key ) + 'Title' ;
var $cbox = $ ( UI . createCheckbox ( 'cp-admin-' + key ,
Messages [ labelKey ] || Messages [ titleKey ] ,
state , { label : { class : 'noTitle' } } ) ) ;
var spinner = UI . makeSpinner ( $cbox ) ;
var $checkbox = $cbox . find ( 'input' ) . on ( 'change' , function ( ) {
spinner . spin ( ) ;
var val = $checkbox . is ( ':checked' ) || false ;
$checkbox . attr ( 'disabled' , 'disabled' ) ;
data . query ( val , function ( state ) {
spinner . done ( ) ;
$checkbox [ 0 ] . checked = state ;
$checkbox . removeAttr ( 'disabled' ) ;
} ) ;
} ) ;
$cbox . appendTo ( $div ) ;
return $div ;
} ;
} ;
// Msg.admin_registrationHint, .admin_registrationTitle, .admin_registrationButton
create [ 'registration' ] = makeAdminCheckbox ( {
key : 'registration' ,
getState : function ( ) {
return APP . instanceStatus . restrictRegistration ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'RESTRICT_REGISTRATION' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . restrictRegistration ) ;
} ) ;
} ) ;
} ,
} ) ;
// XXX remove emailButton
create [ 'email' ] = function ( ) {
var key = 'email' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_emailHint, Msg.admin_emailTitle
var $button = $div . find ( 'button' ) . text ( Messages . settings _save ) ;
var input = h ( 'input' , {
type : 'email' ,
value : ApiConfig . adminEmail || ''
} ) ;
var $input = $ ( input ) ;
var innerDiv = h ( 'div.cp-admin-setlimit-form' , input ) ;
var spinner = UI . makeSpinner ( $ ( innerDiv ) ) ;
$button . click ( function ( ) {
if ( ! $input . val ( ) ) { return ; }
spinner . spin ( ) ;
$button . attr ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_ADMIN_EMAIL' , [ $input . val ( ) ] ]
} , function ( e , response ) {
$button . removeAttr ( 'disabled' ) ;
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
$input . val ( '' ) ;
console . error ( e , response ) ;
spinner . hide ( ) ;
return ;
}
spinner . done ( ) ;
UI . log ( Messages . saved ) ;
} ) ;
} ) ;
$button . before ( innerDiv ) ;
return $div ;
} ;
create [ 'jurisdiction' ] = function ( ) {
var key = 'jurisdiction' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_jurisdictionHint, Msg.admin_jurisdictionTitle, Msg.admin_jurisdictionButton
var $button = $div . find ( 'button' ) . addClass ( 'cp-listing-action' ) . text ( Messages . settings _save ) ;
var input = h ( 'input.cp-listing-info' , {
type : 'text' ,
value : APP . instanceStatus . instanceJurisdiction || '' ,
placeholder : Messages . admin _jurisdictionPlaceholder || Messages . owner _unknownUser , // XXX
} ) ;
var $input = $ ( input ) ;
var innerDiv = h ( 'div.cp-admin-setjurisdiction-form' , input ) ;
var spinner = UI . makeSpinner ( $ ( innerDiv ) ) ;
$button . click ( function ( ) {
spinner . spin ( ) ;
$button . attr ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_INSTANCE_JURISDICTION' , [ $input . val ( ) . trim ( ) ] ]
} , function ( e , response ) {
$button . removeAttr ( 'disabled' ) ;
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
$input . val ( '' ) ;
console . error ( e , response ) ;
spinner . hide ( ) ;
return ;
}
spinner . done ( ) ;
UI . log ( Messages . _getKey ( 'ui_saved' , [ Messages . admin _jurisdictionTitle ] ) ) ;
} ) ;
} ) ;
$button . before ( innerDiv ) ;
return $div ;
} ;
Messages . admin _infoNotice1 = "The following fields describe your instance. Data entered will only be included in your server's telemetry if you opt in to inclusion in the list of public CryptPad instances." ; // XXX
Messages . admin _infoNotice2 = "See the 'Network' tab for more details." ; // XXX
create [ 'instance-info-notice' ] = function ( ) {
return $ ( h ( 'div.cp-admin-instance-info-notice.cp-sidebarlayout-element' ,
h ( 'div.alert.alert-info.cp-admin-bigger-alert' , [
Messages . admin _infoNotice1 ,
' ' ,
Messages . admin _infoNotice2 ,
] )
) ) ;
} ;
create [ 'name' ] = function ( ) {
var key = 'name' ;
var $div = makeBlock ( key , true ) ;
// Msg.admin_nameHint, Msg.admin_nameTitle, Msg.admin_nameButton
var $button = $div . find ( 'button' ) . addClass ( 'cp-listing-action' ) . text ( Messages . settings _save ) ;
var input = h ( 'input.cp-listing-info' , {
type : 'text' ,
value : APP . instanceStatus . instanceName || ApiConfig . httpUnsafeOrigin || '' ,
placeholder : ApiConfig . httpUnsafeOrigin ,
style : 'margin-bottom: 5px;' ,
} ) ;
var $input = $ ( input ) ;
var innerDiv = h ( 'div.cp-admin-setname-form' , input ) ;
var spinner = UI . makeSpinner ( $ ( innerDiv ) ) ;
$button . click ( function ( ) {
spinner . spin ( ) ;
$button . attr ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_INSTANCE_NAME' , [ $input . val ( ) . trim ( ) ] ]
} , function ( e , response ) {
$button . removeAttr ( 'disabled' ) ;
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
$input . val ( '' ) ;
console . error ( e , response ) ;
spinner . hide ( ) ;
return ;
}
spinner . done ( ) ;
UI . log ( Messages . _getKey ( 'ui_saved' , [ Messages . admin _nameTitle ] ) ) ;
} ) ;
} ) ;
$button . before ( innerDiv ) ;
return $div ;
} ;
create [ 'description' ] = function ( ) {
var key = 'description' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_descriptionHint
var textarea = h ( 'textarea.cp-admin-description-text.cp-listing-info' , {
placeholder : Messages . home _host || '' ,
} , APP . instanceStatus . instanceDescription || '' ) ;
var $button = $div . find ( 'button' ) . text ( Messages . settings _save ) ;
$button . addClass ( 'cp-listing-action' ) ;
var innerDiv = h ( 'div.cp-admin-setdescription-form' , [
textarea ,
] ) ;
$button . before ( innerDiv ) ;
var $input = $ ( textarea ) ;
var spinner = UI . makeSpinner ( $ ( innerDiv ) ) ;
$button . click ( function ( ) {
spinner . spin ( ) ;
$button . attr ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_INSTANCE_DESCRIPTION' , [ $input . val ( ) . trim ( ) ] ]
} , function ( e , response ) {
$button . removeAttr ( 'disabled' ) ;
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
$input . val ( '' ) ;
console . error ( e , response ) ;
spinner . hide ( ) ;
return ;
}
spinner . done ( ) ;
UI . log ( Messages . _getKey ( 'ui_saved' , [ Messages . admin _descriptionTitle ] ) ) ;
} ) ;
} ) ;
return $div ;
} ;
var getPrettySize = UIElements . prettySize ;
create [ 'defaultlimit' ] = function ( ) {
var key = 'defaultlimit' ;
var $div = makeBlock ( key ) ; // Msg.admin_defaultlimitHint, .admin_defaultlimitTitle
var _limit = APP . instanceStatus . defaultStorageLimit ;
var _limitMB = Util . bytesToMegabytes ( _limit ) ;
var limit = getPrettySize ( _limit ) ;
var newLimit = h ( 'input' , { type : 'number' , min : 0 , value : _limitMB } ) ;
var set = h ( 'button.btn.btn-primary' , Messages . admin _setlimitButton ) ;
$div . append ( h ( 'div' , [
h ( 'span.cp-admin-defaultlimit-value' , Messages . _getKey ( 'admin_limit' , [ limit ] ) ) ,
h ( 'div.cp-admin-setlimit-form' , [
h ( 'label' , Messages . admin _defaultLimitMB ) ,
newLimit ,
h ( 'nav' , [ set ] )
] )
] ) ) ;
UI . confirmButton ( set , {
classes : 'btn-primary' ,
multiple : true ,
validate : function ( ) {
var l = parseInt ( $ ( newLimit ) . val ( ) ) ;
if ( isNaN ( l ) ) { return false ; }
return true ;
}
} , function ( ) {
var lMB = parseInt ( $ ( newLimit ) . val ( ) ) ; // Megabytes
var l = lMB * 1024 * 1024 ; // Bytes
var data = [ l ] ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'UPDATE_DEFAULT_STORAGE' , data ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
return void console . error ( e , response ) ;
}
var limit = getPrettySize ( l ) ;
$div . find ( '.cp-admin-defaultlimit-value' ) . text ( Messages . _getKey ( 'admin_limit' , [ limit ] ) ) ;
} ) ;
} ) ;
return $div ;
} ;
create [ 'getlimits' ] = function ( ) {
var key = 'getlimits' ;
var $div = makeBlock ( key ) ; // Msg.admin_getlimitsHint, .admin_getlimitsTitle
APP . refreshLimits = function ( ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'GET_LIMITS' ,
} , function ( e , data ) {
if ( e ) { return ; }
if ( ! Array . isArray ( data ) || ! data [ 0 ] ) { return ; }
$div . find ( '.cp-admin-all-limits' ) . remove ( ) ;
var obj = data [ 0 ] ;
if ( obj && ( obj . message || obj . location ) ) {
delete obj . message ;
delete obj . location ;
}
var list = Object . keys ( obj ) . sort ( function ( a , b ) {
return obj [ a ] . limit > obj [ b ] . limit ;
} ) ;
var compact = list . length > 10 ;
var content = list . map ( function ( key ) {
var user = obj [ key ] ;
var limit = getPrettySize ( user . limit ) ;
var title = Messages . _getKey ( 'admin_limit' , [ limit ] ) + ', ' +
Messages . _getKey ( 'admin_limitPlan' , [ user . plan ] ) + ', ' +
Messages . _getKey ( 'admin_limitNote' , [ user . note ] ) ;
var keyEl = h ( 'code.cp-limit-key' , key ) ;
$ ( keyEl ) . click ( function ( ) {
$ ( '.cp-admin-setlimit-form' ) . find ( '.cp-setlimit-key' ) . val ( key ) ;
$ ( '.cp-admin-setlimit-form' ) . find ( '.cp-setlimit-quota' ) . val ( Math . floor ( user . limit / 1024 / 1024 ) ) ;
$ ( '.cp-admin-setlimit-form' ) . find ( '.cp-setlimit-note' ) . val ( user . note ) ;
} ) ;
if ( compact ) {
return h ( 'tr.cp-admin-limit' , {
title : title
} , [
h ( 'td' , keyEl ) ,
h ( 'td.limit' , Messages . _getKey ( 'admin_limit' , [ limit ] ) ) ,
h ( 'td.plan' , Messages . _getKey ( 'admin_limitPlan' , [ user . plan ] ) ) ,
h ( 'td.note' , Messages . _getKey ( 'admin_limitNote' , [ user . note ] ) )
] ) ;
}
return h ( 'li.cp-admin-limit' , [
keyEl ,
h ( 'ul.cp-limit-data' , [
h ( 'li.limit' , Messages . _getKey ( 'admin_limit' , [ limit ] ) ) ,
h ( 'li.plan' , Messages . _getKey ( 'admin_limitPlan' , [ user . plan ] ) ) ,
h ( 'li.note' , Messages . _getKey ( 'admin_limitNote' , [ user . note ] ) )
] )
] ) ;
} ) ;
if ( compact ) { return $div . append ( h ( 'table.cp-admin-all-limits' , content ) ) ; }
$div . append ( h ( 'ul.cp-admin-all-limits' , content ) ) ;
} ) ;
} ;
APP . refreshLimits ( ) ;
return $div ;
} ;
create [ 'setlimit' ] = function ( ) {
var key = 'setlimit' ;
var $div = makeBlock ( key ) ; // Msg.admin_setlimitHint, .admin_setlimitTitle
var user = h ( 'input.cp-setlimit-key' ) ;
var $key = $ ( user ) ;
var limit = h ( 'input.cp-setlimit-quota' , { type : 'number' , min : 0 , value : 0 } ) ;
var note = h ( 'input.cp-setlimit-note' ) ;
var remove = h ( 'button.btn.btn-danger' , Messages . fc _remove ) ;
var set = h ( 'button.btn.btn-primary' , Messages . admin _setlimitButton ) ;
var form = h ( 'div.cp-admin-setlimit-form' , [
h ( 'label' , Messages . admin _limitUser ) ,
user ,
h ( 'label' , Messages . admin _limitMB ) ,
limit ,
h ( 'label' , Messages . admin _limitSetNote ) ,
note ,
h ( 'nav' , [ set , remove ] )
] ) ;
var $note = $ ( note ) ;
var getValues = function ( ) {
var key = $key . val ( ) ;
var _limit = parseInt ( $ ( limit ) . val ( ) ) ;
if ( key . length !== 44 ) {
try {
var u = Keys . parseUser ( key ) ;
if ( ! u . domain || ! u . user || ! u . pubkey ) {
return void UI . warn ( Messages . admin _invalKey ) ;
}
} catch ( e ) {
return void UI . warn ( Messages . admin _invalKey ) ;
}
}
if ( isNaN ( _limit ) || _limit < 0 ) {
return void UI . warn ( Messages . admin _invalLimit ) ;
}
var _note = ( $note . val ( ) || "" ) . trim ( ) ;
return {
key : key ,
data : {
limit : _limit * 1024 * 1024 ,
note : _note ,
plan : 'custom'
}
} ;
} ;
UI . confirmButton ( remove , {
classes : 'btn-danger' ,
multiple : true ,
validate : function ( ) {
var obj = getValues ( ) ;
if ( ! obj || ! obj . key ) { return false ; }
return true ;
}
} , function ( ) {
var obj = getValues ( ) ;
var data = [ obj . key ] ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'RM_QUOTA' , data ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
return ;
}
APP . refreshLimits ( ) ;
$key . val ( '' ) ;
} ) ;
} ) ;
$ ( set ) . click ( function ( ) {
var obj = getValues ( ) ;
if ( ! obj || ! obj . key ) { return ; }
var data = [ obj . key , obj . data ] ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_QUOTA' , data ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
return ;
}
APP . refreshLimits ( ) ;
$key . val ( '' ) ;
$note . val ( '' ) ;
} ) ;
} ) ;
$div . append ( form ) ;
return $div ;
} ;
create [ 'getquota' ] = function ( ) {
var key = 'getquota' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_getquotaHint, .admin_getquotaTitle, .admin_getquotaButton
var input = h ( 'input#cp-admin-getquota' , {
type : 'text'
} ) ;
var $input = $ ( input ) ;
var $button = $div . find ( 'button' ) ;
$button . before ( h ( 'div.cp-admin-setlimit-form' , [
input ,
] ) ) ;
$button . click ( function ( ) {
var val = $input . val ( ) ;
if ( ! val || ! val . trim ( ) ) { return ; }
var key = Keys . canonicalize ( val ) ;
if ( ! key ) { return ; }
$input . val ( '' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'GET_USER_TOTAL_SIZE' ,
data : key
} , function ( e , obj ) {
if ( e || ( obj && obj . error ) ) {
console . error ( e || obj . error ) ;
return void UI . warn ( Messages . error ) ;
}
var size = Array . isArray ( obj ) && obj [ 0 ] ;
if ( typeof ( size ) !== "number" ) { return ; }
UI . alert ( getPrettySize ( size ) ) ;
} ) ;
} ) ;
return $div ;
} ;
var onRefreshStats = Util . mkEvent ( ) ;
create [ 'refresh-stats' ] = function ( ) {
var key = 'refresh-stats' ;
var $div = $ ( '<div>' , { 'class' : 'cp-admin-' + key + ' cp-sidebarlayout-element' } ) ;
var $btn = $ ( h ( 'button.btn.btn-primary' , Messages . oo _refresh ) ) ;
$btn . click ( function ( ) {
onRefreshStats . fire ( ) ;
} ) ;
$div . append ( $btn ) ;
return $div ;
} ;
Messages . admin _uptimeTitle = 'Launch time' ;
Messages . admin _uptimeHint = 'Date and time at which the server was launched' ;
create [ 'uptime' ] = function ( ) {
var key = 'uptime' ;
var $div = makeBlock ( key ) ; // Msg.admin_activeSessionsHint, .admin_activeSessionsTitle
var pre = h ( 'pre' ) ;
var set = function ( ) {
var uptime = APP . instanceStatus . launchTime ;
if ( typeof ( uptime ) !== 'number' ) { return ; }
pre . innerText = new Date ( uptime ) ;
} ;
set ( ) ;
$div . append ( pre ) ;
onRefreshStats . reg ( function ( ) {
set ( ) ;
} ) ;
return $div ;
} ;
create [ 'active-sessions' ] = function ( ) {
var key = 'active-sessions' ;
var $div = makeBlock ( key ) ; // Msg.admin_activeSessionsHint, .admin_activeSessionsTitle
var onRefresh = function ( ) {
$div . find ( 'pre' ) . remove ( ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ACTIVE_SESSIONS' ,
} , function ( e , data ) {
var total = data [ 0 ] ;
var ips = data [ 1 ] ;
$div . find ( 'pre' ) . remove ( ) ;
$div . append ( h ( 'pre' , total + ' (' + ips + ')' ) ) ;
} ) ;
} ;
onRefresh ( ) ;
onRefreshStats . reg ( onRefresh ) ;
return $div ;
} ;
create [ 'active-pads' ] = function ( ) {
var key = 'active-pads' ;
var $div = makeBlock ( key ) ; // Msg.admin_activePadsHint, .admin_activePadsTitle
var onRefresh = function ( ) {
$div . find ( 'pre' ) . remove ( ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ACTIVE_PADS' ,
} , function ( e , data ) {
console . log ( e , data ) ;
$div . find ( 'pre' ) . remove ( ) ;
$div . append ( h ( 'pre' , String ( data ) ) ) ;
} ) ;
} ;
onRefresh ( ) ;
onRefreshStats . reg ( onRefresh ) ;
return $div ;
} ;
create [ 'open-files' ] = function ( ) {
var key = 'open-files' ;
var $div = makeBlock ( key ) ; // Msg.admin_openFilesHint, .admin_openFilesTitle
var onRefresh = function ( ) {
$div . find ( 'pre' ) . remove ( ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'GET_FILE_DESCRIPTOR_COUNT' ,
} , function ( e , data ) {
if ( e || ( data && data . error ) ) {
console . error ( e , data ) ;
$div . append ( h ( 'pre' , {
style : 'text-decoration: underline' ,
} , String ( e || data . error ) ) ) ;
return ;
}
console . log ( e , data ) ;
$div . find ( 'pre' ) . remove ( ) ;
$div . append ( h ( 'pre' , String ( data ) ) ) ;
} ) ;
} ;
onRefresh ( ) ;
onRefreshStats . reg ( onRefresh ) ;
return $div ;
} ;
create [ 'registered' ] = function ( ) {
var key = 'registered' ;
var $div = makeBlock ( key ) ; // Msg.admin_registeredHint, .admin_registeredTitle
var onRefresh = function ( ) {
$div . find ( 'pre' ) . remove ( ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'REGISTERED_USERS' ,
} , function ( e , data ) {
console . log ( e , data ) ;
$div . find ( 'pre' ) . remove ( ) ;
$div . append ( h ( 'pre' , String ( data ) ) ) ;
} ) ;
} ;
onRefresh ( ) ;
onRefreshStats . reg ( onRefresh ) ;
return $div ;
} ;
create [ 'disk-usage' ] = function ( ) {
var key = 'disk-usage' ;
var $div = makeBlock ( key , true ) ; // Msg.admin_diskUsageHint, .admin_diskUsageTitle, .admin_diskUsageButton
var called = false ;
$div . find ( 'button' ) . click ( function ( ) {
$div . find ( 'button' ) . hide ( ) ;
if ( called ) { return ; }
called = true ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'DISK_USAGE' ,
} , function ( e , data ) {
console . log ( e , data ) ;
if ( e ) { return void console . error ( e ) ; }
var obj = data [ 0 ] ;
Object . keys ( obj ) . forEach ( function ( key ) {
var val = obj [ key ] ;
var unit = Util . magnitudeOfBytes ( val ) ;
if ( unit === 'GB' ) {
obj [ key ] = Util . bytesToGigabytes ( val ) + ' GB' ;
} else if ( unit === 'MB' ) {
obj [ key ] = Util . bytesToMegabytes ( val ) + ' MB' ;
} else {
obj [ key ] = Util . bytesToKilobytes ( val ) + ' KB' ;
}
} ) ;
$div . append ( h ( 'ul' , Object . keys ( obj ) . map ( function ( k ) {
return h ( 'li' , [
h ( 'strong' , k === 'total' ? k : '/' + k ) ,
' : ' ,
obj [ k ]
] ) ;
} ) ) ) ;
} ) ;
} ) ;
return $div ;
} ;
var supportKey = ApiConfig . supportMailbox ;
var checkAdminKey = function ( priv ) {
if ( ! supportKey ) { return ; }
return Hash . checkBoxKeyPair ( priv , supportKey ) ;
} ;
create [ 'support-list' ] = function ( ) {
if ( ! supportKey || ! APP . privateKey || ! checkAdminKey ( APP . privateKey ) ) { return ; }
var $container = makeBlock ( 'support-list' ) ; // Msg.admin_supportListHint, .admin_supportListTitle
var $div = $ ( h ( 'div.cp-support-container' ) ) . appendTo ( $container ) ;
var catContainer = h ( 'div.cp-dropdown-container' ) ;
var col1 = h ( 'div.cp-support-column' , h ( 'h1' , [
h ( 'span' , Messages . admin _support _premium ) ,
h ( 'span.cp-support-count' ) ,
h ( 'button.btn.cp-support-column-button' , Messages . admin _support _collapse )
] ) ) ;
var col2 = h ( 'div.cp-support-column' , h ( 'h1' , [
h ( 'span' , Messages . admin _support _normal ) ,
h ( 'span.cp-support-count' ) ,
h ( 'button.btn.cp-support-column-button' , Messages . admin _support _collapse )
] ) ) ;
var col3 = h ( 'div.cp-support-column' , h ( 'h1' , [
h ( 'span' , Messages . admin _support _answered ) ,
h ( 'span.cp-support-count' ) ,
h ( 'button.btn.cp-support-column-button' , Messages . admin _support _collapse )
] ) ) ;
var col4 = h ( 'div.cp-support-column' , h ( 'h1' , [
h ( 'span' , Messages . admin _support _closed ) ,
h ( 'span.cp-support-count' ) ,
h ( 'button.btn.cp-support-column-button' , Messages . admin _support _collapse )
] ) ) ;
var $col1 = $ ( col1 ) , $col2 = $ ( col2 ) , $col3 = $ ( col3 ) , $col4 = $ ( col4 ) ;
$div . append ( [
//catContainer
col1 ,
col2 ,
col3 ,
col4
] ) ;
$div . find ( '.cp-support-column-button' ) . click ( function ( ) {
var $col = $ ( this ) . closest ( '.cp-support-column' ) ;
$col . toggleClass ( 'cp-support-column-collapsed' ) ;
if ( $col . hasClass ( 'cp-support-column-collapsed' ) ) {
$ ( this ) . text ( Messages . admin _support _open ) ;
$ ( this ) . toggleClass ( 'btn-primary' ) ;
} else {
$ ( this ) . text ( Messages . admin _support _collapse ) ;
$ ( this ) . toggleClass ( 'btn-primary' ) ;
}
} ) ;
var category = 'all' ;
var $drop = APP . support . makeCategoryDropdown ( catContainer , function ( key ) {
category = key ;
if ( key === 'all' ) {
$div . find ( '.cp-support-list-ticket' ) . show ( ) ;
return ;
}
$div . find ( '.cp-support-list-ticket' ) . hide ( ) ;
$div . find ( '.cp-support-list-ticket[data-cat="' + key + '"]' ) . show ( ) ;
} , true ) ;
$drop . setValue ( 'all' ) ;
var metadataMgr = common . getMetadataMgr ( ) ;
var privateData = metadataMgr . getPrivateData ( ) ;
var cat = privateData . category || '' ;
var linkedId = cat . indexOf ( '-' ) !== - 1 && cat . slice ( 8 ) ;
var hashesById = { } ;
var getTicketData = function ( id ) {
var t = hashesById [ id ] ;
if ( ! Array . isArray ( t ) || ! t . length ) { return ; }
var ed = Util . find ( t [ 0 ] , [ 'content' , 'msg' , 'content' , 'sender' , 'edPublic' ] ) ;
// If one of their ticket was sent as a premium user, mark them as premium
var premium = t . some ( function ( msg ) {
var _ed = Util . find ( msg , [ 'content' , 'msg' , 'content' , 'sender' , 'edPublic' ] ) ;
if ( ed !== _ed ) { return ; }
return Util . find ( msg , [ 'content' , 'msg' , 'content' , 'sender' , 'plan' ] ) ||
Util . find ( msg , [ 'content' , 'msg' , 'content' , 'sender' , 'quota' , 'plan' ] ) ;
} ) ;
var lastMsg = t [ t . length - 1 ] ;
var lastMsgEd = Util . find ( lastMsg , [ 'content' , 'msg' , 'content' , 'sender' , 'edPublic' ] ) ;
return {
lastMsg : lastMsg ,
time : Util . find ( lastMsg , [ 'content' , 'msg' , 'content' , 'time' ] ) ,
lastMsgEd : lastMsgEd ,
lastAdmin : lastMsgEd !== ed && ApiConfig . adminKeys . indexOf ( lastMsgEd ) !== - 1 ,
premium : premium ,
authorEd : ed ,
closed : Util . find ( lastMsg , [ 'content' , 'msg' , 'type' ] ) === 'CLOSE'
} ;
} ;
var addClickHandler = function ( $ticket ) {
$ticket . on ( 'click' , function ( ) {
$ticket . toggleClass ( 'cp-support-open' , true ) ;
$ticket . off ( 'click' ) ;
} ) ;
} ;
var makeOpenButton = function ( $ticket ) {
var button = h ( 'button.btn.btn-primary.cp-support-expand' , Messages . admin _support _open ) ;
var collapse = h ( 'button.btn.cp-support-collapse' , Messages . admin _support _collapse ) ;
$ ( button ) . click ( function ( ) {
$ticket . toggleClass ( 'cp-support-open' , true ) ;
} ) ;
addClickHandler ( $ticket ) ;
$ ( collapse ) . click ( function ( e ) {
$ticket . toggleClass ( 'cp-support-open' , false ) ;
e . stopPropagation ( ) ;
setTimeout ( function ( ) {
addClickHandler ( $ticket ) ;
} ) ;
} ) ;
$ticket . find ( '.cp-support-title-buttons' ) . prepend ( [ button , collapse ] ) ;
$ticket . append ( h ( 'div.cp-support-collapsed' ) ) ;
} ;
var updateTicketDetails = function ( $ticket , isPremium ) {
var $first = $ticket . find ( '.cp-support-message-from' ) . first ( ) ;
var user = $first . find ( 'span' ) . first ( ) . html ( ) ;
var time = $first . find ( '.cp-support-message-time' ) . text ( ) ;
var last = $ticket . find ( '.cp-support-message-from' ) . last ( ) . find ( '.cp-support-message-time' ) . text ( ) ;
var $c = $ticket . find ( '.cp-support-collapsed' ) ;
var txtClass = isPremium ? ".cp-support-ispremium" : "" ;
$c . html ( '' ) . append ( [
UI . setHTML ( h ( 'span' + txtClass ) , user ) ,
h ( 'span' , [
h ( 'b' , Messages . admin _support _first ) ,
h ( 'span' , time )
] ) ,
h ( 'span' , [
h ( 'b' , Messages . admin _support _last ) ,
h ( 'span' , last )
] )
] ) ;
} ;
var sort = function ( id1 , id2 ) {
var t1 = getTicketData ( id1 ) ;
var t2 = getTicketData ( id2 ) ;
if ( ! t1 ) { return 1 ; }
if ( ! t2 ) { return - 1 ; }
/ *
// If one is answered and not the other, put the unanswered first
if ( t1 . lastAdmin && ! t2 . lastAdmin ) { return 1 ; }
if ( ! t1 . lastAdmin && t2 . lastAdmin ) { return - 1 ; }
* /
// Otherwise, sort them by time
return t1 . time - t2 . time ;
} ;
var _reorder = function ( ) {
var orderAnswered = [ ] ;
var orderPremium = [ ] ;
var orderNormal = [ ] ;
var orderClosed = [ ] ;
Object . keys ( hashesById ) . forEach ( function ( id ) {
var d = getTicketData ( id ) ;
if ( ! d ) { return ; }
if ( d . closed ) {
return void orderClosed . push ( id ) ;
}
if ( d . lastAdmin /* && !d.closed */ ) {
return void orderAnswered . push ( id ) ;
}
if ( d . premium /* && !d.lastAdmin && !d.closed */ ) {
return void orderPremium . push ( id ) ;
}
orderNormal . push ( id ) ;
//if (!d.premium && !d.lastAdmin && !d.closed) { return void orderNormal.push(id); }
} ) ;
var cols = [ $col1 , $col2 , $col3 , $col4 ] ;
[ orderPremium , orderNormal , orderAnswered , orderClosed ] . forEach ( function ( list , j ) {
list . sort ( sort ) ;
list . forEach ( function ( id , i ) {
var $t = $div . find ( '[data-id="' + id + '"]' ) ;
var d = getTicketData ( id ) ;
$t . css ( 'order' , i ) . appendTo ( cols [ j ] ) ;
updateTicketDetails ( $t , d . premium ) ;
} ) ;
if ( ! list . length ) {
cols [ j ] . hide ( ) ;
} else {
cols [ j ] . show ( ) ;
cols [ j ] . find ( '.cp-support-count' ) . text ( list . length ) ;
}
} ) ;
} ;
var reorder = Util . throttle ( _reorder , 150 ) ;
var to = Util . throttle ( function ( ) {
var $ticket = $div . find ( '.cp-support-list-ticket[data-id="' + linkedId + '"]' ) ;
$ticket . addClass ( 'cp-support-open' ) ;
$ticket [ 0 ] . scrollIntoView ( ) ;
linkedId = undefined ;
} , 200 ) ;
// Register to the "support" mailbox
common . mailbox . subscribe ( [ 'supportadmin' ] , {
onMessage : function ( data ) {
/ *
Get ID of the ticket
If we already have a div for this ID
Push the message to the end of the ticket
If it ' s a new ticket ID
Make a new div for this ID
* /
var msg = data . content . msg ;
var hash = data . content . hash ;
var content = msg . content ;
var id = content . id ;
var $ticket = $div . find ( '.cp-support-list-ticket[data-id="' + id + '"]' ) ;
hashesById [ id ] = hashesById [ id ] || [ ] ;
if ( hashesById [ id ] . indexOf ( hash ) === - 1 ) {
hashesById [ id ] . push ( data ) ;
}
if ( msg . type === 'CLOSE' ) {
// A ticket has been closed by the admins...
if ( ! $ticket . length ) { return ; }
$ticket . addClass ( 'cp-support-list-closed' ) ;
$ticket . append ( APP . support . makeCloseMessage ( content , hash ) ) ;
reorder ( ) ;
return ;
}
if ( msg . type !== 'TICKET' ) { return ; }
$ticket . removeClass ( 'cp-support-list-closed' ) ;
if ( ! $ticket . length ) {
$ticket = APP . support . makeTicket ( $div , content , function ( hideButton ) {
// the ticket will still be displayed until the worker confirms its deletion
// so make it unclickable in the meantime
hideButton . setAttribute ( 'disabled' , true ) ;
var error = false ;
nThen ( function ( w ) {
hashesById [ id ] . forEach ( function ( d ) {
common . mailbox . dismiss ( d , w ( function ( err ) {
if ( err ) {
error = true ;
console . error ( err ) ;
}
} ) ) ;
} ) ;
} ) . nThen ( function ( ) {
if ( ! error ) {
$ticket . remove ( ) ;
delete hashesById [ id ] ;
reorder ( ) ;
return ;
}
// if deletion failed then reactivate the button and warn
hideButton . removeAttribute ( 'disabled' ) ;
// and show a generic error message
UI . alert ( Messages . error ) ;
} ) ;
} ) ;
makeOpenButton ( $ticket ) ;
if ( category !== 'all' && $ticket . attr ( 'data-cat' ) !== category ) {
$ticket . hide ( ) ;
}
}
$ticket . append ( APP . support . makeMessage ( content , hash ) ) ;
reorder ( ) ;
if ( linkedId ) { to ( ) ; }
}
} ) ;
return $container ;
} ;
create [ 'support-priv' ] = function ( ) {
if ( ! supportKey || ! APP . privateKey || ! checkAdminKey ( APP . privateKey ) ) { return ; }
var $div = makeBlock ( 'support-priv' , true ) ; // Msg.admin_supportPrivHint, .admin_supportPrivTitle, .admin_supportPrivButton
var $button = $div . find ( 'button' ) . click ( function ( ) {
$button . remove ( ) ;
var $selectable = $ ( UI . dialog . selectable ( APP . privateKey ) ) . css ( { 'max-width' : '28em' } ) ;
$div . append ( $selectable ) ;
} ) ;
return $div ;
} ;
create [ 'support-init' ] = function ( ) {
var $div = makeBlock ( 'support-init' ) ; // Msg.admin_supportInitHint, .admin_supportInitTitle
if ( ! supportKey ) {
( function ( ) {
$div . append ( h ( 'p' , Messages . admin _supportInitHelp ) ) ;
var button = h ( 'button.btn.btn-primary' , Messages . admin _supportInitGenerate ) ;
var $button = $ ( button ) . appendTo ( $div ) ;
$div . append ( $button ) ;
var spinner = UI . makeSpinner ( $div ) ;
$button . click ( function ( ) {
spinner . spin ( ) ;
$button . attr ( 'disabled' , 'disabled' ) ;
var keyPair = Nacl . box . keyPair ( ) ;
var pub = Nacl . util . encodeBase64 ( keyPair . publicKey ) ;
var priv = Nacl . util . encodeBase64 ( keyPair . secretKey ) ;
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan . query ( "Q_ADMIN_MAILBOX" , priv , function ( err , obj ) {
if ( err || ( obj && obj . error ) ) {
console . error ( err || obj . error ) ;
UI . warn ( Messages . error ) ;
spinner . hide ( ) ;
return ;
}
// Then send the decree
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_SUPPORT_MAILBOX' , [ pub ] ]
} , function ( e , response ) {
$button . removeAttr ( 'disabled' ) ;
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
spinner . hide ( ) ;
return ;
}
spinner . done ( ) ;
UI . log ( Messages . saved ) ;
supportKey = pub ;
APP . privateKey = priv ;
$ ( '.cp-admin-support-init' ) . hide ( ) ;
APP . $rightside . append ( create [ 'support-list' ] ( ) ) ;
APP . $rightside . append ( create [ 'support-priv' ] ( ) ) ;
} ) ;
} ) ;
} ) ;
} ) ( ) ;
return $div ;
}
if ( ! APP . privateKey || ! checkAdminKey ( APP . privateKey ) ) {
$div . append ( h ( 'p' , Messages . admin _supportInitPrivate ) ) ;
var error = h ( 'div.cp-admin-support-error' ) ;
var input = h ( 'input.cp-admin-add-private-key' ) ;
var button = h ( 'button.btn.btn-primary' , Messages . admin _supportAddKey ) ;
if ( APP . privateKey && ! checkAdminKey ( APP . privateKey ) ) {
$ ( error ) . text ( Messages . admin _supportAddError ) ;
}
$div . append ( h ( 'div' , [
error ,
input ,
button
] ) ) ;
$ ( button ) . click ( function ( ) {
var key = $ ( input ) . val ( ) ;
if ( ! checkAdminKey ( key ) ) {
$ ( input ) . val ( '' ) ;
return void $ ( error ) . text ( Messages . admin _supportAddError ) ;
}
sFrameChan . query ( "Q_ADMIN_MAILBOX" , key , function ( ) {
APP . privateKey = key ;
$ ( '.cp-admin-support-init' ) . hide ( ) ;
APP . $rightside . append ( create [ 'support-list' ] ( ) ) ;
APP . $rightside . append ( create [ 'support-priv' ] ( ) ) ;
} ) ;
} ) ;
return $div ;
}
return ;
} ;
var getApi = function ( cb ) {
return function ( ) {
require ( [ '/api/broadcast?' + ( + new Date ( ) ) ] , function ( Broadcast ) {
cb ( Broadcast ) ;
setTimeout ( function ( ) {
try {
var ctx = require . s . contexts . _ ;
var defined = ctx . defined ;
Object . keys ( defined ) . forEach ( function ( href ) {
if ( /^\/api\/broadcast\?[0-9]{13}/ . test ( href ) ) {
delete defined [ href ] ;
return ;
}
} ) ;
} catch ( e ) { }
} ) ;
} ) ;
} ;
} ;
// Update the lastBroadcastHash in /api/broadcast if we can do it.
// To do so, find the last "BROADCAST_CUSTOM" in the current history and use the previous
// message's hash.
// If the last BROADCAST_CUSTOM has been deleted by an admin, we can use the most recent
// message's hash.
var checkLastBroadcastHash = function ( ) {
var deleted = [ ] ;
require ( [ '/api/broadcast?' + ( + new Date ( ) ) ] , function ( BCast ) {
var hash = BCast . lastBroadcastHash || '1' ; // Truthy value if no lastKnownHash
common . mailbox . getNotificationsHistory ( 'broadcast' , null , hash , function ( e , msgs ) {
if ( e ) { return void console . error ( e ) ; }
// No history, nothing to change
if ( ! Array . isArray ( msgs ) ) { return ; }
if ( ! msgs . length ) { return ; }
var lastHash ;
var next = false ;
// Start from the most recent messages until you find a CUSTOM message and
// check if it has been deleted
msgs . reverse ( ) . some ( function ( data ) {
var c = data . content ;
// This is the hash we want to keep
if ( next ) {
if ( ! c || ! c . hash ) { return ; }
lastHash = c . hash ;
next = false ;
return true ;
}
// initialize with the most recent hash
if ( ! lastHash && c && c . hash ) { lastHash = c . hash ; }
var msg = c && c . msg ;
if ( ! msg ) { return ; }
// Remember all deleted messages
if ( msg . type === "BROADCAST_DELETE" ) {
deleted . push ( Util . find ( msg , [ 'content' , 'uid' ] ) ) ;
}
// Only check custom messages
if ( msg . type !== "BROADCAST_CUSTOM" ) { return ; }
// If the most recent CUSTOM message has been deleted, it means we don't
// need to keep any message and we can continue with lastHash as the most
// recent broadcast message.
if ( deleted . indexOf ( msg . uid ) !== - 1 ) { return true ; }
// We just found the oldest message we want to keep, move one iteration
// further into the loop to get the next message's hash.
// If this is the end of the loop, don't bump lastBroadcastHash at all.
next = true ;
} ) ;
// If we don't have to bump our lastBroadcastHash, abort
if ( next ) { return ; }
// Otherwise, bump to lastHash
console . warn ( 'Updating last broadcast hash to' , lastHash ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_LAST_BROADCAST_HASH' , [ lastHash ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
return ;
}
console . log ( 'lastBroadcastHash updated' ) ;
} ) ;
} ) ;
} ) ;
} ;
create [ 'broadcast' ] = function ( ) {
var key = 'broadcast' ;
var $div = makeBlock ( key ) ; // Msg.admin_broadcastHint, admin_broadcastTitle
var form = h ( 'div.cp-admin-broadcast-form' ) ;
var $form = $ ( form ) . appendTo ( $div ) ;
var refresh = getApi ( function ( Broadcast ) {
var button = h ( 'button.btn.btn-primary' , Messages . admin _broadcastButton ) ;
var $button = $ ( button ) ;
var removeButton = h ( 'button.btn.btn-danger' , Messages . admin _broadcastCancel ) ;
var active = h ( 'div.cp-broadcast-active' , h ( 'p' , Messages . admin _broadcastActive ) ) ;
var $active = $ ( active ) ;
var activeUid ;
var deleted = [ ] ;
// Render active message (if there is one)
var hash = Broadcast . lastBroadcastHash || '1' ; // Truthy value if no lastKnownHash
common . mailbox . getNotificationsHistory ( 'broadcast' , null , hash , function ( e , msgs ) {
if ( e ) { return void console . error ( e ) ; }
if ( ! Array . isArray ( msgs ) ) { return ; }
if ( ! msgs . length ) {
$active . hide ( ) ;
}
msgs . reverse ( ) . some ( function ( data ) {
var c = data . content ;
var msg = c && c . msg ;
if ( ! msg ) { return ; }
if ( msg . type === "BROADCAST_DELETE" ) {
deleted . push ( Util . find ( msg , [ 'content' , 'uid' ] ) ) ;
}
if ( msg . type !== "BROADCAST_CUSTOM" ) { return ; }
if ( deleted . indexOf ( msg . uid ) !== - 1 ) { return true ; }
// We found an active custom message, show it
var el = common . mailbox . createElement ( data ) ;
var table = h ( 'table.cp-broadcast-delete' ) ;
var $table = $ ( table ) ;
var uid = Util . find ( data , [ 'content' , 'msg' , 'uid' ] ) ;
var time = Util . find ( data , [ 'content' , 'msg' , 'content' , 'time' ] ) ;
var tr = h ( 'tr' , { 'data-uid' : uid } , [
h ( 'td' , 'ID: ' + uid ) ,
h ( 'td' , new Date ( time || 0 ) . toLocaleString ( ) ) ,
h ( 'td' , el ) ,
h ( 'td.delete' , removeButton ) ,
] ) ;
$table . append ( tr ) ;
$active . append ( table ) ;
activeUid = uid ;
return true ;
} ) ;
if ( ! activeUid ) { $active . hide ( ) ; }
} ) ;
// Custom message
var container = h ( 'div.cp-broadcast-container' ) ;
var $container = $ ( container ) ;
var languages = Messages . _languages ;
var keys = Object . keys ( languages ) . sort ( ) ;
// Always keep the textarea ordered by language code
var reorder = function ( ) {
$container . find ( '.cp-broadcast-lang' ) . each ( function ( i , el ) {
var $el = $ ( el ) ;
var l = $el . attr ( 'data-lang' ) ;
$el . css ( 'order' , keys . indexOf ( l ) ) ;
} ) ;
} ;
// Remove a textarea
var removeLang = function ( l ) {
$container . find ( '.cp-broadcast-lang[data-lang="' + l + '"]' ) . remove ( ) ;
var hasDefault = $container . find ( '.cp-broadcast-lang .cp-checkmark input:checked' ) . length ;
if ( ! hasDefault ) {
$container . find ( '.cp-broadcast-lang' ) . first ( ) . find ( '.cp-checkmark input' ) . prop ( 'checked' , 'checked' ) ;
}
} ;
var getData = function ( ) { return false ; } ;
var onPreview = function ( l ) {
var data = getData ( ) ;
if ( data === false ) { return void UI . warn ( Messages . error ) ; }
var msg = {
uid : Util . uid ( ) ,
type : 'BROADCAST_CUSTOM' ,
content : data
} ;
common . mailbox . onMessage ( {
lang : l ,
type : 'broadcast' ,
content : {
msg : msg ,
hash : 'LOCAL|' + JSON . stringify ( msg ) . slice ( 0 , 58 )
}
} , function ( ) {
UI . log ( Messages . saved ) ;
} ) ;
} ;
// Add a textarea
var addLang = function ( l ) {
if ( $container . find ( '.cp-broadcast-lang[data-lang="' + l + '"]' ) . length ) { return ; }
var preview = h ( 'button.btn.btn-secondary' , Messages . broadcast _preview ) ;
$ ( preview ) . click ( function ( ) {
onPreview ( l ) ;
} ) ;
var bcastDefault = Messages . broadcast _defaultLanguage ;
var first = ! $container . find ( '.cp-broadcast-lang' ) . length ;
var radio = UI . createRadio ( 'broadcastDefault' , null , bcastDefault , first , {
'data-lang' : l ,
label : { class : 'noTitle' }
} ) ;
$container . append ( h ( 'div.cp-broadcast-lang' , { 'data-lang' : l } , [
h ( 'h4' , languages [ l ] ) ,
h ( 'label' , Messages . kanban _body ) ,
h ( 'textarea' ) ,
radio ,
preview
] ) ) ;
reorder ( ) ;
} ;
// Checkboxes to select translations
var boxes = keys . map ( function ( l ) {
var $cbox = $ ( UI . createCheckbox ( 'cp-broadcast-custom-lang-' + l ,
languages [ l ] , false , { label : { class : 'noTitle' } } ) ) ;
var $check = $cbox . find ( 'input' ) . on ( 'change' , function ( ) {
var c = $check . is ( ':checked' ) ;
if ( c ) { return void addLang ( l ) ; }
removeLang ( l ) ;
} ) ;
if ( l === 'en' ) {
setTimeout ( function ( ) {
$check . click ( ) ;
} ) ;
}
return $cbox [ 0 ] ;
} ) ;
// Extract form data
getData = function ( ) {
var map = { } ;
var defaultLanguage ;
var error = false ;
$container . find ( '.cp-broadcast-lang' ) . each ( function ( i , el ) {
var $el = $ ( el ) ;
var l = $el . attr ( 'data-lang' ) ;
if ( ! l ) { error = true ; return ; }
var text = $el . find ( 'textarea' ) . val ( ) ;
if ( ! text . trim ( ) ) { error = true ; return ; }
if ( $el . find ( '.cp-checkmark input' ) . is ( ':checked' ) ) {
defaultLanguage = l ;
}
map [ l ] = text ;
} ) ;
if ( ! Object . keys ( map ) . length ) {
console . error ( 'You must select at least one language' ) ;
return false ;
}
if ( error ) {
console . error ( 'One of the selected languages has no data' ) ;
return false ;
}
return {
defaultLanguage : defaultLanguage ,
content : map
} ;
} ;
var send = function ( data ) {
$button . prop ( 'disabled' , 'disabled' ) ;
//data.time = +new Date(); // FIXME not used anymore?
common . mailbox . sendTo ( 'BROADCAST_CUSTOM' , data , { } , function ( err ) {
if ( err ) {
$button . prop ( 'disabled' , '' ) ;
console . error ( err ) ;
return UI . warn ( Messages . error ) ;
}
UI . log ( Messages . saved ) ;
refresh ( ) ;
checkLastBroadcastHash ( ) ;
} ) ;
} ;
$button . click ( function ( ) {
var data = getData ( ) ;
if ( data === false ) { return void UI . warn ( Messages . error ) ; }
send ( data ) ;
} ) ;
UI . confirmButton ( removeButton , {
classes : 'btn-danger' ,
} , function ( ) {
if ( ! activeUid ) { return ; }
common . mailbox . sendTo ( 'BROADCAST_DELETE' , {
uid : activeUid
} , { } , function ( err ) {
if ( err ) { return UI . warn ( Messages . error ) ; }
UI . log ( Messages . saved ) ;
refresh ( ) ;
checkLastBroadcastHash ( ) ;
} ) ;
} ) ;
// Make the form
$form . empty ( ) . append ( [
active ,
h ( 'label' , Messages . broadcast _translations ) ,
h ( 'div.cp-broadcast-languages' , boxes ) ,
container ,
h ( 'div.cp-broadcast-form-submit' , [
h ( 'br' ) ,
button
] )
] ) ;
} ) ;
refresh ( ) ;
return $div ;
} ;
create [ 'maintenance' ] = function ( ) {
var key = 'maintenance' ;
var $div = makeBlock ( key ) ; // Msg.admin_maintenanceHint, admin_maintenanceTitle
var form = h ( 'div.cp-admin-broadcast-form' ) ;
var $form = $ ( form ) . appendTo ( $div ) ;
var refresh = getApi ( function ( Broadcast ) {
var button = h ( 'button.btn.btn-primary' , Messages . admin _maintenanceButton ) ;
var $button = $ ( button ) ;
var removeButton = h ( 'button.btn.btn-danger' , Messages . admin _maintenanceCancel ) ;
var active ;
if ( Broadcast && Broadcast . maintenance ) {
var m = Broadcast . maintenance ;
if ( m . start && m . end && m . end >= ( + new Date ( ) ) ) {
active = h ( 'div.cp-broadcast-active' , [
UI . setHTML ( h ( 'p' ) , Messages . _getKey ( 'broadcast_maintenance' , [
new Date ( m . start ) . toLocaleString ( ) ,
new Date ( m . end ) . toLocaleString ( ) ,
] ) ) ,
removeButton
] ) ;
}
}
// Start and end date pickers
var start = h ( 'input' ) ;
var end = h ( 'input' ) ;
var $start = $ ( start ) ;
var $end = $ ( end ) ;
var is24h = UIElements . is24h ( ) ;
var dateFormat = "Y-m-d H:i" ;
if ( ! is24h ) { dateFormat = "Y-m-d h:i K" ; }
var endPickr = Flatpickr ( end , {
enableTime : true ,
time _24hr : is24h ,
dateFormat : dateFormat ,
minDate : new Date ( )
} ) ;
Flatpickr ( start , {
enableTime : true ,
time _24hr : is24h ,
minDate : new Date ( ) ,
dateFormat : dateFormat ,
onChange : function ( ) {
endPickr . set ( 'minDate' , new Date ( $start . val ( ) ) ) ;
}
} ) ;
// Extract form data
var getData = function ( ) {
var start = + new Date ( $start . val ( ) ) ;
var end = + new Date ( $end . val ( ) ) ;
if ( isNaN ( start ) || isNaN ( end ) ) {
console . error ( 'Invalid dates' ) ;
return false ;
}
return {
start : start ,
end : end
} ;
} ;
var send = function ( data ) {
$button . prop ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_MAINTENANCE' , [ data ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
$button . prop ( 'disabled' , '' ) ;
return ;
}
// Maintenance applied, send notification
common . mailbox . sendTo ( 'BROADCAST_MAINTENANCE' , { } , { } , function ( ) {
refresh ( ) ;
checkLastBroadcastHash ( ) ;
} ) ;
} ) ;
} ;
$button . click ( function ( ) {
var data = getData ( ) ;
if ( data === false ) { return void UI . warn ( Messages . error ) ; }
send ( data ) ;
} ) ;
UI . confirmButton ( removeButton , {
classes : 'btn-danger' ,
} , function ( ) {
send ( "" ) ;
} ) ;
$form . empty ( ) . append ( [
active ,
h ( 'label' , Messages . broadcast _start ) ,
start ,
h ( 'label' , Messages . broadcast _end ) ,
end ,
h ( 'br' ) ,
h ( 'div.cp-broadcast-form-submit' , [
button
] )
] ) ;
} ) ;
refresh ( ) ;
common . makeUniversal ( 'broadcast' , {
onEvent : function ( obj ) {
var cmd = obj . ev ;
if ( cmd !== "MAINTENANCE" ) { return ; }
refresh ( ) ;
}
} ) ;
return $div ;
} ;
create [ 'survey' ] = function ( ) {
var key = 'survey' ;
var $div = makeBlock ( key ) ; // Msg.admin_surveyHint, admin_surveyTitle
var form = h ( 'div.cp-admin-broadcast-form' ) ;
var $form = $ ( form ) . appendTo ( $div ) ;
var refresh = getApi ( function ( Broadcast ) {
var button = h ( 'button.btn.btn-primary' , Messages . admin _surveyButton ) ;
var $button = $ ( button ) ;
var removeButton = h ( 'button.btn.btn-danger' , Messages . admin _surveyCancel ) ;
var active ;
if ( Broadcast && Broadcast . surveyURL ) {
var a = h ( 'a' , { href : Broadcast . surveyURL } , Messages . admin _surveyActive ) ;
$ ( a ) . click ( function ( e ) {
e . preventDefault ( ) ;
common . openUnsafeURL ( Broadcast . surveyURL ) ;
} ) ;
active = h ( 'div.cp-broadcast-active' , [
h ( 'p' , a ) ,
removeButton
] ) ;
}
// Survey form
var label = h ( 'label' , Messages . broadcast _surveyURL ) ;
var input = h ( 'input' ) ;
var $input = $ ( input ) ;
// Extract form data
var getData = function ( ) {
var url = $input . val ( ) ;
if ( ! Util . isValidURL ( url ) ) {
console . error ( 'Invalid URL' , url ) ;
return false ;
}
return url ;
} ;
var send = function ( data ) {
$button . prop ( 'disabled' , 'disabled' ) ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_SURVEY_URL' , [ data ] ]
} , function ( e , response ) {
if ( e || response . error ) {
$button . prop ( 'disabled' , '' ) ;
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
return ;
}
// Maintenance applied, send notification
common . mailbox . sendTo ( 'BROADCAST_SURVEY' , {
url : data
} , { } , function ( ) {
refresh ( ) ;
checkLastBroadcastHash ( ) ;
} ) ;
} ) ;
} ;
$button . click ( function ( ) {
var data = getData ( ) ;
if ( data === false ) { return void UI . warn ( Messages . error ) ; }
send ( data ) ;
} ) ;
UI . confirmButton ( removeButton , {
classes : 'btn-danger' ,
} , function ( ) {
send ( "" ) ;
} ) ;
$form . empty ( ) . append ( [
active ,
label ,
input ,
h ( 'br' ) ,
h ( 'div.cp-broadcast-form-submit' , [
button
] )
] ) ;
} ) ;
refresh ( ) ;
common . makeUniversal ( 'broadcast' , {
onEvent : function ( obj ) {
var cmd = obj . ev ;
if ( cmd !== "SURVEY" ) { return ; }
refresh ( ) ;
}
} ) ;
return $div ;
} ;
var onRefreshPerformance = Util . mkEvent ( ) ;
create [ 'refresh-performance' ] = function ( ) {
var key = 'refresh-performance' ;
var btn = h ( 'button.btn.btn-primary' , Messages . oo _refresh ) ;
var div = h ( 'div.cp-admin-' + key + '.cp-sidebarlayout-element' , btn ) ;
$ ( btn ) . click ( function ( ) {
onRefreshPerformance . fire ( ) ;
} ) ;
return $ ( div ) ;
} ;
create [ 'performance-profiling' ] = function ( ) {
var $div = makeBlock ( 'performance-profiling' ) ; // Msg.admin_performanceProfilingHint, .admin_performanceProfilingTitle
var onRefresh = function ( ) {
var createBody = function ( ) {
return h ( 'div#profiling-chart.cp-charts.cp-bar-table' , [
h ( 'span.cp-charts-row.heading' , [
h ( 'span' , Messages . admin _performanceKeyHeading ) ,
h ( 'span' , Messages . admin _performanceTimeHeading ) ,
h ( 'span' , Messages . admin _performancePercentHeading ) ,
//h('span', ''), //Messages.admin_performancePercentHeading),
] ) ,
] ) ;
} ;
var body = createBody ( ) ;
var appendRow = function ( key , time , percent , scaled ) {
//console.log("[%s] %ss running time (%s%)", key, time, percent);
body . appendChild ( h ( 'span.cp-charts-row' , [
h ( 'span' , key ) ,
h ( 'span' , time ) ,
//h('span', percent),
h ( 'span.cp-bar-container' , [
h ( 'span.cp-bar.profiling-percentage' , {
style : 'width: ' + scaled + '%' ,
} , ' ' ) ,
h ( 'span.profiling-label' , percent + '%' ) ,
] ) ,
] ) ) ;
} ;
var process = function ( _o ) {
$ ( '#profiling-chart' ) . remove ( ) ;
body = createBody ( ) ;
var o = _o [ 0 ] ;
var sorted = Object . keys ( o ) . sort ( function ( a , b ) {
if ( o [ b ] - o [ a ] <= 0 ) { return - 1 ; }
return 1 ;
} ) ;
var values = sorted . map ( function ( k ) { return o [ k ] ; } ) ;
var total = 0 ;
values . forEach ( function ( value ) { total += value ; } ) ;
var max = Math . max . apply ( null , values ) ;
sorted . forEach ( function ( k ) {
var percent = Math . floor ( ( o [ k ] / total ) * 1000 ) / 10 ;
appendRow ( k , o [ k ] , percent , ( o [ k ] / max ) * 100 ) ;
} ) ;
$div . append ( h ( 'div.width-constrained' , body ) ) ;
} ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'GET_WORKER_PROFILES' ,
} , function ( e , data ) {
if ( e || data . error ) {
UI . warn ( Messages . error ) ;
return void console . error ( e , data ) ;
}
process ( data ) ;
} ) ;
} ;
onRefresh ( ) ;
onRefreshPerformance . reg ( onRefresh ) ;
return $div ;
} ;
Messages . admin _enableDiskMeasurementsTitle = "Measure disk performance" ; // XXX
Messages . admin _enableDiskMeasurementsHint = "If enabled, a JSON endpoint will be exposed under /api/profiling which keeps a running measurement of disk I/O within a configurable window (set below). This setting can impact server performance and may reveal data you'd rather keep hidden. It is recommended that you leave it disabled unless you know what you are doing." ; // XXX
create [ 'enable-disk-measurements' ] = makeAdminCheckbox ( { // Msg.admin_enableDiskMeasurementsTitle.admin_enableDiskMeasurementsHint
key : 'enable-disk-measurements' ,
getState : function ( ) {
return APP . instanceStatus . enableProfiling ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'ENABLE_PROFILING' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . enableProfiling ) ;
} ) ;
} ) ;
} ,
} ) ;
Messages . admin _bytesWrittenTitle = "Disk performance measurement window" ; // XXX
Messages . admin _bytesWrittenHint = "If you have enabled disk performance measurements then the duration of the window can be configured below." ; // XXX
Messages . admin _bytesWrittenDuration = "Duration of the window in milliseconds: {0}" ; // XXX
//Messages.admin_defaultDuration = "admin_defaultDuration"; // XXX
Messages . admin _setDuration = "Set duration" ; // XXX
var isPositiveInteger = function ( n ) {
return n && typeof ( n ) === 'number' && n % 1 === 0 && n > 0 ;
} ;
create [ 'bytes-written' ] = function ( ) {
var key = 'bytes-written' ;
var $div = makeBlock ( key ) ;
var duration = APP . instanceStatus . profilingWindow ;
if ( ! isPositiveInteger ( duration ) ) { duration = 10000 ; }
var newDuration = h ( 'input' , { type : 'number' , min : 0 , value : duration } ) ;
var set = h ( 'button.btn.btn-primary' , Messages . admin _setDuration ) ;
$div . append ( h ( 'div' , [
h ( 'span.cp-admin-bytes-written-duration' , Messages . _getKey ( 'admin_bytesWrittenDuration' , [ duration ] ) ) ,
h ( 'div.cp-admin-setlimit-form' , [
newDuration ,
h ( 'nav' , [ set ] )
] )
] ) ) ;
UI . confirmButton ( set , {
classes : 'btn-primary' ,
multiple : true ,
validate : function ( ) {
var l = parseInt ( $ ( newDuration ) . val ( ) ) ;
if ( isNaN ( l ) ) { return false ; }
return true ;
}
} , function ( ) {
var d = parseInt ( $ ( newDuration ) . val ( ) ) ;
if ( ! isPositiveInteger ( d ) ) { return void UI . warn ( Messages . error ) ; }
var data = [ d ] ;
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'SET_PROFILING_WINDOW' , data ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
return void console . error ( e , response ) ;
}
$div . find ( '.cp-admin-bytes-written-duration' ) . text ( Messages . _getKey ( 'admin_bytesWrittenDuration' , [ d ] ) ) ;
} ) ;
} ) ;
return $div ;
} ;
create [ 'update-available' ] = function ( ) { // Messages.admin_updateAvailableTitle.admin_updateAvailableHint.admin_updateAvailableLabel.admin_updateAvailableButton
if ( ! APP . instanceStatus . updateAvailable ) { return ; }
var $div = makeBlock ( 'update-available' , true ) ;
var updateURL = 'https://github.com/xwiki-labs/cryptpad/releases/latest' ;
if ( typeof ( APP . instanceStatus . updateAvailable ) === 'string' ) {
updateURL = APP . instanceStatus . updateAvailable ;
}
$div . find ( 'button' ) . click ( function ( ) {
common . openURL ( updateURL ) ;
} ) ;
return $div ;
} ;
create [ 'checkup' ] = function ( ) {
var $div = makeBlock ( 'checkup' , true ) ; // Messages.admin_checkupButton.admin_checkupHint.admin_checkupTitle
$div . find ( 'button' ) . click ( function ( ) {
common . openURL ( '/checkup/' ) ;
} ) ;
return $div ;
} ;
create [ 'consent-to-contact' ] = makeAdminCheckbox ( { // Messages.admin_consentToContactTitle.admin_consentToContactHint.admin_consentToContactLabel
key : 'consent-to-contact' ,
getState : function ( ) {
return APP . instanceStatus . consentToContact ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'CONSENT_TO_CONTACT' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . consentToContact ) ;
} ) ;
} ) ;
} ,
} ) ;
create [ 'list-my-instance' ] = makeAdminCheckbox ( { // Messages.admin_listMyInstanceTitle.admin_listMyInstanceHint.admin_listMyInstanceLabel
key : 'list-my-instance' ,
getState : function ( ) {
return APP . instanceStatus . listMyInstance ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'LIST_MY_INSTANCE' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . listMyInstance ) ;
} ) ;
} ) ;
} ,
} ) ;
create [ 'provide-aggregate-statistics' ] = makeAdminCheckbox ( { // Messages.admin_provideAggregateStatisticsTitle.admin_provideAggregateStatisticsHint.admin_provideAggregateStatisticsLabel
key : 'provide-aggregate-statistics' ,
getState : function ( ) {
return APP . instanceStatus . provideAggregateStatistics ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'PROVIDE_AGGREGATE_STATISTICS' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . provideAggregateStatistics ) ;
} ) ;
} ) ;
} ,
} ) ;
create [ 'remove-donate-button' ] = makeAdminCheckbox ( { // Messages.admin_removeDonateButtonTitle.admin_removeDonateButtonHint.admin_removeDonateButtonLabel
key : 'remove-donate-button' ,
getState : function ( ) {
return APP . instanceStatus . removeDonateButton ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'REMOVE_DONATE_BUTTON' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . removeDonateButton ) ;
} ) ;
} ) ;
} ,
} ) ;
create [ 'block-daily-check' ] = makeAdminCheckbox ( { // Messages.admin_blockDailyCheckTitle.admin_blockDailyCheckHint.admin_blockDailyCheckLabel
key : 'block-daily-check' ,
getState : function ( ) {
return APP . instanceStatus . blockDailyCheck ;
} ,
query : function ( val , setState ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : [ 'BLOCK_DAILY_CHECK' , [ val ] ]
} , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
console . error ( e , response ) ;
}
APP . updateStatus ( function ( ) {
setState ( APP . instanceStatus . blockDailyCheck ) ;
} ) ;
} ) ;
} ,
} ) ;
var sendDecree = function ( data , cb ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'ADMIN_DECREE' ,
data : data ,
} , cb ) ;
} ;
create [ 'instance-purpose' ] = function ( ) {
var key = 'instance-purpose' ;
var $div = makeBlock ( key ) ; // Messages.admin_instancePurposeTitle.admin_instancePurposeHint
var values = [
'noanswer' , // Messages.admin_purpose_noanswer
'experiment' , // Messages.admin_purpose_experiment
'personal' , // Messages.admin_purpose_personal
'education' , // Messages.admin_purpose_education
'org' , // Messages.admin_purpose_org
'business' , // Messages.admin_purpose_business
'public' , // Messages.admin_purpose_public
] ;
var defaultPurpose = 'noanswer' ;
var purpose = APP . instanceStatus . instancePurpose || defaultPurpose ;
var opts = h ( 'div.cp-admin-radio-container' , [
values . map ( function ( key ) {
var full _key = 'admin_purpose_' + key ;
return UI . createRadio ( 'cp-instance-purpose-radio' , 'cp-instance-purpose-radio-' + key ,
Messages [ full _key ] || Messages . _getKey ( full _key , [ defaultPurpose ] ) ,
key === purpose , {
input : { value : key } ,
label : { class : 'noTitle' }
} ) ;
} )
] ) ;
var $opts = $ ( opts ) ;
//var $br = $(h('br',));
//$div.append($br);
$div . append ( opts ) ;
var setPurpose = function ( value , cb ) {
sendDecree ( [
'SET_INSTANCE_PURPOSE' ,
[ value ]
] , cb ) ;
} ;
$opts . on ( 'change' , function ( ) {
var val = $opts . find ( 'input:radio:checked' ) . val ( ) ;
console . log ( val ) ;
//spinner.spin();
setPurpose ( val , function ( e , response ) {
if ( e || response . error ) {
UI . warn ( Messages . error ) ;
//spinner.hide();
return ;
}
//spinner.done();
UI . log ( Messages . saved ) ;
} ) ;
} ) ;
return $div ;
} ;
var hideCategories = function ( ) {
APP . $rightside . find ( '> div' ) . hide ( ) ;
} ;
var showCategories = function ( cat ) {
hideCategories ( ) ;
cat . forEach ( function ( c ) {
APP . $rightside . find ( '.' + c ) . show ( ) ;
} ) ;
} ;
var SIDEBAR _ICONS = {
general : 'fa fa-user-o' ,
stats : 'fa fa-line-chart' ,
quota : 'fa fa-hdd-o' ,
support : 'fa fa-life-ring' ,
broadcast : 'fa fa-bullhorn' ,
performance : 'fa fa-heartbeat' ,
network : 'fa fa-sitemap' , // or fa-university ?
} ;
var createLeftside = function ( ) {
var $categories = $ ( '<div>' , { 'class' : 'cp-sidebarlayout-categories' } )
. appendTo ( APP . $leftside ) ;
var metadataMgr = common . getMetadataMgr ( ) ;
var privateData = metadataMgr . getPrivateData ( ) ;
var active = privateData . category || 'general' ;
if ( active . indexOf ( '-' ) !== - 1 ) {
active = active . split ( '-' ) [ 0 ] ;
}
if ( ! categories [ active ] ) { active = 'general' ; }
common . setHash ( active ) ;
Object . keys ( categories ) . forEach ( function ( key ) {
var iconClass = SIDEBAR _ICONS [ key ] ;
var icon ;
if ( iconClass ) {
icon = h ( 'span' , { class : iconClass } ) ;
}
var $category = $ ( h ( 'div' , {
'class' : 'cp-sidebarlayout-category'
} , [
icon ,
Messages [ 'admin_cat_' + key ] || key ,
] ) ) . appendTo ( $categories ) ;
if ( key === active ) {
$category . addClass ( 'cp-leftside-active' ) ;
}
$category . click ( function ( ) {
if ( ! Array . isArray ( categories [ key ] ) && categories [ key ] . onClick ) {
categories [ key ] . onClick ( ) ;
return ;
}
active = key ;
common . setHash ( key ) ;
$categories . find ( '.cp-leftside-active' ) . removeClass ( 'cp-leftside-active' ) ;
$category . addClass ( 'cp-leftside-active' ) ;
showCategories ( categories [ key ] ) ;
} ) ;
} ) ;
showCategories ( categories [ active ] ) ;
} ;
var createToolbar = function ( ) {
var displayed = [ 'useradmin' , 'newpad' , 'limit' , 'pageTitle' , 'notifications' ] ;
var configTb = {
displayed : displayed ,
sfCommon : common ,
$container : APP . $toolbar ,
pageTitle : Messages . adminPage || 'Admin' ,
metadataMgr : common . getMetadataMgr ( ) ,
} ;
APP . toolbar = Toolbar . create ( configTb ) ;
APP . toolbar . $rightside . hide ( ) ;
} ;
var updateStatus = APP . updateStatus = function ( cb ) {
sFrameChan . query ( 'Q_ADMIN_RPC' , {
cmd : 'INSTANCE_STATUS' ,
} , function ( e , data ) {
if ( e ) { console . error ( e ) ; return void cb ( e ) ; }
if ( ! Array . isArray ( data ) ) { return void cb ( 'EINVAL' ) ; }
APP . instanceStatus = data [ 0 ] ;
console . log ( "Status" , APP . instanceStatus ) ;
/ *
var isListed = Boolean ( APP . instanceStatus . listMyInstance ) ;
var $actions = $ ( '.cp-listing-action' ) ;
var $fields = $ ( '.cp-listing-info' ) ;
if ( isListed ) {
$actions . removeAttr ( 'disabled' ) ;
$fields . removeAttr ( 'disabled' ) ;
} else {
$actions . attr ( 'disabled' , 'disabled' ) ;
$fields . attr ( 'disabled' , 'disabled' ) ;
}
* /
cb ( ) ;
} ) ;
} ;
nThen ( function ( waitFor ) {
$ ( waitFor ( UI . addLoadingScreen ) ) ;
SFCommon . create ( waitFor ( function ( c ) { APP . common = common = c ; } ) ) ;
} ) . nThen ( function ( waitFor ) {
APP . $container = $ ( '#cp-sidebarlayout-container' ) ;
APP . $toolbar = $ ( '#cp-toolbar' ) ;
APP . $leftside = $ ( '<div>' , { id : 'cp-sidebarlayout-leftside' } ) . appendTo ( APP . $container ) ;
APP . $rightside = $ ( '<div>' , { id : 'cp-sidebarlayout-rightside' } ) . appendTo ( APP . $container ) ;
sFrameChan = common . getSframeChannel ( ) ;
sFrameChan . onReady ( waitFor ( ) ) ;
} ) . nThen ( function ( waitFor ) {
updateStatus ( waitFor ( ) ) ;
} ) . nThen ( function ( /*waitFor*/ ) {
createToolbar ( ) ;
var metadataMgr = common . getMetadataMgr ( ) ;
var privateData = metadataMgr . getPrivateData ( ) ;
common . setTabTitle ( Messages . adminPage || 'Administration' ) ;
if ( ! common . isAdmin ( ) ) {
return void UI . errorLoadingScreen ( Messages . admin _authError || '403 Forbidden' ) ;
}
APP . privateKey = privateData . supportPrivateKey ;
APP . origin = privateData . origin ;
APP . readOnly = privateData . readOnly ;
APP . support = Support . create ( common , true ) ;
// Content
var $rightside = APP . $rightside ;
var addItem = function ( cssClass ) {
var item = cssClass . slice ( 9 ) ; // remove 'cp-settings-'
if ( typeof ( create [ item ] ) === "function" ) {
$rightside . append ( create [ item ] ( ) ) ;
}
} ;
for ( var cat in categories ) {
if ( ! Array . isArray ( categories [ cat ] ) ) { continue ; }
categories [ cat ] . forEach ( addItem ) ;
}
createLeftside ( ) ;
UI . removeLoadingScreen ( ) ;
} ) ;
} ) ;