Skip to content

Commit 65f5b1e

Browse files
committed
Monitoring dashboard
1 parent c53773b commit 65f5b1e

23 files changed

+1041
-155
lines changed

app/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ <h1>JavaScript Required</h1>
185185
<script src="scripts/controllers/overview.js"></script>
186186
<script src="scripts/controllers/topology.js"></script>
187187
<script src="scripts/controllers/quota.js"></script>
188+
<script src="scripts/controllers/monitoring.js"></script>
188189
<script src="scripts/controllers/builds.js"></script>
189190
<script src="scripts/controllers/pipelines.js"></script>
190191
<script src="scripts/controllers/buildConfig.js"></script>
@@ -237,6 +238,7 @@ <h1>JavaScript Required</h1>
237238
<script src="scripts/directives/editLink.js"></script>
238239
<script src="scripts/directives/events.js"></script>
239240
<script src="scripts/directives/eventsSidebar.js"></script>
241+
<script src="scripts/directives/eventsBadge.js"></script>
240242
<script src="scripts/directives/fromFile.js"></script>
241243
<script src="scripts/directives/oscFileInput.js"></script>
242244
<script src="scripts/directives/oscFormSection.js"></script>

app/scripts/app.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ angular
5858
.when('/project/:project/quota', {
5959
templateUrl: 'views/quota.html',
6060
controller: 'QuotaController'
61-
})
61+
})
62+
.when('/project/:project/monitoring', {
63+
templateUrl: 'views/monitoring.html',
64+
controller: 'MonitoringController'
65+
})
6266
.when('/project/:project/browse', {
6367
redirectTo: function(params) {
6468
return '/project/' + encodeURIComponent(params.project) + "/browse/pods"; // TODO decide what subtab to default to here
@@ -336,7 +340,7 @@ angular
336340
LabelFilter.setLabelSelector(new LabelSelector({}, true), true);
337341
});
338342
})
339-
.run(function(dateRelativeFilter, durationFilter) {
343+
.run(function(dateRelativeFilter, durationFilter, timeOnlyDurationFromTimestampsFilter) {
340344
// Use setInterval instead of $interval because we're directly manipulating the DOM and don't want scope.$apply overhead
341345
setInterval(function() {
342346
// Set by relative-timestamp directive.
@@ -350,7 +354,13 @@ angular
350354
var timestamp = $(this).data("timestamp");
351355
var omitSingle = $(this).data("omit-single");
352356
var precision = $(this).data("precision");
353-
return durationFilter(timestamp, null, omitSingle, precision) || existing;
357+
var timeOnly = $(this).data("time-only");
358+
if (timeOnly) {
359+
return timeOnlyDurationFromTimestampsFilter(timestamp, null) || existing;
360+
}
361+
else {
362+
return durationFilter(timestamp, null, omitSingle, precision) || existing;
363+
}
354364
});
355365
}, 1000);
356366
});

app/scripts/constants.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ window.OPENSHIFT_CONSTANTS = {
154154
{
155155
label: "Monitoring",
156156
iconClass: "pficon pficon-screen",
157-
href: "/browse/events"
157+
href: "/monitoring",
158+
prefixes: [
159+
"/browse/events"
160+
]
158161
// TODO uncomment when we have at least one of metrics or logs pages and then take off the href going straight to events
159162
// secondaryNavSections: [
160163
// {

app/scripts/controllers/events.js

+10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ angular.module('openshiftConsole')
1414
hideFilterWidget: true
1515
};
1616

17+
$scope.breadcrumbs = [
18+
{
19+
title: "Monitoring",
20+
link: "project/" + $routeParams.project + "/monitoring"
21+
},
22+
{
23+
title: 'Events'
24+
}
25+
];
26+
1727
ProjectsService
1828
.get($routeParams.project)
1929
.then(_.spread(function(project, context) {

app/scripts/controllers/monitoring.js

+300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
'use strict';
2+
/* jshint unused: false */
3+
4+
/**
5+
* @ngdoc function
6+
* @name openshiftConsole.controller:MonitoringController
7+
* @description
8+
* # MonitoringController
9+
* Controller of the openshiftConsole
10+
*/
11+
angular.module('openshiftConsole')
12+
.controller('MonitoringController', function ($routeParams,
13+
$scope,
14+
$filter,
15+
DataService,
16+
ProjectsService,
17+
MetricsService,
18+
BuildsService,
19+
PodsService,
20+
Logger,
21+
ImageStreamResolver,
22+
$rootScope) {
23+
$scope.projectName = $routeParams.project;
24+
$scope.alerts = $scope.alerts || {};
25+
$scope.renderOptions = $scope.renderOptions || {};
26+
$scope.renderOptions.showEventsSidebar = true;
27+
28+
var watches = [];
29+
30+
$scope.kindSelector = {
31+
selected: {
32+
kind: "All"
33+
}
34+
};
35+
$scope.kinds = [
36+
{
37+
kind: "Pods"
38+
},
39+
{
40+
kind: "Builds"
41+
},
42+
{
43+
label: "Deployments",
44+
kind: "ReplicationControllers"
45+
},
46+
{
47+
kind: "All"
48+
}
49+
];
50+
51+
$scope.logOptions = {
52+
pods: {},
53+
deployments: {},
54+
builds: {}
55+
};
56+
57+
$scope.logCanRun = {
58+
pods: {},
59+
deployments: {},
60+
builds: {}
61+
};
62+
63+
$scope.logEmpty = {
64+
pods: {},
65+
deployments: {},
66+
builds: {}
67+
};
68+
69+
$scope.filters = {
70+
showOlderResources: false,
71+
text: ''
72+
};
73+
74+
var ageFilteredBuilds, ageFilteredDeployments, ageFilteredPods;
75+
76+
// Check if the metrics service is available so we know when to show the tab.
77+
MetricsService.isAvailable().then(function(available) {
78+
$scope.metricsAvailable = available;
79+
});
80+
81+
var orderByDate = $filter('orderObjectsByDate');
82+
83+
var filterExpressions = [];
84+
var updateKeywords = function() {
85+
if (!$scope.filters.text) {
86+
filterExpressions = [];
87+
return;
88+
}
89+
90+
var keywords = _.uniq($scope.filters.text.split(/\s+/));
91+
// Sort the longest keyword first.
92+
keywords.sort(function(a, b){
93+
return b.length - a.length;
94+
});
95+
96+
// Convert the keyword to a case-insensitive regular expression for the filter.
97+
filterExpressions = _.map(keywords, function(keyword) {
98+
return new RegExp(_.escapeRegExp(keyword), "i");
99+
});
100+
};
101+
102+
// Only filter by keyword on certain fields.
103+
var filterFields = [
104+
'metadata.name'
105+
];
106+
107+
var filterForKeyword = function(resources) {
108+
var filteredResources = resources;
109+
if (!filterExpressions.length) {
110+
return filteredResources;
111+
}
112+
113+
// Find events that match all keywords.
114+
angular.forEach(filterExpressions, function(regex) {
115+
var matchesKeyword = function(event) {
116+
var i;
117+
for (i = 0; i < filterFields.length; i++) {
118+
var value = _.get(event, filterFields[i]);
119+
if (value && regex.test(value)) {
120+
return true;
121+
}
122+
}
123+
124+
return false;
125+
};
126+
127+
filteredResources = _.filter(filteredResources, matchesKeyword);
128+
});
129+
return filteredResources;
130+
};
131+
132+
var filterAllResourcesForKeyword = function() {
133+
$scope.filteredPods = filterForKeyword(ageFilteredPods);
134+
$scope.filteredDeployments = filterForKeyword(ageFilteredDeployments);
135+
$scope.filteredBuilds = filterForKeyword(ageFilteredBuilds);
136+
};
137+
138+
var setPodLogVars = function(pod) {
139+
$scope.logOptions.pods[pod.metadata.name] = {
140+
container: pod.spec.containers[0].name
141+
};
142+
$scope.logCanRun.pods[pod.metadata.name] = !(_.includes(['New', 'Pending', 'Unknown'], pod.status.phase));
143+
};
144+
145+
var setDeploymentLogVars = function(deployment) {
146+
$scope.logOptions.deployments[deployment.metadata.name] = {
147+
container: $filter("annotation")(deployment, "pod")
148+
};
149+
var deploymentVersion = $filter("annotation")(deployment, "deploymentVersion");
150+
if (deploymentVersion) {
151+
$scope.logOptions.deployments[deployment.metadata.name].version = deploymentVersion;
152+
}
153+
$scope.logCanRun.deployments[deployment.metadata.name] = !(_.includes(['New', 'Pending'], $filter('deploymentStatus')(deployment)));
154+
};
155+
156+
var setBuildLogVars = function(build) {
157+
$scope.logOptions.builds[build.metadata.name] = {
158+
container: $filter("annotation")(build, "buildPod")
159+
};
160+
$scope.logCanRun.builds[build.metadata.name] = !(_.includes(['New', 'Pending', 'Error'], build.status.phase));
161+
};
162+
163+
var filterPods = function() {
164+
ageFilteredPods = _.filter($scope.pods, function(pod) {
165+
if ($scope.filters.showOlderResources) {
166+
return true;
167+
}
168+
if (pod.status.phase !== 'Succeeded' && pod.status.phase !== 'Failed') {
169+
return true;
170+
}
171+
return false;
172+
});
173+
};
174+
175+
var isIncompleteBuild = $filter('isIncompleteBuild');
176+
var buildConfigForBuild = $filter('buildConfigForBuild');
177+
var filterBuilds = function() {
178+
var fiveMinutesAgo = moment().subtract(5, 'm');
179+
ageFilteredBuilds = _.filter($scope.builds, function(build) {
180+
if ($scope.filters.showOlderResources) {
181+
return true;
182+
}
183+
if (isIncompleteBuild(build)) {
184+
return true;
185+
}
186+
var buildConfigName = buildConfigForBuild(build);
187+
if (buildConfigName) {
188+
return $scope.latestBuildByConfig[buildConfigName].metadata.name === build.metadata.name;
189+
}
190+
191+
// Otherwise this is a one-off build, keep any of those that finished in the last 5 minutes, if we
192+
// don't have a completionTimestamp for some reason then fallback to creationTimestamp
193+
var completed = moment(build.status.completionTimestamp || build.metadata.creationTimestamp);
194+
return completed.isAfter(fiveMinutesAgo);
195+
});
196+
};
197+
198+
var deploymentStatus = $filter('deploymentStatus');
199+
var filterDeployments = function() {
200+
ageFilteredDeployments = _.filter($scope.deployments, function(deployment) {
201+
if ($scope.filters.showOlderResources) {
202+
return true;
203+
}
204+
var status = deploymentStatus(deployment);
205+
if (status !== 'Complete' && status !== 'Failed') {
206+
return true;
207+
}
208+
return false;
209+
});
210+
};
211+
212+
$scope.toggleItem = function(resource, expanded) {
213+
var event = expanded ? 'event.resource.highlight' : 'event.resource.clear-highlight';
214+
$rootScope.$emit(event, resource);
215+
if (resource.kind === 'Build') {
216+
var buildPod = _.get($scope.podsByName, $filter('annotation')(resource, 'buildPod'));
217+
if (buildPod) {
218+
$rootScope.$emit(event, buildPod);
219+
}
220+
}
221+
if (resource.kind === 'ReplicationController') {
222+
var deployerPodName = $filter('annotation')(resource, 'deployerPod');
223+
if (deployerPodName) {
224+
// The deployer pod is deleted immediately so mock the resource to send to the event highlighter
225+
$rootScope.$emit(event, {
226+
kind: "Pod",
227+
metadata: {
228+
name: deployerPodName
229+
}
230+
});
231+
}
232+
_.each($scope.podsByDeployment[resource.metadata.name], function(pod) {
233+
$rootScope.$emit(event, pod);
234+
});
235+
}
236+
};
237+
238+
var groupPods = function() {
239+
if (!$scope.pods || !$scope.deployments) {
240+
return;
241+
}
242+
243+
$scope.podsByDeployment = PodsService.groupByReplicationController($scope.pods, $scope.deployments);
244+
};
245+
246+
ProjectsService
247+
.get($routeParams.project)
248+
.then(_.spread(function(project, context) {
249+
$scope.project = project;
250+
$scope.projectContext = context;
251+
252+
DataService.watch("pods", context, function(pods) {
253+
$scope.podsByName = pods.by("metadata.name");
254+
$scope.pods = orderByDate($scope.podsByName, true);
255+
groupPods();
256+
_.each($scope.pods, function(pod) {
257+
setPodLogVars(pod);
258+
});
259+
filterPods();
260+
Logger.log("pods", $scope.pods);
261+
});
262+
263+
DataService.watch("replicationcontrollers", context, function(deployments) {
264+
$scope.deployments = orderByDate(deployments.by("metadata.name"), true);
265+
groupPods();
266+
_.each($scope.deployments, function(deployment) {
267+
setDeploymentLogVars(deployment);
268+
});
269+
filterDeployments();
270+
Logger.log("deployments", $scope.deployments);
271+
});
272+
273+
DataService.watch("builds", context, function(builds) {
274+
$scope.builds = orderByDate(builds.by("metadata.name"), true);
275+
$scope.latestBuildByConfig = BuildsService.latestBuildByConfig($scope.builds);
276+
_.each($scope.builds, function(build) {
277+
setBuildLogVars(build);
278+
});
279+
filterBuilds();
280+
Logger.log("builds", $scope.builds);
281+
});
282+
283+
$scope.$on('$destroy', function(){
284+
DataService.unwatchAll(watches);
285+
});
286+
287+
$scope.$watchGroup(['filters.showOlderResources'], function() {
288+
filterPods();
289+
filterBuilds();
290+
filterDeployments();
291+
filterAllResourcesForKeyword();
292+
});
293+
294+
$scope.$watch('filters.text', _.debounce(function() {
295+
updateKeywords();
296+
$scope.$apply(filterAllResourcesForKeyword);
297+
}, 50, { maxWait: 250 }));
298+
299+
}));
300+
});

0 commit comments

Comments
 (0)