diff --git a/src/views/graphs.html b/src/views/graphs.html index 3d0d92e10..258da9dbe 100644 --- a/src/views/graphs.html +++ b/src/views/graphs.html @@ -39,30 +39,4 @@ - -
- -
-

Disk Usage

-
- -
-
-
-

{{ disk.filesystem }} mounted at {{ disk.mountpoint }} {{ disk.available | prettyDiskSize }} of {{ disk.size | prettyDiskSize }} still available

-
-
-
-
-

This {{ disk.type }} disk contains:

- -
-
-
- diff --git a/src/views/graphs.js b/src/views/graphs.js index f29a9e9a0..29cf71321 100644 --- a/src/views/graphs.js +++ b/src/views/graphs.js @@ -11,7 +11,6 @@ angular.module('Application').controller('GraphsController', ['$scope', '$locati $scope.memoryUsageSystem = []; $scope.memoryUsageApps = []; $scope.activeApp = null; - $scope.disks = []; $scope.memory = null; $scope.errorMessage = ''; @@ -112,89 +111,6 @@ angular.module('Application').controller('GraphsController', ['$scope', '$locati }); }; - $scope.updateDiskGraphs = function () { - // https://graphite.readthedocs.io/en/latest/render_api.html#paths-and-wildcards - // on scaleway, for some reason docker devices are collected as part of collectd - // until we figure why just hardcode popular disk devices - https://www.mjmwired.net/kernel/Documentation/devices.txt - Client.disks(function (error, result) { - if (error) return $scope.setError('disk', error); - - result.disks.forEach(function (disk, index) { - disk.id = index; - disk.contains = []; - - if (disk.filesystem === result.platformDataDisk) disk.contains.push({ label: 'Platform data', id: 'platformdata', usage: 0 }); - if (disk.filesystem === result.boxDataDisk) disk.contains.push({ label: 'Box data', id: 'boxdata', usage: 0 }); - if (disk.filesystem === result.dockerDataDisk) disk.contains.push({ label: 'Docker images', id: 'docker', usage: 0 }); - if (disk.filesystem === result.mailDataDisk) disk.contains.push({ label: 'Email data', id: 'maildata', usage: 0 }); - if (disk.filesystem === result.backupsDisk) disk.contains.push({ label: 'Backup data', id: 'cloudron-backup', usage: 0 }); - - const apps = Object.keys(result.apps).filter(function (appId) { return result.apps[appId] === disk.filesystem; }); - apps.forEach(function (appId) { - var app = Client.getCachedAppSync(appId); - disk.contains.push({ app: app, label: app.label || app.fqdn, id: appId, usage: 0 }); - }); - }); - - $scope.disks = result.disks; - - // lazy fetch graphite data - $scope.disks.forEach(function (disk) { - // /dev/sda1 -> sda1 - // /dev/mapper/foo -> mapper_foo (see #348) - var diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1); - diskName = diskName.replace(/\//g, '_'); - - Client.graphs([ - 'absolute(collectd.localhost.df-' + diskName + '.df_complex-free)', - 'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)', - 'absolute(collectd.localhost.df-' + diskName + '.df_complex-used)' - ], '-1min', {}, function (error, data) { - if (error) return $scope.setError('disk', error); - - disk.size = data[2].datapoints[0][0] + data[1].datapoints[0][0] + data[0].datapoints[0][0]; - disk.free = data[0].datapoints[0][0]; - disk.occupied = data[2].datapoints[0][0] + data[1].datapoints[0][0]; - - colorIndex = 0; - disk.contains.forEach(function (content) { - content.color = getNextColor(); - }); - - // get disk usage data - var graphiteQueries = disk.contains.map(function (content) { - return 'absolute(collectd.localhost.du-' + content.id + '.capacity-usage)'; - }); - - Client.graphs(graphiteQueries, '-1day', { noNullPoints: true }, function (error, data) { - if (error) return $scope.setError('disk', error); - - var usageOther = disk.occupied; - - data.forEach(function (d) { - var content = disk.contains.find(function (content) { return d.target.indexOf(content.id) !== -1; }); - if (!content) return; // didn't match any content - - var tmp = d.datapoints[d.datapoints.length-1][0]; - content.usage = tmp; - - // deduct from overal disk usage to track other - usageOther -= tmp; - }); - - // add content container for other non tracked data - disk.contains.push({ - label: 'Other', - id: 'other', - color: '#27CE65', - usage: usageOther - }); - }); - }); - }); - }); - }; - $scope.updateMemorySystemChart = function () { var targets = []; var targetsInfo = []; @@ -322,7 +238,6 @@ angular.module('Application').controller('GraphsController', ['$scope', '$locati Client.onReady(function () { getMemory(function () { - $scope.updateDiskGraphs(); $scope.updateMemorySystemChart(); $scope.updateMemoryAppsChart(); $scope.setMemoryApp('system'); diff --git a/src/views/system.html b/src/views/system.html index 0e81a0089..6b3b0084c 100644 --- a/src/views/system.html +++ b/src/views/system.html @@ -59,6 +59,36 @@

System

+
+

Disk Usage

+
+ +
+
+
+

+
+
+
+
+

{{ disk.filesystem }} mounted at {{ disk.mountpoint }} {{ disk.available | prettyDiskSize }} of {{ disk.size | prettyDiskSize }} still available

+
+
+
+
+

This {{ disk.type }} disk contains:

+ +
+
+
+ +
+

Services

@@ -91,6 +121,15 @@ + + + cloudron + + + + + + @@ -109,8 +148,8 @@ - + @@ -120,28 +159,4 @@ - -
-

Server

-
- -
-
-
-

- This server requires a reboot to finalize Ubuntu security updates. -

-

- Use this only when you experience unexpected behavior. All services and apps will be automatically started. -

-
-
-
-
- Show Logs - -
-
-
- diff --git a/src/views/system.js b/src/views/system.js index 4c979eadf..491acebcc 100644 --- a/src/views/system.js +++ b/src/views/system.js @@ -9,9 +9,26 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati $scope.config = Client.getConfig(); $scope.ready = false; $scope.services = []; - $scope.isRebootRequired = false; + $scope.disks = []; $scope.memory = null; + // http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript + function getRandomColor() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++ ) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + } + + var colorIndex = 0; + var colors = [ '#2196F3', '#3995b1', '#f0ad4e', '#ff4c4c' ]; + function getNextColor() { + if (colors[colorIndex+1]) return colors[colorIndex++]; + return getRandomColor(); + } + function refresh(serviceName, callback) { callback = callback || function () {}; @@ -30,45 +47,6 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati }); } - function refreshAll() { - $scope.services.forEach(function (s) { - refresh(s.name); - }); - } - - $scope.reboot = { - busy: false, - - show: function () { - $scope.reboot.busy = false; - - $('#rebootModal').modal('show'); - }, - - waitForReboot: function () { - if (Client.offline) return $scope.reboot.busy = false; // at this point, we are showing the offline banner - - Client.getStatus(function (error, status) { - if (error) return $timeout($scope.reboot.waitForReboot, 5000); - - $scope.reboot.busy = false; - }); - }, - - submit: function () { - $scope.reboot.busy = true; - - Client.reboot(function (error) { - if (error) return Client.error(error); - - $('#rebootModal').modal('hide'); - - // show "busy" indicator for 5 seconds to show some ui activity - $timeout(function () { $scope.reboot.waitForReboot(); }, 5000); - }); - } - }; - $scope.restartService = function (serviceName) { function waitForActive(serviceName) { refresh(serviceName, function (error, result) { @@ -148,33 +126,109 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati } }; - function getMemory(callback) { + + function updateDiskGraphs() { + // https://graphite.readthedocs.io/en/latest/render_api.html#paths-and-wildcards + // on scaleway, for some reason docker devices are collected as part of collectd + // until we figure why just hardcode popular disk devices - https://www.mjmwired.net/kernel/Documentation/devices.txt + Client.disks(function (error, result) { + if (error) return $scope.setError('disk', error); + + result.disks.forEach(function (disk, index) { + disk.id = index; + disk.contains = []; + + if (disk.filesystem === result.platformDataDisk) disk.contains.push({ label: 'Platform data', id: 'platformdata', usage: 0 }); + if (disk.filesystem === result.boxDataDisk) disk.contains.push({ label: 'Box data', id: 'boxdata', usage: 0 }); + if (disk.filesystem === result.dockerDataDisk) disk.contains.push({ label: 'Docker images', id: 'docker', usage: 0 }); + if (disk.filesystem === result.mailDataDisk) disk.contains.push({ label: 'Email data', id: 'maildata', usage: 0 }); + if (disk.filesystem === result.backupsDisk) disk.contains.push({ label: 'Backup data', id: 'cloudron-backup', usage: 0 }); + + const apps = Object.keys(result.apps).filter(function (appId) { return result.apps[appId] === disk.filesystem; }); + apps.forEach(function (appId) { + var app = Client.getCachedAppSync(appId); + disk.contains.push({ app: app, label: app.label || app.fqdn, id: appId, usage: 0 }); + }); + }); + + $scope.disks = result.disks; + + // lazy fetch graphite data + $scope.disks.forEach(function (disk) { + // /dev/sda1 -> sda1 + // /dev/mapper/foo -> mapper_foo (see #348) + var diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1); + diskName = diskName.replace(/\//g, '_'); + + Client.graphs([ + 'absolute(collectd.localhost.df-' + diskName + '.df_complex-free)', + 'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)', + 'absolute(collectd.localhost.df-' + diskName + '.df_complex-used)' + ], '-1min', {}, function (error, data) { + if (error) return $scope.setError('disk', error); + + disk.size = data[2].datapoints[0][0] + data[1].datapoints[0][0] + data[0].datapoints[0][0]; + disk.free = data[0].datapoints[0][0]; + disk.occupied = data[2].datapoints[0][0] + data[1].datapoints[0][0]; + + colorIndex = 0; + disk.contains.forEach(function (content) { + content.color = getNextColor(); + }); + + // get disk usage data + var graphiteQueries = disk.contains.map(function (content) { + return 'absolute(collectd.localhost.du-' + content.id + '.capacity-usage)'; + }); + + Client.graphs(graphiteQueries, '-1day', { noNullPoints: true }, function (error, data) { + if (error) return $scope.setError('disk', error); + + var usageOther = disk.occupied; + + data.forEach(function (d) { + var content = disk.contains.find(function (content) { return d.target.indexOf(content.id) !== -1; }); + if (!content) return; // didn't match any content + + var tmp = d.datapoints[d.datapoints.length-1][0]; + content.usage = tmp; + + // deduct from overal disk usage to track other + usageOther -= tmp; + }); + + // add content container for other non tracked data + disk.contains.push({ + label: 'Other', + id: 'other', + color: '#27CE65', + usage: usageOther + }); + }); + }); + }); + }); + } + + Client.onReady(function () { Client.memory(function (error, memory) { if (error) console.error(error); $scope.memory = memory; - callback(); - }); - } + updateDiskGraphs(); - Client.onReady(function () { - Client.isRebootRequired(function (error, result) { - if (error) console.error(error); + Client.getServices(function (error, result) { + if (error) return Client.error(error); - $scope.isRebootRequired = !!result; + $scope.services = result.map(function (serviceName) { return { name: serviceName }; }); - getMemory(function () { - Client.getServices(function (error, result) { - if (error) return Client.error(error); - - $scope.services = result.map(function (serviceName) { return { name: serviceName }; }); - - // just kick off the status fetching - refreshAll(); - - $scope.ready = true; + // just kick off the status fetching + $scope.services.forEach(function (s) { + refresh(s.name); }); + + $scope.ready = true; }); }); });