// # Copyright (C) 2010-2016 RhodeCode GmbH // # // # This program is free software: you can redistribute it and/or modify // # it under the terms of the GNU Affero General Public License, version 3 // # (only), as published by the Free Software Foundation. // # // # This program is distributed in the hope that it will be useful, // # but WITHOUT ANY WARRANTY; without even the implied warranty of // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // # GNU General Public License for more details. // # // # You should have received a copy of the GNU Affero General Public License // # along with this program. If not, see . // # // # This program is dual-licensed. If you wish to learn more about the // # App Enlight Enterprise Edition, including its added features, Support // # services, and proprietary license terms, please see // # https://rhodecode.com/licenses/ angular.module('appenlight.controllers') .controller('IndexDashboardController', IndexDashboardController); IndexDashboardController.$inject = ['$scope', '$location','$cookies', '$interval', 'stateHolder', 'userSelfPropertyResource', 'applicationsPropertyResource', 'AeConfig', 'AeUser']; function IndexDashboardController($scope, $location, $cookies, $interval, stateHolder, userSelfPropertyResource, applicationsPropertyResource, AeConfig, AeUser) { var vm = this; stateHolder.section = 'dashboard'; vm.timeOptions = {}; var allowed = ['1h', '4h', '12h', '24h', '1w', '2w', '1M']; _.each(allowed, function (key) { if (allowed.indexOf(key) !== -1) { vm.timeOptions[key] = AeConfig.timeOptions[key]; } }); vm.urls = AeConfig.urls; vm.applications = AeUser.applications_map; vm.show_dashboard = false; vm.resource = null; vm.graphType = {selected: null}; vm.timeSpan = vm.timeOptions['1h']; vm.trendingReports = []; vm.exceptions = 0; vm.satisfyingRequests = 0; vm.toleratedRequests = 0; vm.frustratingRequests = 0; vm.uptimeStats = 0; vm.apdexStats = []; vm.seriesRequestsData = []; vm.seriesMetricsData = []; vm.seriesSlowData = []; vm.slowCalls = []; vm.slowURIS = []; vm.reportChartConfig = { data: { json: [], xFormat: '%Y-%m-%dT%H:%M:%S' }, color: { pattern: ['#6baed6', '#e6550d', '#74c476', '#fdd0a2', '#8c564b'] }, axis: { x: { type: 'timeseries', tick: { culling: { max: 6 // the number of tick texts will be adjusted to less than this value }, format: '%Y-%m-%d %H:%M' } }, y: { tick: { count: 5, format: d3.format('.2s') } } }, subchart: { show: true, size: { height: 20 } }, size: { height: 250 }, zoom: { rescale: true }, grid: { x: { show: true }, y: { show: true } }, tooltip: { format: { title: function (d) { return '' + d; }, value: function (v) { return v } } } }; vm.reportChartData = {}; vm.reportSlowChartConfig = { data: { json: [], xFormat: '%Y-%m-%dT%H:%M:%S' }, color: { pattern: ['#6baed6', '#e6550d', '#74c476', '#fdd0a2', '#8c564b'] }, axis: { x: { type: 'timeseries', tick: { culling: { max: 6 // the number of tick texts will be adjusted to less than this value }, format: '%Y-%m-%d %H:%M' } }, y: { tick: { count: 5, format: d3.format('.2s') } } }, subchart: { show: true, size: { height: 20 } }, size: { height: 250 }, zoom: { rescale: true }, grid: { x: { show: true }, y: { show: true } }, tooltip: { format: { title: function (d) { return '' + d; }, value: function (v) { return v } } } }; vm.reportSlowChartData = {}; vm.metricsChartConfig = { data: { json: [], xFormat: '%Y-%m-%dT%H:%M:%S', keys: { x: 'x', value: ["main", "sql", "nosql", "tmpl", "remote", "custom"] }, names: { main: 'View/Application logic', sql: 'Relational database queries', nosql: 'NoSql datastore calls', tmpl: 'Template rendering', custom: 'Custom timed calls', remote: 'Requests to remote resources' }, type: 'area', groups: [["main", "sql", "nosql", "remote", "custom", "tmpl"]], order: null }, color: { pattern: ['#6baed6', '#c7e9c0', '#fd8d3c', '#d6616b', '#ffcc00', '#c6dbef'] }, axis: { x: { type: 'timeseries', tick: { culling: { max: 6 // the number of tick texts will be adjusted to less than this value }, format: '%Y-%m-%d %H:%M' } }, y: { tick: { count: 5, format: d3.format('.2f') } } }, point: { show: false }, subchart: { show: true, size: { height: 20 } }, size: { height: 350 }, zoom: { rescale: true }, grid: { x: { show: true }, y: { show: true } }, tooltip: { format: { title: function (d) { return '' + d; }, value: function (v) { return v } } } }; vm.metricsChartData = {}; vm.responseChartConfig = { data: { json: [], xFormat: '%Y-%m-%dT%H:%M:%S' }, color: { pattern: ['#d6616b', '#6baed6', '#fd8d3c'] }, axis: { x: { type: 'timeseries', tick: { culling: { max: 6 // the number of tick texts will be adjusted to less than this value }, format: '%Y-%m-%d %H:%M' } }, y: { tick: { count: 5, format: d3.format('.2f') } } }, point: { show: false }, subchart: { show: true, size: { height: 20 } }, size: { height: 350 }, zoom: { rescale: true }, grid: { x: { show: true }, y: { show: true } }, tooltip: { format: { title: function (d) { return '' + d; }, value: function (v) { return v } } } }; vm.responseChartData = {}; vm.requestsChartConfig = { data: { json: [], xFormat: '%Y-%m-%dT%H:%M:%S' }, color: { pattern: ['#d6616b', '#6baed6', '#fd8d3c'] }, axis: { x: { type: 'timeseries', tick: { culling: { max: 6 // the number of tick texts will be adjusted to less than this value }, format: '%Y-%m-%d %H:%M' } }, y: { tick: { count: 5, format: d3.format('.2f') } } }, point: { show: false }, subchart: { show: true, size: { height: 20 } }, size: { height: 350 }, zoom: { rescale: true }, grid: { x: { show: true }, y: { show: true } }, tooltip: { format: { title: function (d) { return '' + d; }, value: function (v) { return v } } } }; vm.requestsChartData = {}; vm.loading = { 'apdex': true, 'reports': true, 'graphs': true, 'slowCalls': true, 'slowURIS': true, 'requestsBreakdown': true, 'series': true }; vm.stream = {paused: false, filtered: false, messages: [], reports: []}; vm.websocket = null; userSelfPropertyResource.get({key: 'websocket'}, function (data) { console.log(data); console.log(AeConfig.ws_url + '/ws?conn_id=' + data.conn_id); vm.websocket = new ReconnectingWebSocket(AeConfig.ws_url + '/ws?conn_id=' + data.conn_id); vm.websocket.onopen = function (event) { console.log('open'); }; vm.websocket.onmessage = function (event) { var data = JSON.parse(event.data); console.log('message'); $scope.$apply(function (scope) { _.each(data, function (message) { if (message.type === 'message'){ ws_report = message.message.report; if (ws_report.http_status != 500) { return } if (vm.stream.paused == true) { return } if (vm.stream.filtered && ws_report.resource_id != vm.resource) { return } var should_insert = true; _.each(vm.stream.reports, function (report) { if (report.report_id == ws_report.report_id) { report.occurences = ws_report.occurences; should_insert = false; } }); if (should_insert) { if (vm.stream.reports.length > 7) { vm.stream.reports.pop(); } vm.stream.reports.unshift(ws_report); } } }); }); }; vm.websocket.onclose = function (event) { console.log('closed'); }; vm.websocket.onerror = function (event) { console.log('error'); }; }); vm.determineStartState = function () { if (AeUser.applications.length) { vm.resource = Number($location.search().resource); if (!vm.resource){ var cookieResource = $cookies.getObject('resource'); console.log('cookieResource', cookieResource); if (cookieResource) { vm.resource = cookieResource; } else{ vm.resource = AeUser.applications[0].resource_id; } } } var timespan = $location.search().timespan; if(_.has(vm.timeOptions, timespan)){ vm.timeSpan = vm.timeOptions[timespan]; } else{ vm.timeSpan = vm.timeOptions['1h']; } var graphType = $location.search().graphtype; if(!graphType){ vm.graphType = {selected: 'metrics_graphs'}; } else{ vm.graphType = {selected: graphType}; } vm.updateSearchParams(); }; vm.updateSearchParams = function () { $location.search('resource', vm.resource); $location.search('timespan', vm.timeSpan.key); $location.search('graphtype', vm.graphType.selected); stateHolder.resource = vm.resource; if (vm.resource){ $cookies.putObject('resource', vm.resource, {expires:new Date(3000, 1, 1)}); } }; vm.refreshData = function () { vm.fetchApdexStats(); vm.fetchTrendingReports(); vm.fetchMetrics(); vm.fetchRequestsBreakdown(); vm.fetchSlowCalls(); } vm.changedTimeSpan = function(){ vm.startDateFilter = timeSpanToStartDate(vm.timeSpan.key); vm.refreshData(); } var intervalId = $interval(function () { if (_.contains(['30m', "1h"], vm.timeSpan.key)) { vm.refreshData(); } }, 60000); $scope.$on('$destroy',function(){ $interval.cancel(intervalId); vm.websocket.close(); }); vm.fetchApdexStats = function () { vm.loading.apdex = true; vm.apdexStats = applicationsPropertyResource.query({ 'key': 'apdex_stats', 'resourceId': vm.resource, "start_date": timeSpanToStartDate(vm.timeSpan.key) }, function (data) { vm.loading.apdex = false; vm.exceptions = _.reduce(data, function (memo, row) { return memo + row.errors; }, 0); vm.satisfyingRequests = _.reduce(data, function (memo, row) { return memo + row.satisfying_requests; }, 0); vm.toleratedRequests = _.reduce(data, function (memo, row) { return memo + row.tolerated_requests; }, 0); vm.frustratingRequests = _.reduce(data, function (memo, row) { return memo + row.frustrating_requests; }, 0); if (data.length) { vm.uptimeStats = data[0].uptime; } }, function () { vm.loading.apdex = false; } ); } vm.fetchMetrics = function () { vm.loading.series = true; applicationsPropertyResource.query({ 'resourceId': vm.resource, 'key': vm.graphType.selected, "start_date": timeSpanToStartDate(vm.timeSpan.key) }, function (data) { if (vm.graphType.selected == 'metrics_graphs') { vm.metricsChartData = { json: data, xFormat: '%Y-%m-%dT%H:%M:%S', keys: { x: 'x', value: ["main", "sql", "nosql", "tmpl", "remote", "custom"] }, names: { main: 'View/Application logic', sql: 'Relational database queries', nosql: 'NoSql datastore calls', tmpl: 'Template rendering', custom: 'Custom timed calls', remote: 'Requests to remote resources' }, type: 'area', groups: [["main", "sql", "nosql", "remote", "custom", "tmpl"]], order: null }; } else if (vm.graphType.selected == 'report_graphs') { vm.reportChartData = { json: data, xFormat: '%Y-%m-%dT%H:%M:%S', keys: { x: 'x', value: ["not_found", "report"] }, names: { report: 'Errors', not_found: '404\'s requests' }, type: 'bar' }; } else if (vm.graphType.selected == 'slow_report_graphs') { vm.reportSlowChartData = { json: data, xFormat: '%Y-%m-%dT%H:%M:%S', keys: { x: 'x', value: ["slow_report"] }, names: { slow_report: 'Slow reports' }, type: 'bar' }; } else if (vm.graphType.selected == 'response_graphs') { vm.responseChartData = { json: data, xFormat: '%Y-%m-%dT%H:%M:%S', keys: { x: 'x', value: ["today", "days_ago_2", "days_ago_7"] }, names: { today: 'Today', "days_ago_2": '2 days ago', "days_ago_7": '7 days ago' } }; } else if (vm.graphType.selected == 'requests_graphs') { vm.requestsChartData = { json: data, xFormat: '%Y-%m-%dT%H:%M:%S', keys: { x: 'x', value: ["requests"] }, names: { requests: 'Requests/s' } }; } vm.loading.series = false; }, function(){ vm.loading.series = false; }); } vm.fetchSlowCalls = function () { vm.loading.slowCalls = true; applicationsPropertyResource.query({ 'resourceId': vm.resource, "start_date": timeSpanToStartDate(vm.timeSpan.key), 'key': 'slow_calls' }, function (data) { vm.slowCalls = data; vm.loading.slowCalls = false; }, function () { vm.loading.slowCalls = false; }); } vm.fetchRequestsBreakdown = function () { vm.loading.requestsBreakdown = true; applicationsPropertyResource.query({ 'resourceId': vm.resource, "start_date": timeSpanToStartDate(vm.timeSpan.key), 'key': 'requests_breakdown' }, function (data) { vm.requestsBreakdown = data; vm.loading.requestsBreakdown = false; }, function () { vm.loading.requestsBreakdown = false; }); } vm.fetchTrendingReports = function () { if (vm.graphType.selected == 'slow_report_graphs') { var report_type = 'slow'; } else { var report_type = 'error'; } vm.loading.reports = true; vm.trendingReports = applicationsPropertyResource.query({ 'key': 'trending_reports', 'resourceId': vm.resource, "start_date": timeSpanToStartDate(vm.timeSpan.key), "report_type": report_type }, function () { vm.loading.reports = false; }, function () { vm.loading.reports = false; } ) ; } if (AeUser.applications.length){ vm.show_dashboard = true; vm.determineStartState(); vm.refreshData(); } $scope.$on('$locationChangeSuccess', function () { console.log('$locationChangeSuccess IndexDashboardController'); if (vm.loading.series === false) { vm.determineStartState(); vm.refreshData(); } }); }