diff --git a/app/app.js b/app/app.js index 059b41344..49e424aff 100644 --- a/app/app.js +++ b/app/app.js @@ -23,6 +23,7 @@ angular.module('portainer', [ 'container', 'containerConsole', 'containerLogs', + 'containerStats', 'serviceLogs', 'containers', 'createContainer', @@ -54,7 +55,6 @@ angular.module('portainer', [ 'settings', 'settingsAuthentication', 'sidebar', - 'stats', 'swarm', 'task', 'team', @@ -158,8 +158,8 @@ angular.module('portainer', [ url: '^/containers/:id/stats', views: { 'content@': { - templateUrl: 'app/components/stats/stats.html', - controller: 'StatsController' + templateUrl: 'app/components/containerStats/containerStats.html', + controller: 'ContainerStatsController' }, 'sidebar@': { templateUrl: 'app/components/sidebar/sidebar.html', diff --git a/app/components/containerStats/containerStats.html b/app/components/containerStats/containerStats.html new file mode 100644 index 000000000..63619a917 --- /dev/null +++ b/app/components/containerStats/containerStats.html @@ -0,0 +1,123 @@ + + + + + + Containers > {{ container.Name|trimcontainername }} > Stats + + + +
+
+ + + + +
+
+
+ + This view displays real-time statistics about the container {{ container.Name|trimcontainername }} as well as a list of the running processes + inside this container. + +
+
+
+ +
+ +
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+ + +
+ Items per page: + +
+
+ + + + + + + + + + + + + + + + + + +
+ + {{ title }} + + + +
{{ procInfo }}
Loading...
No processes available.
+
+ +
+
+
+
+
diff --git a/app/components/containerStats/containerStatsController.js b/app/components/containerStats/containerStatsController.js new file mode 100644 index 000000000..a48383507 --- /dev/null +++ b/app/components/containerStats/containerStatsController.js @@ -0,0 +1,159 @@ +angular.module('containerStats', []) +.controller('ContainerStatsController', ['$q', '$scope', '$stateParams', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', 'Pagination', +function ($q, $scope, $stateParams, $document, $interval, ContainerService, ChartService, Notifications, Pagination) { + + $scope.state = { + refreshRate: '5' + }; + + $scope.state.pagination_count = Pagination.getPaginationCount('stats_processes'); + $scope.sortType = 'CMD'; + $scope.sortReverse = false; + + $scope.order = function (sortType) { + $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; + $scope.sortType = sortType; + }; + + $scope.changePaginationCount = function() { + Pagination.setPaginationCount('stats_processes', $scope.state.pagination_count); + }; + + $scope.$on('$destroy', function() { + stopRepeater(); + }); + + function stopRepeater() { + var repeater = $scope.repeater; + if (angular.isDefined(repeater)) { + $interval.cancel(repeater); + repeater = null; + } + } + + function updateNetworkChart(stats, chart) { + var rx = stats.Networks[0].rx_bytes; + var tx = stats.Networks[0].tx_bytes; + var label = moment(stats.Date).format('HH:mm:ss'); + + ChartService.UpdateNetworkChart(label, rx, tx, chart); + } + + function updateMemoryChart(stats, chart) { + var label = moment(stats.Date).format('HH:mm:ss'); + var value = stats.MemoryUsage; + + ChartService.UpdateMemoryChart(label, value, chart); + } + + function updateCPUChart(stats, chart) { + var label = moment(stats.Date).format('HH:mm:ss'); + var value = calculateCPUPercentUnix(stats); + + ChartService.UpdateCPUChart(label, value, chart); + } + + function calculateCPUPercentUnix(stats) { + var cpuPercent = 0.0; + var cpuDelta = stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage; + var systemDelta = stats.CurrentCPUSystemUsage - stats.PreviousCPUSystemUsage; + + if (systemDelta > 0.0 && cpuDelta > 0.0) { + cpuPercent = (cpuDelta / systemDelta) * stats.CPUCores * 100.0; + } + + return cpuPercent; + } + + $scope.changeUpdateRepeater = function() { + var networkChart = $scope.networkChart; + var cpuChart = $scope.cpuChart; + var memoryChart = $scope.memoryChart; + + stopRepeater(); + setUpdateRepeater(networkChart, cpuChart, memoryChart); + $('#refreshRateChange').show(); + $('#refreshRateChange').fadeOut(1500); + }; + + function startChartUpdate(networkChart, cpuChart, memoryChart) { + $('#loadingViewSpinner').show(); + $q.all({ + stats: ContainerService.containerStats($stateParams.id), + top: ContainerService.containerTop($stateParams.id) + }) + .then(function success(data) { + var stats = data.stats; + $scope.processInfo = data.top; + updateNetworkChart(stats, networkChart); + updateMemoryChart(stats, memoryChart); + updateCPUChart(stats, cpuChart); + setUpdateRepeater(networkChart, cpuChart, memoryChart); + }) + .catch(function error(err) { + stopRepeater(); + Notifications.error('Failure', err, 'Unable to retrieve container statistics'); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); + } + + function setUpdateRepeater(networkChart, cpuChart, memoryChart) { + var refreshRate = $scope.state.refreshRate; + $scope.repeater = $interval(function() { + $q.all({ + stats: ContainerService.containerStats($stateParams.id), + top: ContainerService.containerTop($stateParams.id) + }) + .then(function success(data) { + var stats = data.stats; + $scope.processInfo = data.top; + updateNetworkChart(stats, networkChart); + updateMemoryChart(stats, memoryChart); + updateCPUChart(stats, cpuChart); + }) + .catch(function error(err) { + stopRepeater(); + Notifications.error('Failure', err, 'Unable to retrieve container statistics'); + }); + }, refreshRate * 1000); + } + + function initCharts() { + var networkChartCtx = $('#networkChart'); + var networkChart = ChartService.CreateNetworkChart(networkChartCtx); + $scope.networkChart = networkChart; + + var cpuChartCtx = $('#cpuChart'); + var cpuChart = ChartService.CreateCPUChart(cpuChartCtx); + $scope.cpuChart = cpuChart; + + var memoryChartCtx = $('#memoryChart'); + var memoryChart = ChartService.CreateMemoryChart(memoryChartCtx); + $scope.memoryChart = memoryChart; + + startChartUpdate(networkChart, cpuChart, memoryChart); + } + + function initView() { + $('#loadingViewSpinner').show(); + + ContainerService.container($stateParams.id) + .then(function success(data) { + $scope.container = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve container information'); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); + + $document.ready(function() { + initCharts(); + }); + } + + initView(); +}]); diff --git a/app/components/stats/stats.html b/app/components/stats/stats.html deleted file mode 100644 index 47168d464..000000000 --- a/app/components/stats/stats.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - Containers > {{ container.Name|trimcontainername }} > Stats - - - -
-
- - -
- -
-
{{ container.Name|trimcontainername }}
-
- Name -
-
-
-
-
- -
-
- - - - - - -
-
- - - - - - -
-
- -
-
- - - - -
-
-
-
-
-
-
- - -
- Items per page: - -
-
- - - - - - - - - - - - -
- - {{title}} - - - -
{{processInfo}}
-
- -
-
-
-
-
diff --git a/app/components/stats/statsController.js b/app/components/stats/statsController.js deleted file mode 100644 index 1b8de729d..000000000 --- a/app/components/stats/statsController.js +++ /dev/null @@ -1,220 +0,0 @@ -angular.module('stats', []) -.controller('StatsController', ['Pagination', '$scope', 'Notifications', '$timeout', 'Container', 'ContainerTop', '$stateParams', 'humansizeFilter', '$sce', '$document', -function (Pagination, $scope, Notifications, $timeout, Container, ContainerTop, $stateParams, humansizeFilter, $sce, $document) { - // TODO: Force scale to 0-100 for cpu, fix charts on dashboard, - // TODO: Force memory scale to 0 - max memory - $scope.ps_args = ''; - $scope.state = {}; - $scope.state.pagination_count = Pagination.getPaginationCount('stats_processes'); - $scope.sortType = 'CMD'; - $scope.sortReverse = false; - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('stats_processes', $scope.state.pagination_count); - }; - $scope.getTop = function () { - ContainerTop.get($stateParams.id, { - ps_args: $scope.ps_args - }, function (data) { - $scope.containerTop = data; - }); - }; - var destroyed = false; - var timeout; - $document.ready(function(){ - var cpuLabels = []; - var cpuData = []; - var memoryLabels = []; - var memoryData = []; - var networkLabels = []; - var networkTxData = []; - var networkRxData = []; - for (var i = 0; i < 20; i++) { - cpuLabels.push(''); - cpuData.push(0); - memoryLabels.push(''); - memoryData.push(0); - networkLabels.push(''); - networkTxData.push(0); - networkRxData.push(0); - } - var cpuDataset = { // CPU Usage - fillColor: 'rgba(151,187,205,0.5)', - strokeColor: 'rgba(151,187,205,1)', - pointColor: 'rgba(151,187,205,1)', - pointStrokeColor: '#fff', - data: cpuData - }; - var memoryDataset = { - fillColor: 'rgba(151,187,205,0.5)', - strokeColor: 'rgba(151,187,205,1)', - pointColor: 'rgba(151,187,205,1)', - pointStrokeColor: '#fff', - data: memoryData - }; - var networkRxDataset = { - label: 'Rx Bytes', - fillColor: 'rgba(151,187,205,0.5)', - strokeColor: 'rgba(151,187,205,1)', - pointColor: 'rgba(151,187,205,1)', - pointStrokeColor: '#fff', - data: networkRxData - }; - var networkTxDataset = { - label: 'Tx Bytes', - fillColor: 'rgba(255,180,174,0.5)', - strokeColor: 'rgba(255,180,174,1)', - pointColor: 'rgba(255,180,174,1)', - pointStrokeColor: '#fff', - data: networkTxData - }; - var networkLegendData = [ - { - //value: '', - color: 'rgba(151,187,205,0.5)', - title: 'Rx Data' - }, - { - //value: '', - color: 'rgba(255,180,174,0.5)', - title: 'Tx Data' - } - ]; - - legend($('#network-legend').get(0), networkLegendData); - - Chart.defaults.global.animationSteps = 30; // Lower from 60 to ease CPU load. - var cpuChart = new Chart($('#cpu-stats-chart').get(0).getContext('2d')).Line({ - labels: cpuLabels, - datasets: [cpuDataset] - }, { - responsive: true - }); - - var memoryChart = new Chart($('#memory-stats-chart').get(0).getContext('2d')).Line({ - labels: memoryLabels, - datasets: [memoryDataset] - }, - { - scaleLabel: function (valueObj) { - return humansizeFilter(parseInt(valueObj.value, 10), 2); - }, - responsive: true - //scaleOverride: true, - //scaleSteps: 10, - //scaleStepWidth: Math.ceil(initialStats.memory_stats.limit / 10), - //scaleStartValue: 0 - }); - var networkChart = new Chart($('#network-stats-chart').get(0).getContext('2d')).Line({ - labels: networkLabels, - datasets: [networkRxDataset, networkTxDataset] - }, { - scaleLabel: function (valueObj) { - return humansizeFilter(parseInt(valueObj.value, 10), 2); - }, - responsive: true - }); - $scope.networkLegend = $sce.trustAsHtml(networkChart.generateLegend()); - - - function updateStats() { - Container.stats({id: $stateParams.id}, function (d) { - var arr = Object.keys(d).map(function (key) { - return d[key]; - }); - if (arr.join('').indexOf('no such id') !== -1) { - Notifications.error('Unable to retrieve stats', {}, 'Is this container running?'); - return; - } - - // Update graph with latest data - $scope.data = d; - updateCpuChart(d); - updateMemoryChart(d); - updateNetworkChart(d); - setUpdateStatsTimeout(); - }, function () { - Notifications.error('Unable to retrieve stats', {}, 'Is this container running?'); - setUpdateStatsTimeout(); - }); - } - - - $scope.$on('$destroy', function () { - destroyed = true; - $timeout.cancel(timeout); - }); - - updateStats(); - - function updateCpuChart(data) { - cpuChart.addData([calculateCPUPercent(data)], new Date(data.read).toLocaleTimeString()); - cpuChart.removeData(); - } - - function updateMemoryChart(data) { - memoryChart.addData([data.memory_stats.usage], new Date(data.read).toLocaleTimeString()); - memoryChart.removeData(); - } - - var lastRxBytes = 0, lastTxBytes = 0; - - function updateNetworkChart(data) { - // 1.9+ contains an object of networks, for now we'll just show stats for the first network - // TODO: Show graphs for all networks - if (data.networks) { - $scope.networkName = Object.keys(data.networks)[0]; - data.network = data.networks[$scope.networkName]; - } - if(data.network) { - var rxBytes = 0, txBytes = 0; - if (lastRxBytes !== 0 || lastTxBytes !== 0) { - // These will be zero on first call, ignore to prevent large graph spike - rxBytes = data.network.rx_bytes - lastRxBytes; - txBytes = data.network.tx_bytes - lastTxBytes; - } - lastRxBytes = data.network.rx_bytes; - lastTxBytes = data.network.tx_bytes; - networkChart.addData([rxBytes, txBytes], new Date(data.read).toLocaleTimeString()); - networkChart.removeData(); - } - } - - function calculateCPUPercent(stats) { - // Same algorithm the official client uses: https://github.com/docker/docker/blob/master/api/client/stats.go#L195-L208 - var prevCpu = stats.precpu_stats; - var curCpu = stats.cpu_stats; - - var cpuPercent = 0.0; - - // calculate the change for the cpu usage of the container in between readings - var cpuDelta = curCpu.cpu_usage.total_usage - prevCpu.cpu_usage.total_usage; - // calculate the change for the entire system between readings - var systemDelta = curCpu.system_cpu_usage - prevCpu.system_cpu_usage; - - if (systemDelta > 0.0 && cpuDelta > 0.0) { - cpuPercent = (cpuDelta / systemDelta) * curCpu.cpu_usage.percpu_usage.length * 100.0; - } - return cpuPercent; - } - - function setUpdateStatsTimeout() { - if(!destroyed) { - timeout = $timeout(updateStats, 5000); - } - } - }); - - Container.get({id: $stateParams.id}, function (d) { - $scope.container = d; - }, function (e) { - Notifications.error('Failure', e, 'Unable to retrieve container info'); - }); - var endpointProvider = $scope.applicationState.endpoint.mode.provider; - if (endpointProvider !== 'VMWARE_VIC') { - $scope.getTop(); - } -}]); diff --git a/app/models/docker/containerStats.js b/app/models/docker/containerStats.js new file mode 100644 index 000000000..aad3b48b3 --- /dev/null +++ b/app/models/docker/containerStats.js @@ -0,0 +1,12 @@ +function ContainerStatsViewModel(data) { + this.Date = data.read; + this.MemoryUsage = data.memory_stats.usage; + this.PreviousCPUTotalUsage = data.precpu_stats.cpu_usage.total_usage; + this.PreviousCPUSystemUsage = data.precpu_stats.system_cpu_usage; + this.CurrentCPUTotalUsage = data.cpu_stats.cpu_usage.total_usage; + this.CurrentCPUSystemUsage = data.cpu_stats.system_cpu_usage; + if (data.cpu_stats.cpu_usage.percpu_usage) { + this.CPUCores = data.cpu_stats.cpu_usage.percpu_usage.length; + } + this.Networks = _.values(data.networks); +} diff --git a/app/rest/docker/container.js b/app/rest/docker/container.js index 1bad5758f..d8786cb03 100644 --- a/app/rest/docker/container.js +++ b/app/rest/docker/container.js @@ -13,7 +13,14 @@ angular.module('portainer.rest') kill: {method: 'POST', params: {id: '@id', action: 'kill'}}, pause: {method: 'POST', params: {id: '@id', action: 'pause'}}, unpause: {method: 'POST', params: {id: '@id', action: 'unpause'}}, - stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 5000}, + stats: { + method: 'GET', params: { id: '@id', stream: false, action: 'stats' }, + timeout: 4500 + }, + top: { + method: 'GET', params: { id: '@id', action: 'top' }, + timeout: 4500 + }, start: { method: 'POST', params: {id: '@id', action: 'start'}, transformResponse: genericHandler diff --git a/app/rest/docker/containerTop.js b/app/rest/docker/containerTop.js deleted file mode 100644 index 57e51d51c..000000000 --- a/app/rest/docker/containerTop.js +++ /dev/null @@ -1,17 +0,0 @@ -angular.module('portainer.rest') -.factory('ContainerTop', ['$http', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ($http, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return { - get: function (id, params, callback, errorCallback) { - $http({ - method: 'GET', - url: API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker/containers/' + id + '/top', - params: { - ps_args: params.ps_args - } - }).success(callback).error(function (data, status, headers, config) { - console.log(data); - }); - } - }; -}]); diff --git a/app/services/chartService.js b/app/services/chartService.js new file mode 100644 index 000000000..5dcacce80 --- /dev/null +++ b/app/services/chartService.js @@ -0,0 +1,251 @@ +angular.module('portainer.services') +.factory('ChartService', [function ChartService() { + 'use strict'; + + // Max. number of items to display on a chart + var CHART_LIMIT = 600; + + var service = {}; + + service.CreateCPUChart = function(context) { + return new Chart(context, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'CPU', + data: [], + fill: true, + backgroundColor: 'rgba(151,187,205,0.4)', + borderColor: 'rgba(151,187,205,0.6)', + pointBackgroundColor: 'rgba(151,187,205,1)', + pointBorderColor: 'rgba(151,187,205,1)', + pointRadius: 2, + borderWidth: 2 + } + ] + }, + options: { + animation: { + duration: 0 + }, + responsiveAnimationDuration: 0, + responsive: true, + tooltips: { + mode: 'index', + intersect: false, + position: 'nearest', + callbacks: { + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label; + return percentageBasedTooltipLabel(datasetLabel, tooltipItem.yLabel); + } + } + }, + hover: { + animationDuration: 0 + }, + scales: { + yAxes: [ + { + ticks: { + beginAtZero: true, + callback: percentageBasedAxisLabel + } + } + ] + } + } + }); + }; + + service.CreateMemoryChart = function(context) { + return new Chart(context, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'Memory', + data: [], + fill: true, + backgroundColor: 'rgba(151,187,205,0.4)', + borderColor: 'rgba(151,187,205,0.6)', + pointBackgroundColor: 'rgba(151,187,205,1)', + pointBorderColor: 'rgba(151,187,205,1)', + pointRadius: 2, + borderWidth: 2 + } + ] + }, + options: { + animation: { + duration: 0 + }, + responsiveAnimationDuration: 0, + responsive: true, + tooltips: { + mode: 'index', + intersect: false, + position: 'nearest', + callbacks: { + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label; + return byteBasedTooltipLabel(datasetLabel, tooltipItem.yLabel); + } + } + }, + hover: { + animationDuration: 0 + }, + scales: { + yAxes: [ + { + ticks: { + beginAtZero: true, + callback: byteBasedAxisLabel + } + } + ] + } + } + }); + }; + + service.CreateNetworkChart = function(context) { + return new Chart(context, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'RX on eth0', + data: [], + fill: false, + backgroundColor: 'rgba(151,187,205,0.4)', + borderColor: 'rgba(151,187,205,0.6)', + pointBackgroundColor: 'rgba(151,187,205,1)', + pointBorderColor: 'rgba(151,187,205,1)', + pointRadius: 2, + borderWidth: 2 + }, + { + label: 'TX on eth0', + data: [], + fill: false, + backgroundColor: 'rgba(255,180,174,0.5)', + borderColor: 'rgba(255,180,174,0.7)', + pointBackgroundColor: 'rgba(255,180,174,1)', + pointBorderColor: 'rgba(255,180,174,1)', + pointRadius: 2, + borderWidth: 2 + } + ] + }, + options: { + animation: { + duration: 0 + }, + responsiveAnimationDuration: 0, + responsive: true, + tooltips: { + mode: 'index', + intersect: false, + position: 'average', + callbacks: { + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label; + return byteBasedTooltipLabel(datasetLabel, tooltipItem.yLabel); + } + } + }, + hover: { + animationDuration: 0 + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + callback: byteBasedAxisLabel + } + }] + } + } + }); + }; + + service.UpdateMemoryChart = function(label, value, chart) { + chart.data.labels.push(label); + chart.data.datasets[0].data.push(value); + + if (chart.data.datasets[0].data.length > CHART_LIMIT) { + chart.data.labels.pop(); + chart.data.datasets[0].data.pop(); + } + + chart.update(0); + }; + + service.UpdateCPUChart = function(label, value, chart) { + chart.data.labels.push(label); + chart.data.datasets[0].data.push(value); + + if (chart.data.datasets[0].data.length > CHART_LIMIT) { + chart.data.labels.pop(); + chart.data.datasets[0].data.pop(); + } + + chart.update(0); + }; + + service.UpdateNetworkChart = function(label, rx, tx, chart) { + chart.data.labels.push(label); + chart.data.datasets[0].data.push(rx); + chart.data.datasets[1].data.push(tx); + + if (chart.data.datasets[0].data.length > CHART_LIMIT) { + chart.data.labels.pop(); + chart.data.datasets[0].data.pop(); + chart.data.datasets[1].data.pop(); + } + + chart.update(0); + }; + + function byteBasedTooltipLabel(label, value) { + var processedValue = 0; + if (value > 5) { + processedValue = filesize(value, {base: 10, round: 1}); + } else { + processedValue = value.toFixed(1) + 'B'; + } + return label + ': ' + processedValue; + } + + function byteBasedAxisLabel(value, index, values) { + if (value > 5) { + return filesize(value, {base: 10, round: 1}); + } + return value.toFixed(1) + 'B'; + } + + function percentageBasedAxisLabel(value, index, values) { + if (value > 1) { + return Math.round(value) + '%'; + } + return value.toFixed(1) + '%'; + } + + function percentageBasedTooltipLabel(label, value) { + var processedValue = 0; + if (value > 1) { + processedValue = Math.round(value); + } else { + processedValue = value.toFixed(1); + } + return label + ': ' + processedValue + '%'; + } + + return service; +}]); diff --git a/app/services/docker/containerService.js b/app/services/docker/containerService.js index ee946fec3..c9f302d56 100644 --- a/app/services/docker/containerService.js +++ b/app/services/docker/containerService.js @@ -3,6 +3,21 @@ angular.module('portainer.services') 'use strict'; var service = {}; + service.container = function(id) { + var deferred = $q.defer(); + + Container.get({ id: id }).$promise + .then(function success(data) { + var container = new ContainerDetailsViewModel(data); + deferred.resolve(container); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve container information', err: err }); + }); + + return deferred.promise; + }; + service.containers = function(all) { var deferred = $q.defer(); Container.query({ all: all }).$promise @@ -11,7 +26,7 @@ angular.module('portainer.services') deferred.resolve(containers); }) .catch(function error(err) { - deferred.reject({ msg: 'Unable to retriever containers', err: err }); + deferred.reject({ msg: 'Unable to retrieve containers', err: err }); }); return deferred.promise; }; @@ -105,5 +120,35 @@ angular.module('portainer.services') return deferred.promise; }; + service.containerStats = function(id) { + var deferred = $q.defer(); + + Container.stats({id: id}).$promise + .then(function success(data) { + var containerStats = new ContainerStatsViewModel(data); + deferred.resolve(containerStats); + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; + + service.containerTop = function(id) { + var deferred = $q.defer(); + + Container.top({id: id}).$promise + .then(function success(data) { + var containerTop = data; + deferred.resolve(containerTop); + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; + return service; }]); diff --git a/assets/js/legend.js b/assets/js/legend.js deleted file mode 100644 index 7b7933a46..000000000 --- a/assets/js/legend.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * legend.js v0.2.0 - * License: MIT - */ -function legend(parent, data) { - parent.className = 'legend'; - var datas = data.hasOwnProperty('datasets') ? data.datasets : data; - - datas.forEach(function(d) { - var title = document.createElement('span'); - title.className = 'title'; - title.style.borderColor = d.hasOwnProperty('strokeColor') ? d.strokeColor : d.color; - title.style.borderStyle = 'solid'; - parent.appendChild(title); - - var text = document.createTextNode(d.title); - title.appendChild(text); - }); -} diff --git a/bower.json b/bower.json index a61ae1b8c..3fb1da483 100644 --- a/bower.json +++ b/bower.json @@ -24,7 +24,6 @@ "tests" ], "dependencies": { - "Chart.js": "1.0.2", "angular": "~1.5.0", "angular-cookies": "~1.5.0", "angular-bootstrap": "~2.5.0", @@ -49,7 +48,8 @@ "bootbox.js": "bootbox#^4.4.0", "angular-multi-select": "~4.0.0", "toastr": "~2.1.3", - "xterm.js": "~2.8.1" + "xterm.js": "~2.8.1", + "chart.js": "~2.6.0" }, "resolutions": { "angular": "1.5.11" diff --git a/vendor.yml b/vendor.yml index 4c1e83937..f4846a4e0 100644 --- a/vendor.yml +++ b/vendor.yml @@ -5,15 +5,14 @@ js: - bower_components/bootstrap/dist/js/bootstrap.js - bower_components/angular-multi-select/isteven-multi-select.js - bower_components/bootbox.js/bootbox.js - - bower_components/Chart.js/Chart.js - bower_components/filesize/lib/filesize.js - bower_components/lodash/dist/lodash.js - bower_components/moment/moment.js + - bower_components/chart.js/dist/Chart.js - bower_components/splitargs/src/splitargs.js - bower_components/toastr/toastr.js - bower_components/xterm.js/dist/xterm.js - bower_components/xterm.js/dist/addons/fit/fit.js - - assets/js/legend.js minified: - bower_components/jquery/dist/jquery.min.js - bower_components/bootstrap/dist/js/bootstrap.min.js @@ -23,11 +22,11 @@ js: - bower_components/filesize/lib/filesize.min.js - bower_components/lodash/dist/lodash.min.js - bower_components/moment/min/moment.min.js + - bower_components/chart.js/dist/Chart.min.js - bower_components/splitargs/src/splitargs.js - bower_components/toastr/toastr.min.js - bower_components/xterm.js/dist/xterm.js - bower_components/xterm.js/dist/addons/fit/fit.js - - assets/js/legend.js css: regular: - bower_components/bootstrap/dist/css/bootstrap.css @@ -71,4 +70,4 @@ angular: - bower_components/angular-ui-select/dist/select.min.js - bower_components/angular-ui-router/release/angular-ui-router.min.js - bower_components/angular-utils-pagination/dirPagination.js - - bower_components/ng-file-upload/ng-file-upload.min.js \ No newline at end of file + - bower_components/ng-file-upload/ng-file-upload.min.js