2018-01-22 13:01:38 -08:00
'use strict' ;
2024-10-04 17:43:45 +02:00
/* global $, async, angular, redirectIfNeeded */
2023-09-26 12:58:19 +02:00
/* global ERROR,ISTATES,HSTATES,RSTATES,APP_TYPES,NOTIFICATION_TYPES */
2018-01-22 13:01:38 -08:00
// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
var search = decodeURIComponent ( window . location . search ) . slice ( 1 ) . split ( '&' ) . map ( function ( item ) { return item . split ( '=' ) ; } ) . reduce ( function ( o , k ) { o [ k [ 0 ] ] = k [ 1 ] ; return o ; } , { } ) ;
if ( search . accessToken ) {
localStorage . token = search . accessToken ;
// strip the accessToken and expiresAt, then preserve the rest
delete search . accessToken ;
delete search . expiresAt ;
// this will reload the page as this is not a hash change
window . location . search = encodeURIComponent ( Object . keys ( search ) . map ( function ( key ) { return key + '=' + search [ key ] ; } ) . join ( '&' ) ) ;
}
// create main application module
2024-10-04 16:38:36 +02:00
var app = angular . module ( 'Application' , [ 'pascalprecht.translate' , 'ngCookies' , 'ngFitText' , 'ngRoute' , 'ngAnimate' , 'ngSanitize' , 'angular-md5' , 'base64' , 'slick' , 'ui-notification' , 'ui.bootstrap' , 'ui.multiselect' ] ) ;
2018-01-22 13:01:38 -08:00
app . config ( [ 'NotificationProvider' , function ( NotificationProvider ) {
NotificationProvider . setOptions ( {
delay : 5000 ,
startTop : 60 ,
positionX : 'left' ,
templateUrl : 'notification.html'
} ) ;
} ] ) ;
2019-07-31 08:08:46 +02:00
// configure resourceUrlWhitelist https://code.angularjs.org/1.5.8/docs/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist
app . config ( function ( $sceDelegateProvider ) {
$sceDelegateProvider . resourceUrlWhitelist ( [
// Allow same origin resource loads.
'self' ,
2019-08-06 10:24:35 +02:00
// Allow loading from our assets domain.
'https://cloudron.io/**' ,
'https://staging.cloudron.io/**' ,
'https://dev.cloudron.io/**' ,
// Allow local development against the appstore pages
2019-08-04 11:32:42 +02:00
'http://localhost:5000/**'
2019-07-31 08:08:46 +02:00
] ) ;
} ) ;
2018-01-22 13:01:38 -08:00
// setup all major application routes
app . config ( [ '$routeProvider' , function ( $routeProvider ) {
$routeProvider . when ( '/' , {
redirectTo : '/apps'
} ) . when ( '/users' , {
2025-02-11 18:50:10 +01:00
// controller: 'UsersController',
// templateUrl: 'views/users.html?' + window.VITE_CACHE_ID
2024-12-02 09:02:46 +01:00
} ) . when ( '/user-directory' , {
2025-01-19 19:12:00 +01:00
// controller: 'UserSettingsController',
// templateUrl: 'views/user-directory.html?' + window.VITE_CACHE_ID
2019-09-18 17:45:13 +02:00
} ) . when ( '/app/:appId/:view?' , {
2025-02-20 10:54:43 +01:00
// controller: 'AppController',
// templateUrl: 'views/app.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . when ( '/appstore' , {
2025-01-05 22:47:50 +01:00
// controller: 'AppStoreController',
// templateUrl: 'views/appstore.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . when ( '/appstore/:appId' , {
2025-01-08 14:16:17 +01:00
// controller: 'AppStoreController',
// templateUrl: 'views/appstore.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . when ( '/apps' , {
2024-12-29 00:36:48 +01:00
// controller: 'AppsController',
// templateUrl: 'views/apps.html?' + window.VITE_CACHE_ID
2019-11-07 11:07:57 +01:00
} ) . when ( '/profile' , {
2025-01-14 10:27:27 +01:00
// controller: 'ProfileController',
// templateUrl: 'views/profile.html?' + window.VITE_CACHE_ID
2018-06-07 14:22:48 +02:00
} ) . when ( '/backups' , {
2025-02-04 15:10:38 +01:00
// controller: 'BackupsController',
// templateUrl: 'views/backups.html?' + window.VITE_CACHE_ID
2020-03-18 17:53:50 -07:00
} ) . when ( '/branding' , {
2025-02-10 18:42:02 +01:00
// controller: 'BrandingController',
// templateUrl: 'views/branding.html?' + window.VITE_CACHE_ID
2019-11-07 15:26:18 +01:00
} ) . when ( '/network' , {
2025-01-22 14:46:31 +01:00
// controller: 'NetworkController',
// templateUrl: 'views/network.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . when ( '/domains' , {
2025-01-27 22:20:26 +01:00
// controller: 'DomainsController',
// templateUrl: 'views/domains.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . when ( '/email' , {
2025-03-07 19:47:58 +01:00
// controller: 'EmailsController',
// templateUrl: 'views/emails.html?' + window.VITE_CACHE_ID
2021-09-30 14:35:06 +02:00
} ) . when ( '/emails-eventlog' , {
2025-03-10 16:16:04 +01:00
// controller: 'EmailsEventlogController',
// templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
2025-03-13 16:16:07 +01:00
} ) . when ( '/emails-mailboxes' , {
// controller: 'EmailsEventlogController',
// templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
2025-03-14 21:51:26 +01:00
} ) . when ( '/emails-mailinglists' , {
// controller: 'EmailsEventlogController',
// templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
2022-08-31 08:45:28 +02:00
} ) . when ( '/emails-queue' , {
2025-03-10 21:06:33 +01:00
// controller: 'EmailsQueueController',
// templateUrl: 'views/emails-queue.html?' + window.VITE_CACHE_ID
2021-11-19 15:45:16 +01:00
} ) . when ( '/email/:domain/:view?' , {
2025-03-10 21:06:33 +01:00
// controller: 'EmailController',
// templateUrl: 'views/email.html?' + window.VITE_CACHE_ID
2019-01-09 15:18:10 +01:00
} ) . when ( '/notifications' , {
controller : 'NotificationsController' ,
2024-12-19 13:59:19 +01:00
templateUrl : 'views/notifications.html?' + window . VITE _CACHE _ID
2023-03-21 17:40:32 +01:00
} ) . when ( '/oidc' , {
2024-12-02 09:02:46 +01:00
redirectTo : '/user-directory'
2018-01-22 13:01:38 -08:00
} ) . when ( '/settings' , {
2025-01-23 18:36:30 +01:00
// controller: 'SettingsController',
// templateUrl: 'views/settings.html?' + window.VITE_CACHE_ID
2021-06-15 11:17:55 -07:00
} ) . when ( '/eventlog' , {
2025-01-25 17:09:53 +01:00
// controller: 'EventLogController',
// templateUrl: 'views/eventlog.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . when ( '/support' , {
2024-11-01 14:16:09 +01:00
// controller: 'SupportController',
// templateUrl: 'views/support.html?' + window.VITE_CACHE_ID
2018-11-26 08:59:04 +01:00
} ) . when ( '/system' , {
2025-02-17 11:18:57 +01:00
// controller: 'SystemController',
// templateUrl: 'views/system.html?' + window.VITE_CACHE_ID
2020-09-28 15:16:02 +02:00
} ) . when ( '/services' , {
2025-01-21 16:54:56 +01:00
// controller: 'ServicesController',
// templateUrl: 'views/services.html?' + window.VITE_CACHE_ID
2020-10-28 16:14:32 -07:00
} ) . when ( '/volumes' , {
2024-12-26 12:19:48 +01:00
// controller: 'VolumesController',
// templateUrl: 'views/volumes.html?' + window.VITE_CACHE_ID
2018-01-22 13:01:38 -08:00
} ) . otherwise ( { redirectTo : '/' } ) ;
} ] ) ;
2024-02-08 11:51:56 +01:00
app . filter ( 'notificationTypeToColor' , function ( ) {
2023-09-26 12:58:19 +02:00
return function ( n ) {
switch ( n . type ) {
2024-12-11 15:18:00 +01:00
case NOTIFICATION _TYPES . REBOOT :
case NOTIFICATION _TYPES . APP _OOM :
case NOTIFICATION _TYPES . MAIL _STATUS :
case NOTIFICATION _TYPES . CERTIFICATE _RENEWAL _FAILED :
case NOTIFICATION _TYPES . DISK _SPACE :
case NOTIFICATION _TYPES . BACKUP _CONFIG :
case NOTIFICATION _TYPES . BACKUP _FAILED :
2025-02-17 19:01:53 +01:00
case NOTIFICATION _TYPES . DOMAIN _CONFIG _CHECK _FAILED :
2023-09-26 12:58:19 +02:00
return '#ff4c4c' ;
2024-12-11 15:18:00 +01:00
case NOTIFICATION _TYPES . BOX _UPDATE :
case NOTIFICATION _TYPES . MANUAL _APP _UPDATE :
2023-09-26 12:58:19 +02:00
return '#f0ad4e' ;
default :
return '#2196f3' ;
}
} ;
} ) ;
2022-11-10 17:43:50 +01:00
app . filter ( 'capitalize' , function ( ) {
return function ( s ) {
return s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) ;
} ;
} ) ;
2019-08-29 14:28:45 -07:00
app . filter ( 'activeTask' , function ( ) {
return function ( app ) {
2019-09-18 18:06:03 +02:00
if ( ! app ) return false ;
2019-08-29 14:28:45 -07:00
return app . taskId !== null ;
} ;
} ) ;
2018-01-22 13:01:38 -08:00
app . filter ( 'installSuccess' , function ( ) {
return function ( app ) {
2019-09-18 18:06:03 +02:00
if ( ! app ) return false ;
2018-01-22 13:01:38 -08:00
return app . installationState === ISTATES . INSTALLED ;
} ;
} ) ;
2018-06-15 13:35:46 +02:00
app . filter ( 'appIsInstalledAndHealthy' , function ( ) {
return function ( app ) {
2019-09-18 18:06:03 +02:00
if ( ! app ) return false ;
2019-09-19 18:27:02 +02:00
return ( app . installationState === ISTATES . INSTALLED && app . health === HSTATES . HEALTHY && app . runState === RSTATES . RUNNING ) ;
2019-09-10 13:36:29 -07:00
} ;
2018-06-15 13:35:46 +02:00
} ) ;
2022-05-25 21:10:53 +02:00
app . filter ( 'applicationLink' , function ( ) {
2019-09-19 19:46:20 +02:00
return function ( app ) {
if ( ! app ) return '' ;
2022-09-13 12:39:56 +02:00
// app links have http already in the fqdn
if ( app . fqdn . indexOf ( 'http' ) !== 0 ) return 'https://' + app . fqdn ;
return app . fqdn ;
2019-09-19 19:46:20 +02:00
} ;
} ) ;
2022-05-25 21:10:53 +02:00
app . filter ( 'userManagementFilter' , function ( ) {
return function ( apps , option ) {
return apps . filter ( function ( app ) {
if ( option . id === '' ) return true ;
if ( option . id === 'sso' ) return ! ! ( app . manifest . optionalSso || app . manifest . addons . ldap || app . manifest . addons . proxyAuth ) ;
if ( option . id === 'nosso' ) return app . manifest . optionalSso || ( ! app . manifest . addons . ldap && ! app . manifest . addons . proxyAuth ) ;
if ( option . id === 'email' ) return ! ! app . manifest . addons . email ;
return false ;
} ) ;
} ;
} ) ;
2019-07-31 11:38:28 -07:00
// this appears when an item in app grid is clicked
2019-09-07 09:24:39 +02:00
app . filter ( 'prettyAppErrorMessage' , function ( ) {
2019-07-31 11:40:16 -07:00
return function ( app ) {
2019-09-07 09:24:39 +02:00
if ( ! app ) return '' ;
2019-07-31 11:43:14 -07:00
if ( app . installationState === ISTATES . INSTALLED ) {
// app.health can also be null to indicate insufficient data
if ( app . health === HSTATES . UNHEALTHY ) return 'The app is not responding to health checks. Check the logs for any error messages.' ;
}
2019-09-05 18:15:05 -07:00
if ( app . error . reason === 'Access Denied' ) {
if ( app . error . domain ) return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.' ;
} else if ( app . error . reason === 'Already Exists' ) {
if ( app . error . domain ) return 'The DNS record for this location already exists. Cloudron does not remove existing DNS records. Manually remove the DNS record and then click on repair.' ;
}
return app . error . message ;
2018-01-22 13:01:38 -08:00
} ;
} ) ;
2019-07-31 11:38:28 -07:00
// this appears as tool tip in app grid
2019-09-05 10:05:01 -07:00
app . filter ( 'appProgressMessage' , function ( ) {
2019-07-31 11:40:16 -07:00
return function ( app ) {
2019-09-05 10:05:01 -07:00
var message = app . message || ( app . error ? app . error . message : '' ) ;
2018-01-22 13:01:38 -08:00
return message ;
} ;
} ) ;
2020-09-26 17:50:23 +02:00
// see apps.js $scope.states
2022-05-14 14:45:22 +02:00
app . filter ( 'selectedStateFilter' , [ 'Client' , function ( Client ) {
2020-09-26 17:50:23 +02:00
return function selectedStateFilter ( apps , selectedState ) {
return apps . filter ( function ( app ) {
if ( ! selectedState || ! selectedState . state ) return true ;
2022-09-13 11:44:54 +02:00
if ( selectedState . state === 'running' ) return app . runState === RSTATES . RUNNING && app . health === HSTATES . HEALTHY && app . installationState === ISTATES . INSTALLED ;
if ( selectedState . state === 'stopped' ) return app . runState === RSTATES . STOPPED ;
2022-05-14 14:45:22 +02:00
if ( selectedState . state === 'update_available' ) return ! ! ( Client . getConfig ( ) . update [ app . id ] && Client . getConfig ( ) . update [ app . id ] . manifest . version && Client . getConfig ( ) . update [ app . id ] . manifest . version !== app . manifest . version ) ;
2020-09-26 17:50:23 +02:00
2022-09-13 11:44:54 +02:00
return app . runState === RSTATES . RUNNING && ( app . health !== HSTATES . HEALTHY || app . installationState !== ISTATES . INSTALLED ) ; // not responding
2020-09-26 17:50:23 +02:00
} ) ;
} ;
2022-05-14 14:45:22 +02:00
} ] ) ;
2020-09-26 17:50:23 +02:00
2021-02-16 20:10:11 +01:00
app . filter ( 'selectedGroupAccessFilter' , function ( ) {
return function selectedGroupAccessFilter ( apps , group ) {
return apps . filter ( function ( app ) {
if ( ! group . id ) return true ; // case for no filter entry
if ( ! app . accessRestriction ) return true ;
if ( ! app . accessRestriction . groups ) return false ;
if ( app . accessRestriction . groups . indexOf ( group . id ) !== - 1 ) return true ;
return false ;
} ) ;
} ;
} ) ;
2019-04-15 14:31:12 +02:00
app . filter ( 'selectedTagFilter' , function ( ) {
return function selectedTagFilter ( apps , selectedTags ) {
return apps . filter ( function ( app ) {
if ( selectedTags . length === 0 ) return true ;
2019-05-17 13:01:23 -07:00
if ( ! app . tags ) return false ;
2019-04-15 14:31:12 +02:00
2019-05-17 13:01:23 -07:00
for ( var i = 0 ; i < selectedTags . length ; i ++ ) {
if ( app . tags . indexOf ( selectedTags [ i ] ) === - 1 ) return false ;
}
return true ;
2019-04-15 14:31:12 +02:00
} ) ;
} ;
} ) ;
app . filter ( 'selectedDomainFilter' , function ( ) {
2019-05-20 23:40:02 +02:00
return function selectedDomainFilter ( apps , selectedDomain ) {
2019-04-15 14:31:12 +02:00
return apps . filter ( function ( app ) {
2019-05-20 23:40:02 +02:00
if ( selectedDomain . _alldomains ) return true ; // magic domain for single select, see apps.js ALL_DOMAINS_DOMAIN
2022-09-28 12:06:38 +02:00
if ( app . type === APP _TYPES . LINK ) return false ;
2019-04-15 14:31:12 +02:00
2019-05-20 23:40:02 +02:00
if ( selectedDomain . domain === app . domain ) return true ;
2021-01-18 17:55:48 -08:00
if ( app . aliasDomains . find ( function ( ad ) { return ad . domain === selectedDomain . domain ; } ) ) return true ;
2022-01-14 22:32:41 -08:00
if ( app . redirectDomains . find ( function ( ad ) { return ad . domain === selectedDomain . domain ; } ) ) return true ;
2021-01-18 17:55:48 -08:00
return false ;
2019-04-15 14:31:12 +02:00
} ) ;
} ;
} ) ;
2020-01-06 15:27:31 +01:00
app . filter ( 'appSearchFilter' , function ( ) {
return function appSearchFilter ( apps , appSearch ) {
return apps . filter ( function ( app ) {
if ( ! appSearch ) return true ;
2020-08-14 09:32:07 -07:00
appSearch = appSearch . toLowerCase ( ) ;
return app . fqdn . indexOf ( appSearch ) !== - 1
|| ( app . label && app . label . toLowerCase ( ) . indexOf ( appSearch ) !== - 1 )
2020-11-05 10:14:46 -08:00
|| ( app . manifest . title && app . manifest . title . toLowerCase ( ) . indexOf ( appSearch ) !== - 1 )
|| ( appSearch . length >= 6 && app . id . indexOf ( appSearch ) !== - 1 ) ;
2020-01-06 15:27:31 +01:00
} ) ;
} ;
} ) ;
2019-04-15 14:31:12 +02:00
app . filter ( 'prettyDomains' , function ( ) {
return function prettyDomains ( domains ) {
return domains . map ( function ( d ) { return d . domain ; } ) . join ( ', ' ) ;
} ;
} ) ;
2018-01-22 13:01:38 -08:00
app . filter ( 'installationActive' , function ( ) {
2019-08-30 21:21:44 +02:00
return function ( app ) {
2018-01-22 13:01:38 -08:00
if ( app . installationState === ISTATES . ERROR ) return false ;
if ( app . installationState === ISTATES . INSTALLED ) return false ;
return true ;
} ;
} ) ;
2024-06-21 21:17:05 +02:00
// color indicator in app list
app . filter ( 'installationStateClass' , function ( ) {
const ERROR _CLASS = 'status-error' ;
const BUSY _CLASS = 'status-starting fa-beat-fade' ;
const INACTIVE _CLASS = 'status-inactive' ;
const ACTIVE _CLASS = 'status-active' ;
return function ( app ) {
if ( ! app ) return '' ;
switch ( app . installationState ) {
case ISTATES . ERROR : return ERROR _CLASS ;
case ISTATES . INSTALLED : {
if ( app . debugMode ) {
return INACTIVE _CLASS ;
} else {
if ( app . runState === RSTATES . RUNNING ) {
if ( ! app . health ) return BUSY _CLASS ; // no data yet
if ( app . type === APP _TYPES . LINK || app . health === HSTATES . HEALTHY ) return ACTIVE _CLASS ;
return ERROR _CLASS ; // dead/exit/unhealthy
} else {
return INACTIVE _CLASS ;
}
}
}
default : return BUSY _CLASS ;
}
} ;
} ) ;
2019-07-31 11:38:28 -07:00
// this appears in the app grid
2019-08-30 21:21:44 +02:00
app . filter ( 'installationStateLabel' , function ( ) {
2020-05-12 21:30:57 -07:00
return function ( app ) {
2019-09-11 21:24:02 +02:00
if ( ! app ) return '' ;
2019-09-23 22:45:14 +02:00
var waiting = app . progress === 0 ? ' (Queued)' : '' ;
2018-01-22 13:01:38 -08:00
switch ( app . installationState ) {
case ISTATES . PENDING _INSTALL :
return 'Installing' + waiting ;
2019-09-19 09:28:24 -07:00
case ISTATES . PENDING _CLONE :
return 'Cloning' + waiting ;
2019-09-10 14:30:44 -07:00
case ISTATES . PENDING _LOCATION _CHANGE :
case ISTATES . PENDING _CONFIGURE :
case ISTATES . PENDING _RECREATE _CONTAINER :
2023-07-14 17:16:48 +05:30
case ISTATES . PENDING _SERVICES _CHANGE :
2019-09-10 14:30:44 -07:00
case ISTATES . PENDING _DEBUG :
return 'Configuring' + waiting ;
2019-09-19 09:28:24 -07:00
case ISTATES . PENDING _RESIZE :
return 'Resizing' + waiting ;
2019-09-10 14:30:44 -07:00
case ISTATES . PENDING _DATA _DIR _MIGRATION :
return 'Migrating data' + waiting ;
2018-01-22 13:01:38 -08:00
case ISTATES . PENDING _UNINSTALL : return 'Uninstalling' + waiting ;
case ISTATES . PENDING _RESTORE : return 'Restoring' + waiting ;
2021-05-26 09:32:17 -07:00
case ISTATES . PENDING _IMPORT : return 'Importing' + waiting ;
2018-01-22 13:01:38 -08:00
case ISTATES . PENDING _UPDATE : return 'Updating' + waiting ;
case ISTATES . PENDING _BACKUP : return 'Backing up' + waiting ;
2019-09-22 01:01:47 -07:00
case ISTATES . PENDING _START : return 'Starting' + waiting ;
case ISTATES . PENDING _STOP : return 'Stopping' + waiting ;
2019-12-20 11:18:48 -08:00
case ISTATES . PENDING _RESTART : return 'Restarting' + waiting ;
2019-08-30 21:21:44 +02:00
case ISTATES . ERROR : {
2019-09-18 15:54:19 +02:00
if ( app . error && app . error . message === 'ETRYAGAIN' ) return 'DNS Error' ;
return 'Error' ;
2019-08-30 21:21:44 +02:00
}
2018-01-22 13:01:38 -08:00
case ISTATES . INSTALLED : {
2018-05-15 12:07:57 -07:00
if ( app . debugMode ) {
2019-12-20 11:53:06 -08:00
return 'Recovery Mode' ;
2022-09-13 11:44:54 +02:00
} else if ( app . runState === RSTATES . RUNNING ) {
2018-01-22 13:01:38 -08:00
if ( ! app . health ) return 'Starting...' ; // no data yet
2022-07-11 16:58:10 +02:00
if ( app . type === APP _TYPES . LINK ) return '' ;
2018-01-22 13:01:38 -08:00
if ( app . health === HSTATES . HEALTHY ) return 'Running' ;
return 'Not responding' ; // dead/exit/unhealthy
2022-09-13 11:44:54 +02:00
} else if ( app . runState === RSTATES . STOPPED ) return 'Stopped' ;
2018-01-22 13:01:38 -08:00
else return app . runState ;
}
default : return app . installationState ;
}
} ;
} ) ;
2019-09-23 23:28:14 -07:00
app . filter ( 'taskName' , function ( ) {
2019-11-23 17:49:13 -08:00
return function ( installationState ) {
switch ( installationState ) {
2019-09-23 23:28:14 -07:00
case ISTATES . PENDING _INSTALL : return 'install' ;
case ISTATES . PENDING _CLONE : return 'clone' ;
case ISTATES . PENDING _LOCATION _CHANGE : return 'location change' ;
case ISTATES . PENDING _CONFIGURE : return 'configure' ;
case ISTATES . PENDING _RECREATE _CONTAINER : return 'create container' ;
case ISTATES . PENDING _DEBUG : return 'debug' ;
case ISTATES . PENDING _RESIZE : return 'resize' ;
case ISTATES . PENDING _DATA _DIR _MIGRATION : return 'data migration' ;
case ISTATES . PENDING _UNINSTALL : return 'uninstall' ;
case ISTATES . PENDING _RESTORE : return 'restore' ;
2021-05-26 09:32:17 -07:00
case ISTATES . PENDING _IMPORT : return 'import' ;
2019-09-23 23:28:14 -07:00
case ISTATES . PENDING _UPDATE : return 'update' ;
case ISTATES . PENDING _BACKUP : return 'backup' ;
case ISTATES . PENDING _START : return 'start app' ;
case ISTATES . PENDING _STOP : return 'stop app' ;
2019-12-20 11:18:48 -08:00
case ISTATES . PENDING _RESTART : return 'restart app' ;
2019-11-23 17:49:13 -08:00
default : return installationState || '' ;
2019-09-23 23:28:14 -07:00
}
} ;
} ) ;
2019-09-23 23:51:15 -07:00
app . filter ( 'errorSuggestion' , function ( ) {
return function ( error ) {
2019-09-24 18:46:07 +02:00
if ( ! error ) return '' ;
2019-09-23 23:51:15 -07:00
switch ( error . reason ) {
2019-09-24 00:59:12 -07:00
case ERROR . ACCESS _DENIED :
if ( error . domain ) return 'Check the DNS credentials of ' + error . domain . domain + ' in the Domains & Certs view' ;
return '' ;
case ERROR . COLLECTD _ERROR : return 'Check if collectd is running on the server' ;
case ERROR . DATABASE _ERROR : return 'Check if MySQL database is running on the server' ;
case ERROR . DOCKER _ERROR : return 'Check if docker is running on the server' ;
case ERROR . DNS _ERROR : return 'Check if the DNS service of the domain is running' ;
case ERROR . LOGROTATE _ERROR : return 'Check if logrotate is running on the server' ;
case ERROR . NETWORK _ERROR : return 'Check if there are any network issues on the server' ;
case ERROR . REVERSEPROXY _ERROR : return 'Check if nginx is running on the server' ;
2019-09-23 23:51:15 -07:00
default : return '' ;
}
} ;
} ) ;
2024-07-15 09:48:04 +02:00
app . filter ( 'canUpdate' , function ( ) {
2018-01-22 13:01:38 -08:00
return function ( apps ) {
return apps . every ( function ( app ) {
return ( app . installationState === ISTATES . ERROR ) || ( app . installationState === ISTATES . INSTALLED ) ;
} ) ;
} ;
} ) ;
app . filter ( 'inProgressApps' , function ( ) {
return function ( apps ) {
return apps . filter ( function ( app ) {
return app . installationState !== ISTATES . ERROR && app . installationState !== ISTATES . INSTALLED ;
} ) ;
} ;
} ) ;
app . filter ( 'prettyHref' , function ( ) {
return function ( input ) {
if ( ! input ) return input ;
if ( input . indexOf ( 'http://' ) === 0 ) return input . slice ( 'http://' . length ) ;
if ( input . indexOf ( 'https://' ) === 0 ) return input . slice ( 'https://' . length ) ;
return input ;
} ;
} ) ;
2020-02-12 15:37:05 +01:00
app . filter ( 'prettyEmailAddresses' , function ( ) {
return function prettyEmailAddresses ( addresses ) {
2021-10-08 20:34:06 -07:00
if ( ! addresses ) return '' ;
if ( addresses === '<>' ) return '<>' ;
2020-02-12 15:37:05 +01:00
if ( Array . isArray ( addresses ) ) return addresses . map ( function ( a ) { return a . slice ( 1 , - 1 ) ; } ) . join ( ', ' ) ;
return addresses . slice ( 1 , - 1 ) ;
} ;
} ) ;
2018-01-22 13:01:38 -08:00
// custom directive for dynamic names in forms
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
app . directive ( 'laterName' , function ( ) { // (2)
return {
restrict : 'A' ,
require : [ '?ngModel' , '^?form' ] , // (3)
link : function postLink ( scope , elem , attrs , ctrls ) {
attrs . $set ( 'name' , attrs . laterName ) ;
var modelCtrl = ctrls [ 0 ] ; // (3)
var formCtrl = ctrls [ 1 ] ; // (3)
if ( modelCtrl && formCtrl ) {
modelCtrl . $name = attrs . name ; // (4)
formCtrl . $addControl ( modelCtrl ) ; // (2)
scope . $on ( '$destroy' , function ( ) {
formCtrl . $removeControl ( modelCtrl ) ; // (5)
} ) ;
}
}
} ;
} ) ;
app . run ( [ '$route' , '$rootScope' , '$location' , function ( $route , $rootScope , $location ) {
var original = $location . path ;
$location . path = function ( path , reload ) {
if ( reload === false ) {
var lastRoute = $route . current ;
var un = $rootScope . $on ( '$locationChangeSuccess' , function ( ) {
$route . current = lastRoute ;
un ( ) ;
} ) ;
}
return original . apply ( $location , [ path ] ) ;
} ;
} ] ) ;
app . directive ( 'ngClickSelect' , function ( ) {
return {
restrict : 'AC' ,
2019-01-22 10:54:03 +01:00
link : function ( scope , element /*, attrs */ ) {
2018-01-22 13:01:38 -08:00
element . bind ( 'click' , function ( ) {
var selection = window . getSelection ( ) ;
var range = document . createRange ( ) ;
range . selectNodeContents ( this ) ;
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
} ) ;
}
} ;
} ) ;
2024-06-20 15:47:49 +02:00
// handles various states and triggers a href or configure/repair view
// used by attaching to controller $scope
// if $scope.appPostInstallConfirm.show(app); exists it will be called if not yet confirmed
function onAppClick ( app , $event , isOperator , $scope ) {
function stopEvent ( ) {
$event . originalEvent . stopPropagation ( ) ;
$event . originalEvent . preventDefault ( ) ;
}
if ( app . installationState !== ISTATES . INSTALLED ) {
if ( app . installationState === ISTATES . ERROR && isOperator ) $scope . showAppConfigure ( app , 'repair' ) ;
return stopEvent ( ) ;
}
// app.health can also be null to indicate insufficient data
if ( ! app . health ) return stopEvent ( ) ;
if ( app . runState === RSTATES . STOPPED ) return stopEvent ( ) ;
if ( app . runState === RSTATES . STOPPED ) return stopEvent ( ) ;
if ( app . health === HSTATES . UNHEALTHY || app . health === HSTATES . ERROR || app . health === HSTATES . DEAD ) {
if ( isOperator ) $scope . showAppConfigure ( app , 'repair' ) ;
return stopEvent ( ) ;
}
if ( app . pendingPostInstallConfirmation && $scope . appPostInstallConfirm ) {
$scope . appPostInstallConfirm . show ( app ) ;
return stopEvent ( ) ;
}
}
2018-01-22 13:01:38 -08:00
app . directive ( 'ngClickReveal' , function ( ) {
return {
restrict : 'A' ,
link : function ( scope , element , attrs ) {
element . addClass ( 'hand' ) ;
var value = '' ;
scope . $watch ( attrs . ngClickReveal , function ( newValue , oldValue ) {
if ( newValue !== oldValue ) {
element . html ( '<i>hidden</i>' ) ;
value = newValue ;
}
} ) ;
element . bind ( 'click' , function ( ) {
element . text ( value ) ;
} ) ;
}
} ;
} ) ;
// https://codepen.io/webmatze/pen/isuHh
app . directive ( 'tagInput' , function ( ) {
return {
restrict : 'E' ,
scope : {
inputTags : '=taglist'
} ,
2020-03-25 06:13:44 +01:00
require : '^form' ,
link : function ( $scope , element , attrs , formCtrl ) {
2018-01-22 13:01:38 -08:00
$scope . defaultWidth = 200 ;
$scope . tagText = '' ; // current tag being edited
$scope . placeholder = attrs . placeholder ;
$scope . tagArray = function ( ) {
if ( $scope . inputTags === undefined ) {
return [ ] ;
}
2020-03-28 16:46:06 -07:00
return $scope . inputTags . split ( ' ' ) . filter ( function ( tag ) {
2018-01-22 13:01:38 -08:00
return tag !== '' ;
} ) ;
} ;
$scope . addTag = function ( ) {
2020-03-25 06:51:39 +01:00
var tagArray = $scope . tagArray ( ) ;
// prevent adding empty or existing items
if ( $scope . tagText . length === 0 || tagArray . indexOf ( $scope . tagText ) !== - 1 ) {
return $scope . tagText = '' ;
2018-01-22 13:01:38 -08:00
}
2020-03-25 06:51:39 +01:00
2018-01-22 13:01:38 -08:00
tagArray . push ( $scope . tagText ) ;
2020-03-28 16:46:06 -07:00
$scope . inputTags = tagArray . join ( ' ' ) ;
2018-01-22 13:01:38 -08:00
return $scope . tagText = '' ;
} ;
$scope . deleteTag = function ( key ) {
var tagArray ;
tagArray = $scope . tagArray ( ) ;
if ( tagArray . length > 0 && $scope . tagText . length === 0 && key === undefined ) {
tagArray . pop ( ) ;
} else {
if ( key !== undefined ) {
tagArray . splice ( key , 1 ) ;
}
}
2020-03-25 06:13:44 +01:00
formCtrl . $setDirty ( ) ;
2020-03-28 16:46:06 -07:00
return $scope . inputTags = tagArray . join ( ' ' ) ;
2018-01-22 13:01:38 -08:00
} ;
$scope . $watch ( 'tagText' , function ( newVal , oldVal ) {
var tempEl ;
if ( ! ( newVal === oldVal && newVal === undefined ) ) {
tempEl = $ ( '<span>' + newVal + '</span>' ) . appendTo ( 'body' ) ;
$scope . inputWidth = tempEl . width ( ) + 5 ;
if ( $scope . inputWidth < $scope . defaultWidth ) {
$scope . inputWidth = $scope . defaultWidth ;
}
return tempEl . remove ( ) ;
}
} ) ;
2020-03-25 06:31:31 +01:00
element . bind ( 'click' , function ( ) {
element [ 0 ] . firstChild . lastChild . focus ( ) ;
} ) ;
2018-01-22 13:01:38 -08:00
element . bind ( 'keydown' , function ( e ) {
var key = e . which ;
if ( key === 9 || key === 13 ) {
e . preventDefault ( ) ;
}
if ( key === 8 ) {
return $scope . $apply ( 'deleteTag()' ) ;
}
} ) ;
element . bind ( 'keyup' , function ( e ) {
var key = e . which ;
2020-03-28 16:46:06 -07:00
if ( key === 9 || key === 13 || key === 32 ) {
2018-01-22 13:01:38 -08:00
e . preventDefault ( ) ;
return $scope . $apply ( 'addTag()' ) ;
}
} ) ;
} ,
template :
'<div class="tag-input-container">' +
2020-03-25 06:48:45 +01:00
'<div class="btn-group input-tag" data-ng-repeat="tag in tagArray()">' +
'<button type="button" class="btn btn-xs btn-primary" disabled>{{ tag }}</button>' +
'<button type="button" class="btn btn-xs btn-primary" data-ng-click="deleteTag($index)">×</button>' +
2018-01-22 13:01:38 -08:00
'</div>' +
'<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
'</div>'
} ;
} ) ;
app . config ( [ 'fitTextConfigProvider' , function ( fitTextConfigProvider ) {
fitTextConfigProvider . config = {
loadDelay : 250 ,
compressor : 0.9 ,
min : 8 ,
max : 24
} ;
} ] ) ;
2023-08-11 12:25:40 +02:00
app . controller ( 'MainController' , [ '$scope' , '$route' , '$timeout' , '$location' , '$interval' , 'Notification' , 'Client' , function ( $scope , $route , $timeout , $location , $interval , Notification , Client ) {
$scope . initialized = false ; // used to animate the UI
$scope . user = Client . getUserInfo ( ) ;
$scope . installedApps = Client . getInstalledApps ( ) ;
$scope . config = { } ;
$scope . client = Client ;
$scope . subscription = { } ;
$scope . notificationCount = 0 ;
$scope . hideNavBarActions = $location . path ( ) === '/logs' ;
$scope . backgroundImageUrl = '' ;
2025-01-02 16:53:20 +01:00
$scope . sideBarOpen = false ;
2023-08-11 12:25:40 +02:00
2024-10-15 13:16:50 +02:00
$scope . closeNavbar = function ( ) {
2025-01-02 16:53:20 +01:00
$scope . sideBarOpen = false ;
2024-10-15 13:16:50 +02:00
} ;
2025-01-02 13:37:35 +01:00
$scope . onSidebarToggle = function ( ) {
2025-01-02 16:53:20 +01:00
$scope . sideBarOpen = ! $scope . sideBarOpen ;
2025-01-02 13:37:35 +01:00
} ;
2023-08-11 12:25:40 +02:00
$scope . reboot = {
busy : false ,
show : function ( ) {
$scope . reboot . busy = false ;
$ ( '#rebootModal' ) . modal ( 'show' ) ;
} ,
submit : function ( ) {
$scope . reboot . busy = true ;
Client . reboot ( function ( error ) {
if ( error ) return Client . error ( error ) ;
$ ( '#rebootModal' ) . modal ( 'hide' ) ;
// trigger refetch to show offline banner
$timeout ( function ( ) { Client . getStatus ( function ( ) { } ) ; } , 5000 ) ;
} ) ;
}
} ;
$scope . isActive = function ( url ) {
if ( ! $route . current ) return false ;
2024-12-15 12:16:09 +01:00
return $route . current . $$route . originalPath === url ;
2023-08-11 12:25:40 +02:00
} ;
$scope . logout = function ( event ) {
event . stopPropagation ( ) ;
$scope . initialized = false ;
Client . logout ( ) ;
} ;
$scope . openSubscriptionSetup = function ( ) {
Client . openSubscriptionSetup ( $scope . subscription ) ;
} ;
// NOTE: this function is exported and called from the appstore.js
$scope . updateSubscriptionStatus = function ( ) {
Client . getSubscription ( function ( error , subscription ) {
if ( error && error . statusCode === 412 ) return ; // not yet registered
if ( error && error . statusCode === 402 ) return ; // invalid appstore token
if ( error ) return console . error ( error ) ;
$scope . subscription = subscription ;
} ) ;
} ;
function refreshNotifications ( ) {
if ( ! Client . getUserInfo ( ) . isAtLeastAdmin ) return ;
Client . getNotifications ( { acknowledged : false } , 1 , 100 , function ( error , results ) { // counter maxes out at 100
if ( error ) console . error ( error ) ;
else $scope . notificationCount = results . length ;
} ) ;
}
// update state of acknowledged notification
$scope . notificationAcknowledged = function ( ) {
refreshNotifications ( ) ;
} ;
function redirectOnMandatory2FA ( ) {
2024-05-25 12:54:40 +02:00
if ( Client . getConfig ( ) . mandatory2FA ) {
if ( Client . getUserInfo ( ) . twoFactorAuthenticationEnabled ) return ; // user already has 2fa
if ( Client . getUserInfo ( ) . source && $scope . config . external2FA ) return ; // 2fa is external
2023-08-11 12:25:40 +02:00
$location . path ( '/profile' ) . search ( { setup2fa : true } ) ;
}
}
// Make it redirect if the browser URL is changed directly - https://forum.cloudron.io/topic/7510/bug-in-2fa-force
$scope . $on ( '$routeChangeStart' , function ( /* event */ ) {
if ( $scope . initialized ) redirectOnMandatory2FA ( ) ;
} ) ;
var gPlatformStatusNotification = null ;
function trackPlatformStatus ( ) {
Client . getPlatformStatus ( function ( error , result ) {
if ( error ) return console . error ( 'Failed to get platform status.' , error ) ;
// see box/src/platform.js
if ( result . message === 'Ready' ) {
if ( gPlatformStatusNotification ) {
gPlatformStatusNotification . kill ( ) ;
gPlatformStatusNotification = null ;
}
return ;
}
if ( ! gPlatformStatusNotification ) {
var options = { title : 'Platform status' , message : result . message , delay : 'notimeout' , replaceMessage : true , closeOnClick : false } ;
Notification . primary ( options ) . then ( function ( result ) {
gPlatformStatusNotification = result ;
$timeout ( trackPlatformStatus , 5000 ) ;
} ) ;
} else {
gPlatformStatusNotification . message = result . message ;
$timeout ( trackPlatformStatus , 5000 ) ;
}
} ) ;
}
// this loads the very first thing when accessing via IP or domain
function init ( ) {
Client . getProvisionStatus ( function ( error , status ) {
if ( error ) return Client . initError ( error , init ) ;
2024-07-15 17:30:50 +02:00
if ( redirectIfNeeded ( status , 'dashboard' ) ) return ; // we got redirected...
2023-08-11 12:25:40 +02:00
// check version and force reload if needed
if ( ! localStorage . version ) {
localStorage . version = status . version ;
} else if ( localStorage . version !== status . version ) {
localStorage . version = status . version ;
window . location . reload ( true ) ;
}
console . log ( 'Running dashboard version ' , localStorage . version ) ;
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
2024-02-26 11:56:31 +01:00
async . series ( [
2024-02-26 12:33:52 +01:00
Client . refreshProfile . bind ( Client ) ,
2024-02-26 11:56:31 +01:00
Client . refreshConfig . bind ( Client ) ,
Client . refreshAvailableLanguages . bind ( Client ) ,
Client . refreshInstalledApps . bind ( Client )
] , function ( error ) {
2023-08-11 12:25:40 +02:00
if ( error ) return Client . initError ( error , init ) ;
2024-02-26 11:56:31 +01:00
// now mark the Client to be ready
Client . setReady ( ) ;
2023-08-11 12:25:40 +02:00
2024-02-26 11:56:31 +01:00
$scope . config = Client . getConfig ( ) ;
2023-08-11 12:25:40 +02:00
2024-04-06 10:00:16 +02:00
if ( Client . getUserInfo ( ) . hasBackgroundImage ) {
document . getElementById ( 'mainContentContainer' ) . style . backgroundImage = 'url("' + Client . getBackgroundImageUrl ( ) + '")' ;
document . getElementById ( 'mainContentContainer' ) . classList . add ( 'has-background' ) ;
}
2024-02-26 11:56:31 +01:00
$scope . initialized = true ;
2023-08-11 12:25:40 +02:00
2024-02-26 11:56:31 +01:00
redirectOnMandatory2FA ( ) ;
2023-08-11 12:25:40 +02:00
2024-02-26 11:56:31 +01:00
$interval ( refreshNotifications , 60 * 1000 ) ;
refreshNotifications ( ) ;
2023-08-11 12:25:40 +02:00
2024-02-26 11:56:31 +01:00
Client . getSubscription ( function ( error , subscription ) {
if ( error && error . statusCode === 412 ) return ; // not yet registered
if ( error && error . statusCode === 402 ) return ; // invalid appstore token
if ( error ) return console . error ( error ) ;
2023-08-11 12:25:40 +02:00
2024-02-26 11:56:31 +01:00
$scope . subscription = subscription ;
2023-08-11 12:25:40 +02:00
2024-02-26 11:56:31 +01:00
// only track platform status if we are registered
trackPlatformStatus ( ) ;
2023-08-11 12:25:40 +02:00
} ) ;
} ) ;
} ) ;
}
Client . onConfig ( function ( config ) {
if ( config . cloudronName ) {
document . title = config . cloudronName ;
}
} ) ;
init ( ) ;
// setup all the dialog focus handling
[ 'updateModal' ] . forEach ( function ( id ) {
$ ( '#' + id ) . on ( 'shown.bs.modal' , function ( ) {
$ ( this ) . find ( '[autofocus]:first' ) . focus ( ) ;
} ) ;
} ) ;
} ] ) ;