2018-11-15 19:59:24 +01:00
'use strict' ;
2024-10-04 17:43:45 +02:00
/* global angular, moment, $, Chart, TASK_TYPES */
2018-11-16 17:21:57 +01:00
2020-02-25 14:54:29 -08:00
angular . module ( 'Application' ) . controller ( 'SystemController' , [ '$scope' , '$location' , '$timeout' , 'Client' , function ( $scope , $location , $timeout , Client ) {
2020-02-24 12:56:13 +01:00
Client . onReady ( function ( ) { if ( ! Client . getUserInfo ( ) . isAtLeastAdmin ) $location . path ( '/' ) ; } ) ;
2018-11-15 19:59:24 +01:00
2018-11-20 16:53:42 +01:00
$scope . config = Client . getConfig ( ) ;
2020-01-28 09:37:25 -08:00
$scope . memory = null ;
2023-12-04 00:31:38 +01:00
$scope . cpus = null ;
$scope . info = null ;
2021-01-04 15:14:07 -08:00
$scope . volumesById = { } ;
2018-11-15 19:59:24 +01:00
2022-10-14 00:18:57 +02:00
// https://stackoverflow.com/questions/1484506/random-color-generator
function rainbow ( numOfSteps , step ) {
// This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distinguishable vibrant markers in Google Maps and other apps.
// Adam Cole, 2011-Sept-14
// HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
var r , g , b ;
var h = step / numOfSteps ;
var i = ~ ~ ( h * 6 ) ;
var f = h * 6 - i ;
var q = 1 - f ;
switch ( i % 6 ) {
2022-11-09 14:35:05 +01:00
case 0 : r = 1 ; g = f ; b = 0 ; break ;
case 1 : r = q ; g = 1 ; b = 0 ; break ;
case 2 : r = 0 ; g = 1 ; b = f ; break ;
case 3 : r = 0 ; g = q ; b = 1 ; break ;
case 4 : r = f ; g = 0 ; b = 1 ; break ;
case 5 : r = 1 ; g = 0 ; b = q ; break ;
2020-03-05 18:26:58 -08:00
}
2022-11-09 14:35:05 +01:00
var c = '#' + ( '00' + ( ~ ~ ( r * 255 ) ) . toString ( 16 ) ) . slice ( - 2 ) + ( '00' + ( ~ ~ ( g * 255 ) ) . toString ( 16 ) ) . slice ( - 2 ) + ( '00' + ( ~ ~ ( b * 255 ) ) . toString ( 16 ) ) . slice ( - 2 ) ;
2022-10-14 00:18:57 +02:00
return ( c ) ;
}
2022-10-14 01:03:00 +02:00
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
function shuffle ( a ) {
for ( let i = a . length - 1 ; i > 0 ; i -- ) {
const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
[ a [ i ] , a [ j ] ] = [ a [ j ] , a [ i ] ] ;
}
return a ;
2020-03-05 18:26:58 -08:00
}
var colorIndex = 0 ;
2022-10-14 01:03:00 +02:00
var colors = [ ] ;
function resetColors ( n ) {
colorIndex = 0 ;
colors = [ ] ;
for ( var i = 0 ; i < n ; i ++ ) colors . push ( rainbow ( n , i ) ) ;
shuffle ( colors ) ;
}
2020-03-05 18:26:58 -08:00
function getNextColor ( ) {
2022-10-14 01:03:00 +02:00
return colors [ colorIndex ++ ] ;
2020-03-05 18:26:58 -08:00
}
2022-10-12 16:00:41 +02:00
$scope . disks = {
busy : true ,
2022-10-12 17:05:45 +02:00
busyRefresh : false ,
ts : 0 ,
2022-11-09 15:04:21 +01:00
taskId : '' ,
2022-10-12 16:00:41 +02:00
disks : [ ] ,
2020-03-05 18:26:58 -08:00
2022-10-12 17:05:45 +02:00
show : function ( ) {
Client . diskUsage ( function ( error , result ) {
2022-10-12 16:26:09 +02:00
if ( error ) return console . error ( 'Failed to refresh disk usage.' , error ) ;
2022-09-15 14:43:00 +02:00
2022-10-13 02:19:38 +02:00
if ( ! result . usage ) {
$scope . disks . busy = false ;
return ;
}
2022-10-12 17:05:45 +02:00
$scope . disks . ts = result . usage . ts ;
2022-10-12 16:26:09 +02:00
// [ { filesystem, type, size, used, available, capacity, mountpoint }]
2024-11-30 11:46:28 +01:00
$scope . disks . disks = Object . keys ( result . usage . filesystems ) . map ( function ( k ) { return result . usage . filesystems [ k ] ; } ) ; // convert object to array...
2022-09-15 14:43:00 +02:00
2022-10-12 16:26:09 +02:00
$scope . disks . disks . forEach ( function ( disk ) {
2022-10-13 23:30:55 +02:00
var usageOther = disk . used ;
2022-09-15 14:43:00 +02:00
2022-10-14 01:03:00 +02:00
resetColors ( disk . contents . length ) ;
2022-11-04 20:53:07 +01:00
2022-11-04 22:47:51 +01:00
// if this disk is a volume amend it and remove it from contents
disk . contents . forEach ( function ( content ) { if ( content . path === disk . mountpoint ) disk . volume = $scope . volumesById [ content . id ] ; } ) ;
2022-11-04 20:53:07 +01:00
disk . contents = disk . contents . filter ( function ( content ) { return content . path !== disk . mountpoint ; } ) ;
2023-08-01 18:35:11 +05:30
// only show old backups if the size is significant
disk . contents = disk . contents . filter ( function ( content ) { return content . id !== 'cloudron-backup-default' || content . usage > 1024 * 1024 * 1024 ; } ) ;
2022-10-12 16:26:09 +02:00
disk . contents . forEach ( function ( content ) {
content . color = getNextColor ( ) ;
2020-04-18 22:48:09 -07:00
2022-10-14 12:31:45 +02:00
if ( content . type === 'app' ) {
content . app = Client . getInstalledAppsByAppId ( ) [ content . id ] ;
if ( ! content . app ) content . uninstalled = true ;
2024-01-03 18:49:49 +01:00
else content . label = content . app . label || content . app . fqdn ;
2023-08-01 18:35:11 +05:30
} else if ( content . type === 'volume' ) {
content . volume = $scope . volumesById [ content . id ] ;
2024-05-22 11:39:19 +02:00
content . label = content . volume ? content . volume . name : 'Removed volume' ;
2022-10-14 12:31:45 +02:00
}
2020-05-13 21:18:34 +02:00
2023-12-14 13:01:59 +01:00
// ensure a label for ui
content . label = content . label || content . id ;
2022-10-12 16:26:09 +02:00
usageOther -= content . usage ;
2022-09-16 17:09:54 +02:00
} ) ;
2020-05-13 23:34:14 +02:00
2022-10-12 16:26:09 +02:00
disk . contents . sort ( function ( x , y ) { return y . usage - x . usage ; } ) ; // sort by usage
if ( $scope . disks . disks [ 0 ] === disk ) { // the root mount point is the first disk. keep this 'contains' in the end
disk . contents . push ( {
type : 'standard' ,
2022-11-22 12:18:05 +01:00
label : 'Everything else (Ubuntu, etc)' ,
2022-10-12 16:26:09 +02:00
id : 'other' ,
2022-10-14 01:03:00 +02:00
color : '#555555' ,
2022-10-12 16:26:09 +02:00
usage : usageOther
} ) ;
} else {
disk . contents . push ( {
type : 'standard' ,
label : 'Used' ,
id : 'other' ,
2022-10-14 01:03:00 +02:00
color : '#555555' ,
2022-10-12 16:26:09 +02:00
usage : usageOther
} ) ;
}
} ) ;
$scope . disks . busy = false ;
} ) ;
2022-10-12 17:05:45 +02:00
} ,
2022-11-09 15:04:21 +01:00
checkStatus : function ( ) {
2023-01-25 10:06:35 +01:00
Client . getLatestTaskByType ( TASK _TYPES . TASK _UPDATE _DISK _USAGE , function ( error , task ) {
2022-11-09 15:04:21 +01:00
if ( error ) return console . error ( error ) ;
if ( ! task ) return ;
$scope . disks . taskId = task . id ;
$scope . disks . busyRefresh = true ;
$scope . disks . updateStatus ( ) ;
} ) ;
} ,
updateStatus : function ( ) {
Client . getTask ( $scope . disks . taskId , function ( error , data ) {
if ( error ) return $timeout ( $scope . disks . updateStatus , 3000 ) ;
if ( ! data . active ) {
$scope . disks . busyRefresh = false ;
$scope . disks . taskId = '' ;
$scope . disks . show ( ) ;
return ;
}
$timeout ( $scope . disks . updateStatus , 3000 ) ;
} ) ;
} ,
2022-10-12 17:05:45 +02:00
refresh : function ( ) {
$scope . disks . busyRefresh = true ;
2022-11-09 15:04:21 +01:00
Client . refreshDiskUsage ( function ( error , taskId ) {
if ( error ) {
$scope . disks . busyRefresh = false ;
return console . error ( 'Failed to refresh disk usage.' , error ) ;
}
2022-10-12 17:05:45 +02:00
2022-11-09 15:04:21 +01:00
$scope . disks . taskId = taskId ;
$timeout ( $scope . disks . updateStatus , 3000 ) ;
2022-10-12 17:05:45 +02:00
} ) ;
2022-10-12 16:00:41 +02:00
}
} ;
2020-05-13 21:18:34 +02:00
2022-10-12 16:00:41 +02:00
$scope . graphs = {
busy : false ,
period : 6 ,
memoryChart : null ,
diskChart : null ,
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
setPeriod : function ( hours ) {
$scope . graphs . period = hours ;
$scope . graphs . refresh ( ) ;
} ,
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
refresh : function ( ) {
$scope . graphs . busy = true ;
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
Client . getSystemGraphs ( $scope . graphs . period * 60 , function ( error , result ) {
if ( error ) return console . error ( 'Failed to fetch system graphs:' , error ) ;
2020-05-13 23:34:14 +02:00
2022-10-13 23:05:10 +02:00
var cpuCount = result . cpuCount ;
2022-10-12 16:00:41 +02:00
// in minutes
var timePeriod = $scope . graphs . period * 60 ;
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
// keep in sync with graphs.js
var timeBucketSizeMinutes = timePeriod > ( 24 * 60 ) ? ( 6 * 60 ) : 5 ;
var steps = Math . floor ( timePeriod / timeBucketSizeMinutes ) ;
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
var labels = new Array ( steps ) . fill ( 0 ) ;
labels = labels . map ( function ( v , index ) {
var dateTime = new Date ( Date . now ( ) - ( ( timePeriod - ( index * timeBucketSizeMinutes ) ) * 60 * 1000 ) ) ;
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
if ( $scope . graphs . period > 24 ) {
return dateTime . toLocaleDateString ( ) ;
} else {
return dateTime . toLocaleTimeString ( ) ;
}
} ) ;
2022-10-11 21:22:56 +02:00
2022-10-13 22:16:35 +02:00
function fillGraph ( canvasId , contents , chartPropertyName , divisor , max , format , formatDivisor ) {
2022-10-12 16:00:41 +02:00
if ( ! contents || ! contents [ 0 ] ) return ; // no data available yet
var datasets = [ ] ;
2022-10-14 01:03:00 +02:00
resetColors ( contents . length ) ;
2022-10-12 16:00:41 +02:00
contents . forEach ( function ( content , index ) {
// fill holes with previous value
var cur = 0 ;
2022-10-14 11:28:00 +02:00
content . data . forEach ( function ( d ) {
2022-10-12 16:00:41 +02:00
if ( d [ 0 ] === null ) d [ 0 ] = cur ;
else cur = d [ 0 ] ;
} ) ;
var datapoints = Array ( steps ) . map ( function ( ) { return '0' ; } ) ;
// walk backwards and fill up the datapoints
2022-10-14 11:28:00 +02:00
content . data . reverse ( ) . forEach ( function ( d , index ) {
2022-10-12 16:00:41 +02:00
datapoints [ datapoints . length - 1 - index ] = ( d [ 0 ] / divisor ) . toFixed ( 2 ) ;
} ) ;
2022-10-14 01:03:00 +02:00
var color = index === 0 ? '#2196F3' : getNextColor ( ) ;
2022-10-12 16:00:41 +02:00
datasets . push ( {
label : content . label ,
backgroundColor : color + '4F' ,
borderColor : color , // FIXME give real distinct colors
borderWidth : 1 ,
radius : 0 ,
data : datapoints ,
cubicInterpolationMode : 'monotone' ,
tension : 0.4
} ) ;
2022-10-11 21:22:56 +02:00
} ) ;
2022-09-15 12:12:32 +02:00
2022-10-12 16:00:41 +02:00
var graphData = {
labels : labels ,
datasets : datasets
} ;
var options = {
responsive : true ,
maintainAspectRatio : true ,
aspectRatio : 2.5 ,
animation : false ,
plugins : {
legend : {
display : false
}
} ,
interaction : {
intersect : false ,
mode : 'index' ,
2022-10-11 21:22:56 +02:00
} ,
2022-10-12 16:00:41 +02:00
scales : {
x : {
ticks : { autoSkipPadding : 50 , maxRotation : 0 }
} ,
y : {
2022-10-13 22:16:35 +02:00
ticks : { maxTicksLimit : 6 } ,
2022-10-12 16:00:41 +02:00
min : 0 ,
beginAtZero : true
}
2022-10-11 21:22:56 +02:00
}
2022-10-12 16:00:41 +02:00
} ;
2020-05-13 21:18:34 +02:00
2022-10-13 22:16:35 +02:00
if ( format ) options . scales . y . ticks . callback = function ( value ) { return ( formatDivisor ? ( value / formatDivisor ) . toFixed ( 0 ) : value ) + ' ' + format ; } ;
2022-10-12 16:00:41 +02:00
if ( max ) options . scales . y . max = max ;
2022-10-11 21:22:56 +02:00
2022-10-12 16:00:41 +02:00
var ctx = $ ( canvasId ) . get ( 0 ) . getContext ( '2d' ) ;
2020-05-13 21:18:34 +02:00
2022-10-12 16:00:41 +02:00
if ( $scope . graphs [ chartPropertyName ] ) $scope . graphs [ chartPropertyName ] . destroy ( ) ;
$scope . graphs [ chartPropertyName ] = new Chart ( ctx , { type : 'line' , data : graphData , options : options } ) ;
}
2020-05-13 21:18:34 +02:00
2022-10-13 01:36:29 +02:00
var cpuThreshold = 20 ;
var appsWithHighCPU = Object . keys ( result . apps ) . map ( function ( appId ) {
result . apps [ appId ] . id = appId ;
var app = Client . getInstalledAppsByAppId ( ) [ appId ] ;
if ( ! app ) result . apps [ appId ] . label = appId ;
else result . apps [ appId ] . label = app . label || app . fqdn ;
return result . apps [ appId ] ;
} ) . filter ( function ( app ) {
if ( ! app . cpu ) return false ; // not sure why we get empty objects
2022-10-14 11:28:00 +02:00
return app . cpu . some ( function ( d ) { return d [ 0 ] > cpuThreshold ; } ) ;
2022-10-13 01:36:29 +02:00
} ) . map ( function ( app ) {
return { data : app . cpu , label : app . label } ;
} ) ;
var memoryThreshold = 1024 * 1024 * 1024 ;
2022-10-12 23:25:46 +02:00
var appsWithHighMemory = Object . keys ( result . apps ) . map ( function ( appId ) {
result . apps [ appId ] . id = appId ;
var app = Client . getInstalledAppsByAppId ( ) [ appId ] ;
if ( ! app ) result . apps [ appId ] . label = appId ;
else result . apps [ appId ] . label = app . label || app . fqdn ;
return result . apps [ appId ] ;
} ) . filter ( function ( app ) {
if ( ! app . memory ) return false ; // not sure why we get empty objects
2022-10-14 11:28:00 +02:00
return app . memory . some ( function ( d ) { return d [ 0 ] > memoryThreshold ; } ) ;
2022-10-12 23:25:46 +02:00
} ) . map ( function ( app ) {
return { data : app . memory , label : app . label } ;
} ) ;
2020-05-13 21:18:34 +02:00
2022-10-13 23:05:10 +02:00
fillGraph ( '#graphsCPUChart' , [ { data : result . cpu , label : 'CPU' } ] . concat ( appsWithHighCPU ) , 'cpuChart' , 1 , cpuCount * 100 , '%' ) ;
2022-10-13 22:16:35 +02:00
fillGraph ( '#graphsSystemMemoryChart' , [ { data : result . memory , label : 'Memory' } ] . concat ( appsWithHighMemory ) , 'memoryChart' , 1024 * 1024 , Number . parseInt ( $scope . memory . memory / 1024 / 1024 ) , 'GiB' , 1024 ) ;
2020-05-13 21:18:34 +02:00
2022-10-12 16:00:41 +02:00
$scope . graphs . busy = false ;
} ) ;
}
2022-09-16 17:09:54 +02:00
} ;
2021-01-04 15:14:07 -08:00
2018-11-18 20:01:53 +01:00
Client . onReady ( function ( ) {
2023-12-04 00:31:38 +01:00
Client . cpus ( function ( error , cpus ) {
if ( error ) console . error ( error ) ;
$scope . cpus = cpus ;
} ) ;
Client . systemInfo ( function ( error , info ) {
if ( error ) console . error ( error ) ;
// prettify for UI
info . uptimeSecs = moment . duration ( info . uptimeSecs , 'seconds' ) . locale ( navigator . language ) . humanize ( ) ;
$scope . info = info ;
} ) ;
2020-03-05 18:26:58 -08:00
Client . memory ( function ( error , memory ) {
2018-11-26 09:24:58 +01:00
if ( error ) console . error ( error ) ;
2018-11-18 20:01:53 +01:00
2020-03-05 18:26:58 -08:00
$scope . memory = memory ;
2018-11-26 09:24:58 +01:00
2022-09-16 17:09:54 +02:00
Client . getVolumes ( function ( error , volumes ) {
if ( error ) return console . error ( error ) ;
$scope . volumesById = { } ;
volumes . forEach ( function ( v ) { $scope . volumesById [ v . id ] = v ; } ) ;
2021-01-04 15:14:07 -08:00
2022-10-12 16:00:41 +02:00
$scope . graphs . refresh ( ) ;
2022-11-09 15:04:21 +01:00
2022-10-12 17:05:45 +02:00
$scope . disks . show ( ) ;
2022-11-09 15:04:21 +01:00
$scope . disks . checkStatus ( ) ;
2021-01-04 15:14:07 -08:00
} ) ;
2018-11-18 20:01:53 +01:00
} ) ;
} ) ;
2020-03-17 22:09:34 -07:00
Client . onReconnect ( function ( ) {
$scope . reboot . busy = false ;
} ) ;
2018-11-15 19:59:24 +01:00
} ] ) ;