Skip to content

Instantly share code, notes, and snippets.

@fradaloisio
Created July 10, 2017 13:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fradaloisio/32d21004d347383464efbe13c4656866 to your computer and use it in GitHub Desktop.
Save fradaloisio/32d21004d347383464efbe13c4656866 to your computer and use it in GitHub Desktop.
/// <reference path="angular.d.ts" />
/// <reference path="angular-route.d.ts" />
/// <reference path="angular-sanitize.d.ts" />
/// <reference path="bootstrap.d.ts" />
/// <reference path="moment.d.ts" />
/// <reference path="moment-duration-format.d.ts" />
/// <reference path="d3.d.ts" />
/// <reference path="underscore.d.ts" />
var bosunApp = angular.module('bosunApp', [
'ngRoute',
'bosunControllers',
'mgcrea.ngStrap',
'ngSanitize',
'ui.ace',
]);
bosunApp.config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
$routeProvider.
when('/alerting/', {
title: 'Dashboard',
templateUrl: 'partials/dashboard.html',
controller: 'DashboardCtrl'
}).
when('/alerting/items', {
title: 'Items',
templateUrl: 'partials/items.html',
controller: 'ItemsCtrl'
}).
when('/alerting/expr', {
title: 'Expression',
templateUrl: 'partials/expr.html',
controller: 'ExprCtrl'
}).
when('/alerting/errors', {
title: 'Errors',
templateUrl: 'partials/errors.html',
controller: 'ErrorCtrl'
}).
when('/alerting/graph', {
title: 'Graph',
templateUrl: 'partials/graph.html',
controller: 'GraphCtrl'
}).
when('/alerting/host', {
title: 'Host View',
templateUrl: 'partials/host.html',
controller: 'HostCtrl',
reloadOnSearch: false
}).
when('/alerting/silence', {
title: 'Silence',
templateUrl: 'partials/silence.html',
controller: 'SilenceCtrl'
}).
when('/alerting/config', {
title: 'Configuration',
templateUrl: 'partials/config.html',
controller: 'ConfigCtrl',
reloadOnSearch: false
}).
when('/alerting/action', {
title: 'Action',
templateUrl: 'partials/action.html',
controller: 'ActionCtrl'
}).
when('/alerting/history', {
title: 'Alert History',
templateUrl: 'partials/history.html',
controller: 'HistoryCtrl'
}).
when('/alerting/put', {
title: 'Data Entry',
templateUrl: 'partials/put.html',
controller: 'PutCtrl'
}).
when('/alerting/annotation', {
title: 'Annotation',
templateUrl: 'partials/annotation.html',
controller: 'AnnotationCtrl'
}).
when('/alerting/incident', {
title: 'Incident',
templateUrl: 'partials/incident.html',
controller: 'IncidentCtrl'
}).
otherwise({
redirectTo: '/alerting/'
});
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (config) {
config.headers['X-Miniprofiler'] = 'true';
return config;
}
};
});
}]);
bosunApp.run(['$location', '$rootScope', function ($location, $rootScope) {
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
$rootScope.title = current.$$route.title;
$rootScope.shortlink = false;
});
}]);
var bosunControllers = angular.module('bosunControllers', []);
bosunControllers.controller('BosunCtrl', ['$scope', '$route', '$http', '$q', '$rootScope', function ($scope, $route, $http, $q, $rootScope) {
$scope.$on('$routeChangeSuccess', function (event, current, previous) {
$scope.stop(true);
});
$scope.active = function (v) {
if (!$route.current) {
return null;
}
if ($route.current.loadedTemplateUrl == 'partials/' + v + '.html') {
return { active: true };
}
return null;
};
$http.get("/alerting/api/annotate")
.success(function (data) {
$scope.annotateEnabled = data;
})
.error(function (data) {
console.log(data);
});
$http.get("/alerting/api/opentsdb/version")
.success(function (data) {
$scope.version = data;
$scope.opentsdbEnabled = $scope.version.Major != 0 && $scope.version.Minor != 0;
})
.error(function (data) {
console.log(data);
});
;
$scope.json = function (v) {
return JSON.stringify(v, null, ' ');
};
$scope.btoa = function (v) {
return encodeURIComponent(btoa(v));
};
$scope.encode = function (v) {
return encodeURIComponent(v);
};
$scope.req_from_m = function (m) {
var r = new Request();
var q = new Query(false);
q.metric = m;
r.queries.push(q);
return r;
};
$scope.panelClass = function (status, prefix) {
if (prefix === void 0) { prefix = "panel-"; }
switch (status) {
case "critical": return prefix + "danger";
case "unknown": return prefix + "info";
case "warning": return prefix + "warning";
case "normal": return prefix + "success";
case "error": return prefix + "danger";
default: return prefix + "default";
}
};
$scope.values = {};
$scope.setKey = function (key, value) {
if (value === undefined) {
delete $scope.values[key];
}
else {
$scope.values[key] = value;
}
};
$scope.getKey = function (key) {
return $scope.values[key];
};
var scheduleFilter;
$scope.refresh = function (filter) {
var d = $q.defer();
scheduleFilter = filter;
$scope.animate();
var p = $http.get('/alerting/api/alerts?filter=' + encodeURIComponent(filter || ""))
.success(function (data) {
$scope.schedule = data;
$scope.timeanddate = data.TimeAndDate;
d.resolve();
})
.error(function (err) {
d.reject(err);
});
p.finally($scope.stop);
return d.promise;
};
var sz = 30;
var orig = 700;
var light = '#4ba2d9';
var dark = '#1f5296';
var med = '#356eb6';
var mult = sz / orig;
var bgrad = 25 * mult;
var circles = [
[150, 150, dark],
[550, 150, dark],
[150, 550, light],
[550, 550, light],
];
var svg = d3.select('#logo')
.append('svg')
.attr('height', sz)
.attr('width', sz);
svg.selectAll('rect.bg')
.data([[0, light], [sz / 2, dark]])
.enter()
.append('rect')
.attr('class', 'bg')
.attr('width', sz)
.attr('height', sz / 2)
.attr('rx', bgrad)
.attr('ry', bgrad)
.attr('fill', function (d) { return d[1]; })
.attr('y', function (d) { return d[0]; });
svg.selectAll('path.diamond')
.data([150, 550])
.enter()
.append('path')
.attr('d', function (d) {
var s = 'M ' + d * mult + ' ' + 150 * mult;
var w = 200 * mult;
s += ' l ' + w + ' ' + w;
s += ' l ' + -w + ' ' + w;
s += ' l ' + -w + ' ' + -w + ' Z';
return s;
})
.attr('fill', med)
.attr('stroke', 'white')
.attr('stroke-width', 15 * mult);
svg.selectAll('rect.white')
.data([150, 350, 550])
.enter()
.append('rect')
.attr('class', 'white')
.attr('width', .5)
.attr('height', '100%')
.attr('fill', 'white')
.attr('x', function (d) { return d * mult; });
svg.selectAll('circle')
.data(circles)
.enter()
.append('circle')
.attr('cx', function (d) { return d[0] * mult; })
.attr('cy', function (d) { return d[1] * mult; })
.attr('r', 62.5 * mult)
.attr('fill', function (d) { return d[2]; })
.attr('stroke', 'white')
.attr('stroke-width', 25 * mult);
var transitionDuration = 750;
var animateCount = 0;
$scope.animate = function () {
animateCount++;
if (animateCount == 1) {
doAnimate();
}
};
function doAnimate() {
if (!animateCount) {
return;
}
d3.shuffle(circles);
svg.selectAll('circle')
.data(circles, function (d, i) { return i; })
.transition()
.duration(transitionDuration)
.attr('cx', function (d) { return d[0] * mult; })
.attr('cy', function (d) { return d[1] * mult; })
.attr('fill', function (d) { return d[2]; });
setTimeout(doAnimate, transitionDuration);
}
$scope.stop = function (all) {
if (all === void 0) { all = false; }
if (all) {
animateCount = 0;
}
else if (animateCount > 0) {
animateCount--;
}
};
var short = $('#shortlink')[0];
$scope.shorten = function () {
$http.get('/alerting/api/shorten').success(function (data) {
if (data.id) {
short.value = data.id;
$rootScope.shortlink = true;
setTimeout(function () {
short.setSelectionRange(0, data.id.length);
});
}
});
};
}]);
var tsdbDateFormat = 'YYYY/MM/DD-HH:mm:ss';
moment.defaultFormat = tsdbDateFormat;
moment.locale('en', {
relativeTime: {
future: "in %s",
past: "%s-ago",
s: "%ds",
m: "%dm",
mm: "%dm",
h: "%dh",
hh: "%dh",
d: "%dd",
dd: "%dd",
M: "%dn",
MM: "%dn",
y: "%dy",
yy: "%dy"
}
});
function createCookie(name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
document.cookie = escape(name) + "=" + escape(value) + expires + "; path=/";
}
function readCookie(name) {
var nameEQ = escape(name) + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ')
c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0)
return unescape(c.substring(nameEQ.length, c.length));
}
return null;
}
function eraseCookie(name) {
createCookie(name, "", -1);
}
function getUser() {
return readCookie('action-user');
}
function setUser(name) {
createCookie('action-user', name, 1000);
}
function getOwner() {
return readCookie('action-owner');
}
function setOwner(name) {
createCookie('action-owner', name, 1000);
}
function getShowAnnotations() {
return readCookie('annotations-show');
}
function setShowAnnotations(yes) {
createCookie('annotations-show', yes, 1000);
}
// from: http://stackoverflow.com/a/15267754/864236
bosunApp.filter('reverse', function () {
return function (items) {
if (!angular.isArray(items)) {
return [];
}
return items.slice().reverse();
};
});
var timeFormat = 'YYYY-MM-DDTHH:mm:ssZ';
var Annotation = (function () {
function Annotation(a, get) {
a = a || {};
this.Id = a.Id || "";
this.Message = a.Message || "";
this.StartDate = a.StartDate || "";
this.EndDate = a.EndDate || "";
this.CreationUser = a.CreationUser || !get && getUser() || "";
this.Url = a.Url || "";
this.Source = a.Source || "bosun-ui";
this.Host = a.Host || "";
this.Owner = a.Owner || !get && getOwner() || "";
this.Category = a.Category || "";
}
Annotation.prototype.setTimeUTC = function () {
var now = moment().utc().format(timeFormat);
this.StartDate = now;
this.EndDate = now;
};
Annotation.prototype.setTime = function () {
var now = moment().format(timeFormat);
this.StartDate = now;
this.EndDate = now;
};
return Annotation;
})();
bosunControllers.controller('ActionCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
$scope.user = readCookie("action-user");
$scope.type = search.type;
$scope.notify = true;
$scope.msgValid = true;
$scope.message = "";
$scope.validateMsg = function () {
$scope.msgValid = (!$scope.notify) || ($scope.message != "");
};
if (search.key) {
var keys = search.key;
if (!angular.isArray(search.key)) {
keys = [search.key];
}
$location.search('key', null);
$scope.setKey('action-keys', keys);
}
else {
$scope.keys = $scope.getKey('action-keys');
}
$scope.submit = function () {
$scope.validateMsg();
if (!$scope.msgValid || ($scope.user == "")) {
return;
}
var data = {
Type: $scope.type,
User: $scope.user,
Message: $scope.message,
Keys: $scope.keys,
Notify: $scope.notify
};
createCookie("action-user", $scope.user, 1000);
$http.post('/alerting/api/action', data)
.success(function (data) {
$location.url('/alerting/');
})
.error(function (error) {
alert(error);
});
};
}]);
bosunControllers.controller('AnnotationCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
$scope.id = search.id;
if ($scope.id && $scope.id != "") {
$http.get('/alerting/api/annotation/' + $scope.id)
.success(function (data) {
$scope.annotation = new Annotation(data, true);
$scope.error = "";
})
.error(function (data) {
$scope.error = "failed to get annotation with id: " + $scope.id + ", error: " + data;
});
}
else {
$scope.annotation = new Annotation();
$scope.annotation.setTimeUTC();
}
$http.get('/alerting/api/annotation/values/Owner')
.success(function (data) {
$scope.owners = data;
});
$http.get('/alerting/api/annotation/values/Category')
.success(function (data) {
$scope.categories = data;
});
$http.get('/alerting/api/annotation/values/Host')
.success(function (data) {
$scope.hosts = data;
});
$scope.submitAnnotation = function () { return $http.post('/alerting/api/annotation', $scope.annotation)
.success(function (data) {
$scope.annotation = new Annotation(data, true);
$scope.error = "";
$scope.submitSuccess = true;
$scope.deleteSuccess = false;
})
.error(function (error) {
$scope.error = error;
$scope.submitSuccess = false;
}); };
$scope.deleteAnnotation = function () { return $http.delete('/alerting/annotation/' + $scope.annotation.Id)
.success(function (data) {
$scope.error = "";
$scope.deleteSuccess = true;
$scope.submitSuccess = false;
$scope.annotation = new (Annotation);
$scope.annotation.setTimeUTC();
})
.error(function (error) {
$scope.error = "failed to delete annotation with id: " + $scope.annotation.Id + ", error: " + error;
$scope.deleteSuccess = false;
}); };
}]);
bosunControllers.controller('ConfigCtrl', ['$scope', '$http', '$location', '$route', '$timeout', '$sce', function ($scope, $http, $location, $route, $timeout, $sce) {
var search = $location.search();
$scope.fromDate = search.fromDate || '';
$scope.fromTime = search.fromTime || '';
$scope.toDate = search.toDate || '';
$scope.toTime = search.toTime || '';
$scope.intervals = +search.intervals || 5;
$scope.duration = +search.duration || null;
$scope.config_text = 'Loading config...';
$scope.selected_alert = search.alert || '';
$scope.email = search.email || '';
$scope.template_group = search.template_group || '';
$scope.items = parseItems();
$scope.tab = search.tab || 'results';
$scope.aceTheme = 'chrome';
$scope.aceMode = 'bosun';
var expr = search.expr;
function buildAlertFromExpr() {
if (!expr)
return;
var newAlertName = "test";
var idx = 1;
//find a unique alert name
while ($scope.items["alert"].indexOf(newAlertName) != -1 || $scope.items["template"].indexOf(newAlertName) != -1) {
newAlertName = "test" + idx;
idx++;
}
var text = '\n\ntemplate ' + newAlertName + ' {\n' +
' subject = {{.Last.Status}}: {{.Alert.Name}} on {{.Group.host}}\n' +
' body = `<p>Name: {{.Alert.Name}}\n' +
' <p>Tags:\n' +
' <table>\n' +
' {{range $k, $v := .Group}}\n' +
' <tr><td>{{$k}}</td><td>{{$v}}</td></tr>\n' +
' {{end}}\n' +
' </table>`\n' +
'}\n\n';
var expression = atob(expr);
var lines = expression.split("\n").map(function (l) { return l.trim(); });
lines[lines.length - 1] = "crit = " + lines[lines.length - 1];
expression = lines.join("\n ");
text += 'alert ' + newAlertName + ' {\n' +
' template = ' + newAlertName + '\n' +
' ' + expression + '\n' +
'}\n';
$scope.config_text += text;
$scope.items = parseItems();
$timeout(function () {
//can't scroll editor until after control is updated. Defer it.
$scope.scrollTo("alert", newAlertName);
});
}
function parseItems() {
var configText = $scope.config_text;
var re = /^\s*(alert|template|notification|lookup|macro)\s+([\w\-\.\$]+)\s*\{/gm;
var match;
var items = {};
items["alert"] = [];
items["template"] = [];
items["lookup"] = [];
items["notification"] = [];
items["macro"] = [];
while (match = re.exec(configText)) {
var type = match[1];
var name = match[2];
var list = items[type];
if (!list) {
list = [];
items[type] = list;
}
list.push(name);
}
return items;
}
$http.get('/alerting/api/config?hash=' + (search.hash || ''))
.success(function (data) {
$scope.config_text = data;
$scope.items = parseItems();
buildAlertFromExpr();
if (!$scope.selected_alert && $scope.items["alert"].length) {
$scope.selected_alert = $scope.items["alert"][0];
}
$timeout(function () {
//can't scroll editor until after control is updated. Defer it.
$scope.scrollTo("alert", $scope.selected_alert);
});
})
.error(function (data) {
$scope.validationResult = "Error fetching config: " + data;
});
$scope.reparse = function () {
$scope.items = parseItems();
};
var editor;
$scope.aceLoaded = function (_editor) {
editor = _editor;
$scope.editor = editor;
editor.getSession().setUseWrapMode(true);
editor.on("blur", function () {
$scope.$apply(function () {
$scope.items = parseItems();
});
});
};
var syntax = true;
$scope.aceToggleHighlight = function () {
if (syntax) {
editor.getSession().setMode();
syntax = false;
return;
}
syntax = true;
editor.getSession().setMode({
path: 'ace/mode/' + $scope.aceMode,
v: Date.now()
});
};
$scope.scrollTo = function (type, name) {
var searchRegex = new RegExp("^\\s*" + type + "\\s+" + name, "g");
editor.find(searchRegex, {
backwards: false,
wrap: true,
caseSensitive: false,
wholeWord: false,
regExp: true
});
if (type == "alert") {
$scope.selectAlert(name);
}
};
$scope.scrollToInterval = function (id) {
document.getElementById('time-' + id).scrollIntoView();
$scope.show($scope.sets[id]);
};
$scope.show = function (set) {
set.show = 'loading...';
$scope.animate();
var url = '/alerting/rule?' +
'alert=' + encodeURIComponent($scope.selected_alert) +
'&from=' + encodeURIComponent(set.Time);
$http.post(url, $scope.config_text)
.success(function (data) {
procResults(data);
set.Results = data.Sets[0].Results;
})
.error(function (error) {
$scope.error = error;
})
.finally(function () {
$scope.stop();
delete (set.show);
});
};
$scope.setInterval = function () {
var from = moment.utc($scope.fromDate + ' ' + $scope.fromTime);
var to = moment.utc($scope.toDate + ' ' + $scope.toTime);
if (!from.isValid() || !to.isValid()) {
return;
}
var diff = from.diff(to);
if (!diff) {
return;
}
var intervals = +$scope.intervals;
if (intervals < 2) {
return;
}
diff /= 1000 * 60;
var d = Math.abs(Math.round(diff / intervals));
if (d < 1) {
d = 1;
}
$scope.duration = d;
};
$scope.setDuration = function () {
var from = moment.utc($scope.fromDate + ' ' + $scope.fromTime);
var to = moment.utc($scope.toDate + ' ' + $scope.toTime);
if (!from.isValid() || !to.isValid()) {
return;
}
var diff = from.diff(to);
if (!diff) {
return;
}
var duration = +$scope.duration;
if (duration < 1) {
return;
}
$scope.intervals = Math.abs(Math.round(diff / duration / 1000 / 60));
};
$scope.selectAlert = function (alert) {
$scope.selected_alert = alert;
$location.search("alert", alert);
// Attempt to find `template = foo` in order to set up quick jump between template and alert
var searchRegex = new RegExp("^\\s*alert\\s+" + alert, "g");
var lines = $scope.config_text.split("\n");
$scope.quickJumpTarget = null;
for (var i = 0; i < lines.length; i++) {
if (searchRegex.test(lines[i])) {
for (var j = i + 1; j < lines.length; j++) {
// Close bracket at start of line means end of alert.
if (/^\s*\}/m.test(lines[j])) {
return;
}
var found = /^\s*template\s*=\s*([\w\-\.\$]+)/m.exec(lines[j]);
if (found) {
$scope.quickJumpTarget = "template " + found[1];
}
}
}
}
};
$scope.quickJump = function () {
var parts = $scope.quickJumpTarget.split(" ");
if (parts.length != 2) {
return;
}
$scope.scrollTo(parts[0], parts[1]);
if (parts[0] == "template" && $scope.selected_alert) {
$scope.quickJumpTarget = "alert " + $scope.selected_alert;
}
};
$scope.setTemplateGroup = function (group) {
var match = group.match(/{(.*)}/);
if (match) {
$scope.template_group = match[1];
}
};
var line_re = /test:(\d+)/;
$scope.validate = function () {
$http.post('/alerting/api/config_test', $scope.config_text)
.success(function (data) {
if (data == "") {
$scope.validationResult = "Valid";
$timeout(function () {
$scope.validationResult = "";
}, 2000);
}
else {
$scope.validationResult = data;
var m = data.match(line_re);
if (angular.isArray(m) && (m.length > 1)) {
editor.gotoLine(m[1]);
}
}
})
.error(function (error) {
$scope.validationResult = 'Error validating: ' + error;
});
};
$scope.test = function () {
$scope.error = '';
$scope.running = true;
$scope.warning = [];
$location.search('fromDate', $scope.fromDate || null);
$location.search('fromTime', $scope.fromTime || null);
$location.search('toDate', $scope.toDate || null);
$location.search('toTime', $scope.toTime || null);
$location.search('intervals', String($scope.intervals) || null);
$location.search('duration', String($scope.duration) || null);
$location.search('email', $scope.email || null);
$location.search('template_group', $scope.template_group || null);
$scope.animate();
var from = moment.utc($scope.fromDate + ' ' + $scope.fromTime);
var to = moment.utc($scope.toDate + ' ' + $scope.toTime);
if (!from.isValid()) {
from = to;
}
if (!to.isValid()) {
to = from;
}
if (!from.isValid() && !to.isValid()) {
from = to = moment.utc();
}
var diff = from.diff(to);
var intervals;
if (diff == 0) {
intervals = 1;
}
else if (Math.abs(diff) < 60 * 1000) {
intervals = 2;
}
else {
intervals = +$scope.intervals;
}
var url = '/alerting/rule?' +
'alert=' + encodeURIComponent($scope.selected_alert) +
'&from=' + encodeURIComponent(from.format()) +
'&to=' + encodeURIComponent(to.format()) +
'&intervals=' + encodeURIComponent(intervals) +
'&email=' + encodeURIComponent($scope.email) +
'&template_group=' + encodeURIComponent($scope.template_group);
$http.post(url, $scope.config_text)
.success(function (data) {
$scope.sets = data.Sets;
$scope.alert_history = data.AlertHistory;
if (data.Hash) {
$location.search('hash', data.Hash);
}
procResults(data);
})
.error(function (error) {
$scope.error = error;
})
.finally(function () {
$scope.running = false;
$scope.stop();
});
};
$scope.zws = function (v) {
return v.replace(/([,{}()])/g, '$1\u200b');
};
$scope.loadTimelinePanel = function (entry, v) {
if (v.doneLoading && !v.error) {
return;
}
v.error = null;
v.doneLoading = false;
var ak = entry.key;
var openBrack = ak.indexOf("{");
var closeBrack = ak.indexOf("}");
var alertName = ak.substr(0, openBrack);
var template = ak.substring(openBrack + 1, closeBrack);
var url = '/alerting/rule?' +
'alert=' + encodeURIComponent(alertName) +
'&from=' + encodeURIComponent(moment.utc(v.Time).format()) +
'&template_group=' + encodeURIComponent(template);
$http.post(url, $scope.config_text)
.success(function (data) {
v.subject = data.Subject;
v.body = $sce.trustAsHtml(data.Body);
})
.error(function (error) {
v.error = error;
})
.finally(function () {
v.doneLoading = true;
});
};
function procResults(data) {
$scope.subject = data.Subject;
$scope.body = $sce.trustAsHtml(data.Body);
$scope.data = JSON.stringify(data.Data, null, ' ');
$scope.error = data.Errors;
$scope.warning = data.Warnings;
}
$scope.downloadConfig = function () {
var blob = new Blob([$scope.config_text], { type: "text/plain;charset=utf-8" });
saveAs(blob, "bosun.conf");
};
return $scope;
}]);
bosunControllers.controller('DashboardCtrl', ['$scope', '$http', '$location', function ($scope, $http, $location) {
var search = $location.search();
$scope.loading = 'Loading';
$scope.error = '';
$scope.filter = search.filter;
if (!$scope.filter) {
$scope.filter = readCookie("filter");
}
$location.search('filter', $scope.filter || null);
reload();
function reload() {
$scope.refresh($scope.filter).then(function () {
$scope.loading = '';
$scope.error = '';
}, function (err) {
$scope.loading = '';
$scope.error = 'Unable to fetch alerts: ' + err;
});
}
$scope.keydown = function ($event) {
if ($event.keyCode == 13) {
createCookie("filter", $scope.filter || "", 1000);
$location.search('filter', $scope.filter || null);
}
};
}]);
bosunApp.directive('tsResults', function () {
return {
templateUrl: '/partials/results.html',
link: function (scope, elem, attrs) {
scope.isSeries = function (v) {
return typeof (v) === 'object';
};
}
};
});
bosunApp.directive('tsComputations', function () {
return {
scope: {
computations: '=tsComputations',
time: '=',
header: '='
},
templateUrl: '/partials/computations.html',
link: function (scope, elem, attrs) {
if (scope.time) {
var m = moment.utc(scope.time);
scope.timeParam = "&date=" + encodeURIComponent(m.format("YYYY-MM-DD")) + "&time=" + encodeURIComponent(m.format("HH:mm"));
}
scope.btoa = function (v) {
return encodeURIComponent(btoa(v));
};
}
};
});
function fmtDuration(v) {
var diff = moment.duration(v, 'milliseconds');
var f;
if (Math.abs(v) < 60000) {
return diff.format('ss[s]');
}
return diff.format('d[d]hh[h]mm[m]ss[s]');
}
function fmtTime(v) {
var m = moment(v).utc();
var now = moment().utc();
var msdiff = now.diff(m);
var ago = '';
var inn = '';
if (msdiff >= 0) {
ago = ' ago';
}
else {
inn = 'in ';
}
return m.format() + ' (' + inn + fmtDuration(msdiff) + ago + ')';
}
function parseDuration(v) {
var pattern = /(\d+)(d|y|n|h|m|s)-ago/;
var m = pattern.exec(v);
return moment.duration(parseInt(m[1]), m[2].replace('n', 'M'));
}
bosunApp.directive("tsTime", function () {
return {
link: function (scope, elem, attrs) {
scope.$watch(attrs.tsTime, function (v) {
var m = moment(v).utc();
var text = fmtTime(v);
if (attrs.tsEndTime) {
var diff = moment(scope.$eval(attrs.tsEndTime)).diff(m);
var duration = fmtDuration(diff);
text += " for " + duration;
}
if (attrs.noLink) {
elem.text(text);
}
else {
var el = document.createElement('a');
el.text = text;
el.href = 'http://www.timeanddate.com/worldclock/converted.html?iso=';
el.href += m.format('YYYYMMDDTHHmm');
el.href += '&p1=0';
angular.forEach(scope.timeanddate, function (v, k) {
el.href += '&p' + (k + 2) + '=' + v;
});
elem.html(el);
}
});
}
};
});
bosunApp.directive("tsSince", function () {
return {
link: function (scope, elem, attrs) {
scope.$watch(attrs.tsSince, function (v) {
var m = moment(v).utc();
elem.text(m.fromNow());
});
}
};
});
bosunApp.directive("tooltip", function () {
return {
link: function (scope, elem, attrs) {
angular.element(elem[0]).tooltip({ placement: "bottom" });
}
};
});
bosunApp.directive('tsTab', function () {
return {
link: function (scope, elem, attrs) {
var ta = elem[0];
elem.keydown(function (evt) {
if (evt.ctrlKey) {
return;
}
// This is so shift-enter can be caught to run a rule when tsTab is called from
// the rule page
if (evt.keyCode == 13 && evt.shiftKey) {
return;
}
switch (evt.keyCode) {
case 9:
evt.preventDefault();
var v = ta.value;
var start = ta.selectionStart;
ta.value = v.substr(0, start) + "\t" + v.substr(start);
ta.selectionStart = ta.selectionEnd = start + 1;
return;
case 13:
if (ta.selectionStart != ta.selectionEnd) {
return;
}
evt.preventDefault();
var v = ta.value;
var start = ta.selectionStart;
var sub = v.substr(0, start);
var last = sub.lastIndexOf("\n") + 1;
for (var i = last; i < sub.length && /[ \t]/.test(sub[i]); i++)
;
var ws = sub.substr(last, i - last);
ta.value = v.substr(0, start) + "\n" + ws + v.substr(start);
ta.selectionStart = ta.selectionEnd = start + 1 + ws.length;
}
});
}
};
});
bosunApp.directive('tsresizable', function () {
return {
restrict: 'A',
scope: {
callback: '&onResize'
},
link: function postLink(scope, elem, attrs) {
elem.resizable();
elem.on('resizestop', function (evt, ui) {
if (scope.callback) {
scope.callback();
}
});
}
};
});
bosunApp.directive('tsTableSort', ['$timeout', function ($timeout) {
return {
link: function (scope, elem, attrs) {
$timeout(function () {
$(elem).tablesorter({
sortList: scope.$eval(attrs.tsTableSort)
});
});
}
};
}]);
bosunApp.directive('tsTimeLine', function () {
var tsdbFormat = d3.time.format.utc("%Y/%m/%d-%X");
function parseDate(s) {
return moment.utc(s).toDate();
}
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 250
};
return {
link: function (scope, elem, attrs) {
scope.shown = {};
scope.collapse = function (i, entry, v) {
scope.shown[i] = !scope.shown[i];
if (scope.loadTimelinePanel && entry && scope.shown[i]) {
scope.loadTimelinePanel(entry, v);
}
};
scope.$watch('alert_history', update);
function update(history) {
if (!history) {
return;
}
var entries = d3.entries(history);
if (!entries.length) {
return;
}
entries.sort(function (a, b) {
return a.key.localeCompare(b.key);
});
scope.entries = entries;
var values = entries.map(function (v) { return v.value; });
var keys = entries.map(function (v) { return v.key; });
var barheight = 500 / values.length;
barheight = Math.min(barheight, 45);
barheight = Math.max(barheight, 15);
var svgHeight = values.length * barheight + margin.top + margin.bottom;
var height = svgHeight - margin.top - margin.bottom;
var svgWidth = elem.width();
var width = svgWidth - margin.left - margin.right;
var xScale = d3.time.scale.utc().range([0, width]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
elem.empty();
var svg = d3.select(elem[0])
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'x axis tl-axis')
.attr('transform', 'translate(0,' + height + ')');
xScale.domain([
d3.min(values, function (d) { return d3.min(d.History, function (c) { return parseDate(c.Time); }); }),
d3.max(values, function (d) { return d3.max(d.History, function (c) { return parseDate(c.EndTime); }); }),
]);
var legend = d3.select(elem[0])
.append('div')
.attr('class', 'tl-legend');
var time_legend = legend
.append('div')
.text(values[0].History[0].Time);
var alert_legend = legend
.append('div')
.text(keys[0]);
svg.select('.x.axis')
.transition()
.call(xAxis);
var chart = svg.append('g');
angular.forEach(entries, function (entry, i) {
chart.selectAll('.bars')
.data(entry.value.History)
.enter()
.append('rect')
.attr('class', function (d) { return 'tl-' + d.Status; })
.attr('x', function (d) { return xScale(parseDate(d.Time)); })
.attr('y', i * barheight)
.attr('height', barheight)
.attr('width', function (d) {
return xScale(parseDate(d.EndTime)) - xScale(parseDate(d.Time));
})
.on('mousemove.x', mousemove_x)
.on('mousemove.y', function (d) {
alert_legend.text(entry.key);
})
.on('click', function (d, j) {
var id = 'panel' + i + '-' + j;
scope.shown['group' + i] = true;
scope.shown[id] = true;
if (scope.loadTimelinePanel) {
scope.loadTimelinePanel(entry, d);
}
scope.$apply();
setTimeout(function () {
var e = $("#" + id);
if (!e) {
console.log('no', id, e);
return;
}
$('html, body').scrollTop(e.offset().top);
});
});
});
chart.selectAll('.labels')
.data(keys)
.enter()
.append('text')
.attr('text-anchor', 'end')
.attr('x', 0)
.attr('dx', '-.5em')
.attr('dy', '.25em')
.attr('y', function (d, i) { return (i + .5) * barheight; })
.text(function (d) { return d; });
chart.selectAll('.sep')
.data(values)
.enter()
.append('rect')
.attr('y', function (d, i) { return (i + 1) * barheight; })
.attr('height', 1)
.attr('x', 0)
.attr('width', width)
.on('mousemove.x', mousemove_x);
function mousemove_x() {
var x = xScale.invert(d3.mouse(this)[0]);
time_legend
.text(tsdbFormat(x));
}
}
;
}
};
});
var fmtUnits = ['', 'k', 'M', 'G', 'T', 'P', 'E'];
function nfmt(s, mult, suffix, opts) {
opts = opts || {};
var n = parseFloat(s);
if (isNaN(n) && typeof s === 'string') {
return s;
}
if (opts.round)
n = Math.round(n);
if (!n)
return suffix ? '0 ' + suffix : '0';
if (isNaN(n) || !isFinite(n))
return '-';
var a = Math.abs(n);
if (a >= 1) {
var number = Math.floor(Math.log(a) / Math.log(mult));
a /= Math.pow(mult, Math.floor(number));
if (fmtUnits[number]) {
suffix = fmtUnits[number] + suffix;
}
}
var r = a.toFixed(5);
if (a < 1e-5) {
r = a.toString();
}
var neg = n < 0 ? '-' : '';
return neg + (+r) + suffix;
}
bosunApp.filter('nfmt', function () {
return function (s) {
return nfmt(s, 1000, '', {});
};
});
bosunApp.filter('bytes', function () {
return function (s) {
return nfmt(s, 1024, 'B', { round: true });
};
});
bosunApp.filter('bits', function () {
return function (s) {
return nfmt(s, 1024, 'b', { round: true });
};
});
bosunApp.directive('elastic', [
'$timeout',
function ($timeout) {
return {
restrict: 'A',
link: function ($scope, element) {
$scope.initialHeight = $scope.initialHeight || element[0].style.height;
var resize = function () {
element[0].style.height = $scope.initialHeight;
element[0].style.height = "" + element[0].scrollHeight + "px";
};
element.on("input change", resize);
$timeout(resize, 0);
}
};
}
]);
bosunApp.directive('tsBar', ['$window', 'nfmtFilter', function ($window, fmtfilter) {
var margin = {
top: 20,
right: 20,
bottom: 0,
left: 200
};
return {
scope: {
data: '=',
height: '='
},
link: function (scope, elem, attrs) {
var svgHeight = +scope.height || 150;
var height = svgHeight - margin.top - margin.bottom;
var svgWidth;
var width;
var xScale = d3.scale.linear();
var yScale = d3.scale.ordinal();
var top = d3.select(elem[0])
.append('svg')
.attr('height', svgHeight)
.attr('width', '100%');
var svg = top
.append('g');
//.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("top");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
scope.$watch('data', update);
var w = angular.element($window);
scope.$watch(function () {
return w.width();
}, resize, true);
w.bind('resize', function () {
scope.$apply();
});
function resize() {
if (!scope.data) {
return;
}
svgWidth = elem.width();
if (svgWidth <= 0) {
return;
}
margin.left = d3.max(scope.data, function (d) { return d.name.length * 8; });
width = svgWidth - margin.left - margin.right;
svgHeight = scope.data.length * 15;
height = svgHeight - margin.top - margin.bottom;
xScale.range([0, width]);
yScale.rangeRoundBands([0, height], .1);
yAxis.scale(yScale);
svg.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.attr('width', svgWidth);
svg.attr('height', height);
top.attr('height', svgHeight);
xAxis.ticks(width / 60);
draw();
}
function update(v) {
if (!angular.isArray(v) || v.length == 0) {
return;
}
resize();
}
function draw() {
if (!scope.data) {
return;
}
yScale.domain(scope.data.map(function (d) { return d.name; }));
xScale.domain([0, d3.max(scope.data, function (d) { return d.Value; })]);
svg.selectAll('g.axis').remove();
//X axis
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style("text-anchor", "end");
var bars = svg.selectAll(".bar").data(scope.data);
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function (d) { return yScale(d.name); })
.attr("height", yScale.rangeBand())
.attr('width', function (d) { return xScale(d.Value); });
}
;
}
};
}]);
bosunApp.directive('tsGraph', ['$window', 'nfmtFilter', function ($window, fmtfilter) {
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 80
};
return {
scope: {
data: '=',
annotations: '=',
height: '=',
generator: '=',
brushStart: '=bstart',
brushEnd: '=bend',
enableBrush: '@',
max: '=',
min: '=',
normalize: '=',
annotation: '=',
annotateEnabled: '=',
showAnnotations: '='
},
template: '<div class="row"></div>' +
'<div class="row col-lg-12"></div>' +
'<div class"row">' +
'<div class="col-lg-6"></div>' +
'<div class="col-lg-6"></div>' +
'</div>',
link: function (scope, elem, attrs, $compile) {
var chartElem = d3.select(elem.children()[0]);
var timeElem = d3.select(elem.children()[1]);
var legendAnnContainer = angular.element(elem.children()[2]);
var legendElem = d3.select(legendAnnContainer.children()[0]);
if (scope.annotateEnabled) {
var annElem = d3.select(legendAnnContainer.children()[1]);
}
var valueIdx = 1;
if (scope.normalize) {
valueIdx = 2;
}
var svgHeight = +scope.height || 150;
var height = svgHeight - margin.top - margin.bottom;
var svgWidth;
var width;
var yScale = d3.scale.linear().range([height, 0]);
var xScale = d3.time.scale.utc();
var xAxis = d3.svg.axis()
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(Math.min(10, height / 20))
.tickFormat(fmtfilter);
var line;
switch (scope.generator) {
case 'area':
line = d3.svg.area();
break;
default:
line = d3.svg.line();
}
var brush = d3.svg.brush()
.x(xScale)
.on('brush', brushed)
.on('brushend', annotateBrushed);
var top = chartElem
.append('svg')
.attr('height', svgHeight)
.attr('width', '100%');
var svg = top
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var defs = svg.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('height', height);
var chart = svg.append('g')
.attr('pointer-events', 'all')
.attr('clip-path', 'url(#clip)');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')');
svg.append('g')
.attr('class', 'y axis');
var paths = chart.append('g');
chart.append('g')
.attr('class', 'x brush');
if (scope.annotateEnabled) {
var ann = chart.append('g');
}
top.append('rect')
.style('opacity', 0)
.attr('x', 0)
.attr('y', 0)
.attr('height', height)
.attr('width', margin.left)
.style('cursor', 'pointer')
.on('click', yaxisToggle);
var xloc = timeElem.append('div').attr("class", "col-lg-6");
xloc.style('float', 'left');
var brushText = timeElem.append('div').attr("class", "col-lg-6").append('p').attr("class", "text-right");
var legend = legendElem;
var aLegend = annElem;
var color = d3.scale.ordinal().range([
'#e41a1c',
'#377eb8',
'#4daf4a',
'#984ea3',
'#ff7f00',
'#a65628',
'#f781bf',
'#999999',
]);
var annColor = d3.scale.ordinal().range([
'#e41a1c',
'#377eb8',
'#4daf4a',
'#984ea3',
'#ff7f00',
'#a65628',
'#f781bf',
'#999999',
]);
var mousex = 0;
var mousey = 0;
var oldx = 0;
var hover = svg.append('g')
.attr('class', 'hover')
.style('pointer-events', 'none')
.style('display', 'none');
var hoverPoint = hover.append('svg:circle')
.attr('r', 5);
var hoverRect = hover.append('svg:rect')
.attr('fill', 'white');
var hoverText = hover.append('svg:text')
.style('font-size', '12px');
var focus = svg.append('g')
.attr('class', 'focus')
.style('pointer-events', 'none');
focus.append('line');
var yaxisZero = false;
function yaxisToggle() {
yaxisZero = !yaxisZero;
draw();
}
var drawAnnLegend = function () {
if (scope.annotation) {
aLegend.html('');
var a = scope.annotation;
//var table = aLegend.append('table').attr("class", "table table-condensed")
var table = aLegend.append("div");
var row = table.append("div").attr("class", "row");
row.append("div").attr("class", "col-lg-2").text("CreationUser");
row.append("div").attr("class", "col-lg-10").text(a.CreationUser);
row = table.append("div").attr("class", "row");
row.append("div").attr("class", "col-lg-2").text("Owner");
row.append("div").attr("class", "col-lg-10").text(a.Owner);
row = table.append("div").attr("class", "row");
row.append("div").attr("class", "col-lg-2").text("Url");
row.append("div").attr("class", "col-lg-10").append('a')
.attr("xlink:href", a.Url).text(a.Url).on("click", function (d) {
window.open(a.Url, "_blank");
});
row = table.append("div").attr("class", "row");
row.append("div").attr("class", "col-lg-2").text("Category");
row.append("div").attr("class", "col-lg-10").text(a.Category);
row = table.append("div").attr("class", "row");
row.append("div").attr("class", "col-lg-2").text("Host");
row.append("div").attr("class", "col-lg-10").text(a.Host);
row = table.append("div").attr("class", "row");
row.append("div").attr("class", "col-lg-2").text("Message");
row.append("div").attr("class", "col-lg-10").text(a.Message);
} //
};
var drawLegend = _.throttle(function (normalizeIdx) {
var names = legend.selectAll('.series')
.data(scope.data, function (d) { return d.Name; });
names.enter()
.append('div')
.attr('class', 'series');
names.exit()
.remove();
var xi = xScale.invert(mousex);
xloc.text('Time: ' + fmtTime(xi));
var t = xi.getTime() / 1000;
var minDist = width + height;
var minName, minColor;
var minX, minY;
names
.each(function (d) {
var idx = bisect(d.Data, t);
if (idx >= d.Data.length) {
idx = d.Data.length - 1;
}
var e = d3.select(this);
var pt = d.Data[idx];
if (pt) {
e.attr('title', pt[normalizeIdx]);
e.text(d.Name + ': ' + fmtfilter(pt[1]));
var ptx = xScale(pt[0] * 1000);
var pty = yScale(pt[normalizeIdx]);
var ptd = Math.sqrt(Math.pow(ptx - mousex, 2) +
Math.pow(pty - mousey, 2));
if (ptd < minDist) {
minDist = ptd;
minX = ptx;
minY = pty;
minName = d.Name + ': ' + pt[1];
minColor = color(d.Name);
}
}
})
.style('color', function (d) { return color(d.Name); });
hover
.attr('transform', 'translate(' + minX + ',' + minY + ')');
hoverPoint.style('fill', minColor);
hoverText
.text(minName)
.style('fill', minColor);
var isRight = minX > width / 2;
var isBottom = minY > height / 2;
hoverText
.attr('x', isRight ? -5 : 5)
.attr('y', isBottom ? -8 : 15)
.attr('text-anchor', isRight ? 'end' : 'start');
var node = hoverText.node();
var bb = node.getBBox();
hoverRect
.attr('x', bb.x - 1)
.attr('y', bb.y - 1)
.attr('height', bb.height + 2)
.attr('width', bb.width + 2);
var x = mousex;
if (x > width) {
x = 0;
}
focus.select('line')
.attr('x1', x)
.attr('x2', x)
.attr('y1', 0)
.attr('y2', height);
if (extentStart) {
var s = extentStart;
if (extentEnd != extentStart) {
s += ' - ' + extentEnd;
s += ' (' + extentDiff + ')';
}
brushText.text(s);
}
}, 50);
scope.$watchCollection('[data, annotations, showAnnotations]', update);
var showAnnotations = function (show) {
if (show) {
ann.attr("visibility", "visible");
return;
}
ann.attr("visibility", "hidden");
aLegend.html('');
};
var w = angular.element($window);
scope.$watch(function () {
return w.width();
}, resize, true);
w.bind('resize', function () {
scope.$apply();
});
function resize() {
svgWidth = elem.width();
if (svgWidth <= 0) {
return;
}
width = svgWidth - margin.left - margin.right;
xScale.range([0, width]);
xAxis.scale(xScale);
if (!mousex) {
mousex = width + 1;
}
svg.attr('width', svgWidth);
defs.attr('width', width);
xAxis.ticks(width / 60);
draw();
}
var oldx = 0;
var bisect = d3.bisector(function (d) { return d[0]; }).left;
var bisectA = d3.bisector(function (d) { return moment(d.StartDate).unix(); }).left;
function update(v) {
if (!angular.isArray(v) || v.length == 0) {
return;
}
d3.selectAll(".x.brush").call(brush.clear());
if (scope.annotateEnabled) {
showAnnotations(scope.showAnnotations);
}
resize();
}
function draw() {
if (!scope.data) {
return;
}
if (scope.normalize) {
valueIdx = 2;
}
function mousemove() {
var pt = d3.mouse(this);
mousex = pt[0];
mousey = pt[1];
drawLegend(valueIdx);
}
scope.data.map(function (data, i) {
var max = d3.max(data.Data, function (d) { return d[1]; });
data.Data.map(function (d, j) {
d.push(d[1] / max * 100 || 0);
});
});
line.y(function (d) { return yScale(d[valueIdx]); });
line.x(function (d) { return xScale(d[0] * 1000); });
var xdomain = [
d3.min(scope.data, function (d) { return d3.min(d.Data, function (c) { return c[0]; }); }) * 1000,
d3.max(scope.data, function (d) { return d3.max(d.Data, function (c) { return c[0]; }); }) * 1000,
];
if (!oldx) {
oldx = xdomain[1];
}
xScale.domain(xdomain);
var ymin = d3.min(scope.data, function (d) { return d3.min(d.Data, function (c) { return c[1]; }); });
var ymax = d3.max(scope.data, function (d) { return d3.max(d.Data, function (c) { return c[valueIdx]; }); });
var diff = (ymax - ymin) / 50;
if (!diff) {
diff = 1;
}
ymin -= diff;
ymax += diff;
if (yaxisZero) {
if (ymin > 0) {
ymin = 0;
}
else if (ymax < 0) {
ymax = 0;
}
}
var ydomain = [ymin, ymax];
if (angular.isNumber(scope.min)) {
ydomain[0] = +scope.min;
}
if (angular.isNumber(scope.max)) {
ydomain[valueIdx] = +scope.max;
}
yScale.domain(ydomain);
if (scope.generator == 'area') {
line.y0(yScale(0));
}
svg.select('.x.axis')
.transition()
.call(xAxis);
svg.select('.y.axis')
.transition()
.call(yAxis);
svg.append('text')
.attr("class", "ylabel")
.attr("transform", "rotate(-90)")
.attr("y", -margin.left)
.attr("x", -(height / 2))
.attr("dy", "1em")
.text(_.uniq(scope.data.map(function (v) { return v.Unit; })).join("; "));
if (scope.annotateEnabled) {
var rowId = {}; // annotation Id -> rowId
var rowEndDate = {}; // rowId -> EndDate
var maxRow = 0;
for (var i = 0; i < scope.annotations.length; i++) {
if (i == 0) {
rowId[scope.annotations[i].Id] = 0;
rowEndDate[0] = scope.annotations[0].EndDate;
continue;
}
for (var row = 0; row <= maxRow + 1; row++) {
if (row == maxRow + 1) {
rowId[scope.annotations[i].Id] = row;
rowEndDate[row] = scope.annotations[i].EndDate;
maxRow += 1;
break;
}
if (rowEndDate[row] < scope.annotations[i].StartDate) {
rowId[scope.annotations[i].Id] = row;
rowEndDate[row] = scope.annotations[i].EndDate;
break;
}
}
}
var annotations = ann.selectAll('.annotation')
.data(scope.annotations, function (d) { return d.Id; });
annotations.enter()
.append("svg:a")
.append('rect')
.attr('visilibity', function () {
if (scope.showAnnotations) {
return "visible";
}
return "hidden";
})
.attr("y", function (d) { return rowId[d.Id] * ((height * .05) + 2); })
.attr("height", height * .05)
.attr("class", "annotation")
.attr("stroke", function (d) { return annColor(d.Id); })
.attr("stroke-opacity", .5)
.attr("fill", function (d) { return annColor(d.Id); })
.attr("fill-opacity", 0.1)
.attr("stroke-width", 1)
.attr("x", function (d) { return xScale(moment(d.StartDate).utc().unix() * 1000); })
.attr("width", function (d) {
var startT = moment(d.StartDate).utc().unix() * 1000;
var endT = moment(d.EndDate).utc().unix() * 1000;
if (startT == endT) {
return 3;
}
return xScale(endT) - xScale(startT);
})
.on("mouseenter", function (ann) {
if (!scope.showAnnotations) {
return;
}
if (ann) {
scope.annotation = ann;
drawAnnLegend();
}
scope.$apply();
})
.on("click", function () {
if (!scope.showAnnotations) {
return;
}
angular.element('#modalShower').trigger('click');
});
annotations.exit().remove();
}
var queries = paths.selectAll('.line')
.data(scope.data, function (d) { return d.Name; });
switch (scope.generator) {
case 'area':
queries.enter()
.append('path')
.attr('stroke', function (d) { return color(d.Name); })
.attr('class', 'line')
.style('fill', function (d) { return color(d.Name); });
break;
default:
queries.enter()
.append('path')
.attr('stroke', function (d) { return color(d.Name); })
.attr('class', 'line');
}
queries.exit()
.remove();
queries
.attr('d', function (d) { return line(d.Data); })
.attr('transform', null)
.transition()
.ease('linear')
.attr('transform', 'translate(' + (xScale(oldx) - xScale(xdomain[1])) + ')');
chart.select('.x.brush')
.call(brush)
.selectAll('rect')
.attr('height', height)
.on('mouseover', function () {
hover.style('display', 'block');
})
.on('mouseout', function () {
hover.style('display', 'none');
})
.on('mousemove', mousemove);
chart.select('.x.brush .extent')
.style('stroke', '#fff')
.style('fill-opacity', '.125')
.style('shape-rendering', 'crispEdges');
oldx = xdomain[1];
drawLegend(valueIdx);
}
;
var extentStart;
var extentEnd;
var extentDiff;
function brushed() {
var e;
e = d3.event.sourceEvent;
if (e.shiftKey) {
return;
}
var extent = brush.extent();
extentStart = datefmt(extent[0]);
extentEnd = datefmt(extent[1]);
extentDiff = fmtDuration(moment(extent[1]).diff(moment(extent[0])));
drawLegend(valueIdx);
if (scope.enableBrush && extentEnd != extentStart) {
scope.brushStart = extentStart;
scope.brushEnd = extentEnd;
scope.$apply();
}
}
function annotateBrushed() {
if (!scope.annotateEnabled) {
return;
}
var e;
e = d3.event.sourceEvent;
if (!e.shiftKey) {
return;
}
var extent = brush.extent();
scope.annotation = new Annotation();
scope.annotation.StartDate = moment(extent[0]).utc().format(timeFormat);
scope.annotation.EndDate = moment(extent[1]).utc().format(timeFormat);
scope.$apply(); // This logs a console type error, but also works .. odd.
angular.element('#modalShower').trigger('click');
}
var mfmt = 'YYYY/MM/DD-HH:mm:ss';
function datefmt(d) {
return moment(d).utc().format(mfmt);
}
}
};
}]);
bosunControllers.controller('ErrorCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
$scope.loading = true;
$http.get('/alerting/api/errors')
.success(function (data) {
$scope.errors = [];
_(data).forEach(function (err, name) {
err.Name = name;
err.Sum = 0;
err.Shown = true;
_(err.Errors).forEach(function (line) {
err.Sum += line.Count;
line.FirstTime = moment.utc(line.FirstTime);
line.LastTime = moment.utc(line.LastTime);
});
$scope.errors.push(err);
});
})
.error(function (data) {
$scope.error = "Error fetching data: " + data;
})
.finally(function () { $scope.loading = false; });
$scope.click = function (err, event) {
event.stopPropagation();
};
$scope.totalLines = function () {
return $scope.errors.length;
};
$scope.selectedLines = function () {
var t = 0;
_($scope.errors).forEach(function (err) {
if (err.checked) {
t++;
}
});
return t;
};
var getChecked = function () {
var keys = [];
_($scope.errors).forEach(function (err) {
if (err.checked) {
keys.push(err.Name);
}
});
return keys;
};
var clear = function (keys) {
$http.post('/alerting/api/errors', keys)
.success(function (data) {
$route.reload();
})
.error(function (data) {
$scope.error = "Error Clearing Errors: " + data;
});
};
$scope.clearAll = function () {
clear(["all"]);
};
$scope.clearSelected = function () {
var keys = getChecked();
clear(keys);
};
$scope.ruleLink = function (line, err) {
var url = "/alerting/config?alert=" + err.Name;
var fromDate = moment.utc(line.FirstTime);
url += "&fromDate=" + fromDate.format("YYYY-MM-DD");
url += "&fromTime=" + fromDate.format("hh:mm");
var toDate = moment.utc(line.LastTime);
url += "&toDate=" + toDate.format("YYYY-MM-DD");
url += "&toTime=" + toDate.format("hh:mm");
return url;
};
}]);
bosunControllers.controller('ExprCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
var current;
try {
current = atob(search.expr);
}
catch (e) {
current = '';
}
if (!current) {
$location.search('expr', btoa('avg(q("avg:rate:os.cpu{host=*bosun*}", "5m", "")) > 80'));
return;
}
$scope.date = search.date || '';
$scope.time = search.time || '';
$scope.expr = current;
$scope.running = current;
$scope.tab = search.tab || 'results';
$scope.animate();
$http.post('/alerting/api/expr?' +
'date=' + encodeURIComponent($scope.date) +
'&time=' + encodeURIComponent($scope.time), current)
.success(function (data) {
$scope.result = data.Results;
$scope.queries = data.Queries;
$scope.result_type = data.Type;
if (data.Type == 'series') {
$scope.svg_url = '/alerting/egraph/' + btoa(current) + '.svg?now=' + Math.floor(Date.now() / 1000);
$scope.graph = toChart(data.Results);
}
if (data.Type == 'number') {
angular.forEach(data.Results, function (d) {
var name = '{';
angular.forEach(d.Group, function (tagv, tagk) {
if (name.length > 1) {
name += ',';
}
name += tagk + '=' + tagv;
});
name += '}';
d.name = name;
});
$scope.bar = data.Results;
}
$scope.running = '';
})
.error(function (error) {
$scope.error = error;
$scope.running = '';
})
.finally(function () {
$scope.stop();
});
$scope.set = function () {
$location.search('expr', btoa($scope.expr));
$location.search('date', $scope.date || null);
$location.search('time', $scope.time || null);
$route.reload();
};
function toChart(res) {
var graph = [];
angular.forEach(res, function (d, idx) {
var data = [];
angular.forEach(d.Value, function (val, ts) {
data.push([+ts, val]);
});
if (data.length == 0) {
return;
}
var name = '{';
angular.forEach(d.Group, function (tagv, tagk) {
if (name.length > 1) {
name += ',';
}
name += tagk + '=' + tagv;
});
name += '}';
var series = {
Data: data,
Name: name
};
graph[idx] = series;
});
return graph;
}
$scope.keydown = function ($event) {
if ($event.shiftKey && $event.keyCode == 13) {
$scope.set();
}
};
}]);
var TagSet = (function () {
function TagSet() {
}
return TagSet;
})();
var TagV = (function () {
function TagV() {
}
return TagV;
})();
var RateOptions = (function () {
function RateOptions() {
}
return RateOptions;
})();
var Filter = (function () {
function Filter(f) {
this.type = f && f.type || "auto";
this.tagk = f && f.tagk || "";
this.filter = f && f.filter || "";
this.groupBy = f && f.groupBy || false;
}
return Filter;
})();
var FilterMap = (function () {
function FilterMap() {
}
return FilterMap;
})();
var Query = (function () {
function Query(filterSupport, q) {
this.aggregator = q && q.aggregator || 'sum';
this.metric = q && q.metric || '';
this.rate = q && q.rate || false;
this.rateOptions = q && q.rateOptions || new RateOptions;
if (q && !q.derivative) {
// back compute derivative from q
if (!this.rate) {
this.derivative = 'gauge';
}
else if (this.rateOptions.counter) {
this.derivative = 'counter';
}
else {
this.derivative = 'rate';
}
}
else {
this.derivative = q && q.derivative || 'auto';
}
this.ds = q && q.ds || '';
this.dstime = q && q.dstime || '';
this.tags = q && q.tags || new TagSet;
this.gbFilters = q && q.gbFilters || new FilterMap;
this.nGbFilters = q && q.nGbFilters || new FilterMap;
var that = this;
// Copy tags with values to group by filters so old links work
if (filterSupport) {
_.each(this.tags, function (v, k) {
if (v === "") {
return;
}
var f = new (Filter);
f.filter = v;
f.groupBy = true;
f.tagk = k;
that.gbFilters[k] = f;
});
// Load filters from raw query and turn them into gb and nGbFilters.
// This makes links from other pages work (i.e. the expr page)
if (_.has(q, 'filters')) {
_.each(q.filters, function (filter) {
if (filter.groupBy) {
that.gbFilters[filter.tagk] = filter;
return;
}
that.nGbFilters[filter.tagk] = filter;
});
}
}
this.setFilters();
this.setDs();
this.setDerivative();
}
Query.prototype.setFilters = function () {
this.filters = [];
var that = this;
_.each(this.gbFilters, function (filter, tagk) {
if (filter.filter && filter.type) {
that.filters.push(filter);
}
});
_.each(this.nGbFilters, function (filter, tagk) {
if (filter.filter && filter.type) {
that.filters.push(filter);
}
});
};
Query.prototype.setDs = function () {
if (this.dstime && this.ds) {
this.downsample = this.dstime + '-' + this.ds;
}
else {
this.downsample = '';
}
};
Query.prototype.setDerivative = function () {
var max = this.rateOptions.counterMax;
this.rate = false;
this.rateOptions = new RateOptions();
switch (this.derivative) {
case "rate":
this.rate = true;
break;
case "counter":
this.rate = true;
this.rateOptions.counter = true;
this.rateOptions.counterMax = max;
this.rateOptions.resetValue = 1;
break;
case "gauge":
this.rate = false;
break;
}
};
return Query;
})();
var Request = (function () {
function Request() {
this.start = '1h-ago';
this.queries = [];
}
Request.prototype.prune = function () {
var _this = this;
for (var i = 0; i < this.queries.length; i++) {
angular.forEach(this.queries[i], function (v, k) {
var qi = _this.queries[i];
switch (typeof v) {
case "string":
if (!v) {
delete qi[k];
}
break;
case "boolean":
if (!v) {
delete qi[k];
}
break;
case "object":
if (Object.keys(v).length == 0) {
delete qi[k];
}
break;
}
});
}
};
return Request;
})();
var graphRefresh;
var Version = (function () {
function Version() {
}
return Version;
})();
bosunControllers.controller('GraphCtrl', ['$scope', '$http', '$location', '$route', '$timeout', function ($scope, $http, $location, $route, $timeout) {
$scope.aggregators = ["sum", "min", "max", "avg", "dev", "zimsum", "mimmin", "minmax"];
$scope.dsaggregators = ["", "sum", "min", "max", "avg", "dev", "zimsum", "mimmin", "minmax"];
$scope.filters = ["auto", "iliteral_or", "iwildcard", "literal_or", "not_iliteral_or", "not_literal_or", "regexp", "wildcard"];
if ($scope.version.Major >= 2 && $scope.version.Minor >= 2) {
$scope.filterSupport = true;
}
$scope.rate_options = ["auto", "gauge", "counter", "rate"];
$scope.canAuto = {};
$scope.showAnnotations = (getShowAnnotations() == "true");
$scope.setShowAnnotations = function () {
if ($scope.showAnnotations) {
setShowAnnotations("true");
return;
}
setShowAnnotations("false");
};
var search = $location.search();
var j = search.json;
if (search.b64) {
j = atob(search.b64);
}
$scope.annotation = {};
var request = j ? JSON.parse(j) : new Request;
$scope.index = parseInt($location.hash()) || 0;
$scope.tagvs = [];
$scope.sorted_tagks = [];
$scope.query_p = [];
angular.forEach(request.queries, function (q, i) {
$scope.query_p[i] = new Query($scope.filterSupport, q);
});
$scope.start = request.start;
$scope.end = request.end;
$scope.autods = search.autods != 'false';
$scope.refresh = search.refresh == 'true';
$scope.normalize = search.normalize == 'true';
if (search.min) {
$scope.min = +search.min;
}
if (search.max) {
$scope.max = +search.max;
}
var duration_map = {
"s": "s",
"m": "m",
"h": "h",
"d": "d",
"w": "w",
"n": "M",
"y": "y"
};
var isRel = /^(\d+)(\w)-ago$/;
function RelToAbs(m) {
return moment().utc().subtract(parseFloat(m[1]), duration_map[m[2]]).format();
}
function AbsToRel(s) {
//Not strict parsing of the time format. For example, just "2014" will be valid
var t = moment.utc(s, moment.defaultFormat).fromNow();
return t;
}
function SwapTime(s) {
if (!s) {
return moment().utc().format();
}
var m = isRel.exec(s);
if (m) {
return RelToAbs(m);
}
return AbsToRel(s);
}
$scope.submitAnnotation = function () { return $http.post('/alerting/api/annotation', $scope.annotation)
.success(function (data) {
//debugger;
if ($scope.annotation.Id == "" && $scope.annotation.Owner != "") {
setOwner($scope.annotation.Owner);
}
$scope.annotation = new Annotation(data);
$scope.error = "";
// This seems to make angular refresh, where a push doesn't
$scope.annotations = $scope.annotations.concat($scope.annotation);
})
.error(function (error) {
$scope.error = error;
}); };
$scope.deleteAnnotation = function () { return $http.delete('/alerting/annotation/' + $scope.annotation.Id)
.success(function (data) {
$scope.error = "";
$scope.annotations = _.without($scope.annotations, _.findWhere($scope.annotations, { Id: $scope.annotation.Id }));
})
.error(function (error) {
$scope.error = error;
}); };
$scope.SwitchTimes = function () {
$scope.start = SwapTime($scope.start);
$scope.end = SwapTime($scope.end);
};
$scope.AddTab = function () {
$scope.index = $scope.query_p.length;
$scope.query_p.push(new Query($scope.filterSupport));
};
$scope.setIndex = function (i) {
$scope.index = i;
};
var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
if ($scope.annotateEnabled) {
$http.get('/alerting/api/annotation/values/Owner')
.success(function (data) {
$scope.owners = data;
});
$http.get('/alerting/api/annotation/values/Category')
.success(function (data) {
$scope.categories = data;
});
$http.get('/alerting/api/annotation/values/Host')
.success(function (data) {
$scope.hosts = data;
});
}
$scope.GetTagKByMetric = function (index) {
$scope.tagvs[index] = new TagV;
var metric = $scope.query_p[index].metric;
if (!metric) {
$scope.canAuto[metric] = true;
return;
}
$http.get('/alerting/api/tagk/' + metric)
.success(function (data) {
var q = $scope.query_p[index];
var tags = new TagSet;
q.metric_tags = {};
if (!q.gbFilters) {
q.gbFilters = new FilterMap;
}
if (!q.nGbFilters) {
q.nGbFilters = new FilterMap;
}
for (var i = 0; i < data.length; i++) {
var d = data[i];
if ($scope.filterSupport) {
if (!q.gbFilters[d]) {
var filter = new Filter;
filter.tagk = d;
filter.groupBy = true;
q.gbFilters[d] = filter;
}
if (!q.nGbFilters[d]) {
var filter = new Filter;
filter.tagk = d;
q.nGbFilters[d] = filter;
}
}
if (q.tags) {
tags[d] = q.tags[d];
}
if (!tags[d]) {
tags[d] = '';
}
q.metric_tags[d] = true;
GetTagVs(d, index);
}
angular.forEach(q.tags, function (val, key) {
if (val) {
tags[key] = val;
}
});
q.tags = tags;
// Make sure host is always the first tag.
$scope.sorted_tagks[index] = Object.keys(tags);
$scope.sorted_tagks[index].sort(function (a, b) {
if (a == 'host') {
return -1;
}
else if (b == 'host') {
return 1;
}
return a.localeCompare(b);
});
})
.error(function (error) {
$scope.error = 'Unable to fetch metrics: ' + error;
});
$http.get('/alerting/api/metadata/metrics?metric=' + metric)
.success(function (data) {
var canAuto = data && data.Rate;
$scope.canAuto[metric] = canAuto;
})
.error(function (err) {
$scope.error = err;
});
};
if ($scope.query_p.length == 0) {
$scope.AddTab();
}
$http.get('/alerting/api/metric' + "?since=" + moment().utc().subtract(2, "days").unix())
.success(function (data) {
$scope.metrics = data;
})
.error(function (error) {
$scope.error = 'Unable to fetch metrics: ' + error;
});
function GetTagVs(k, index) {
$http.get('/alerting/api/tagv/' + k + '/' + $scope.query_p[index].metric)
.success(function (data) {
data.sort();
$scope.tagvs[index][k] = data;
})
.error(function (error) {
$scope.error = 'Unable to fetch metrics: ' + error;
});
}
function getRequest() {
request = new Request;
request.start = $scope.start;
request.end = $scope.end;
angular.forEach($scope.query_p, function (p) {
if (!p.metric) {
return;
}
var q = new Query($scope.filterSupport, p);
var tags = q.tags;
q.tags = new TagSet;
if (!$scope.filterSupport) {
angular.forEach(tags, function (v, k) {
if (v && k) {
q.tags[k] = v;
}
});
}
request.queries.push(q);
});
return request;
}
$scope.Query = function () {
var r = getRequest();
angular.forEach($scope.query_p, function (q, index) {
var m = q.metric_tags;
if (!m) {
return;
}
if (!r.queries[index]) {
return;
}
angular.forEach(q.tags, function (key, tag) {
if (m[tag]) {
return;
}
delete r.queries[index].tags[tag];
});
if ($scope.filterSupport) {
_.each(r.queries[index].filters, function (f) {
if (m[f.tagk]) {
return;
}
delete r.queries[index].nGbFilters[f.tagk];
delete r.queries[index].gbFilters[f.tagk];
r.queries[index].filters = _.without(r.queries[index].filters, _.findWhere(r.queries[index].filters, { tagk: f.tagk }));
});
}
});
r.prune();
$location.search('b64', btoa(JSON.stringify(r)));
$location.search('autods', $scope.autods ? undefined : 'false');
$location.search('refresh', $scope.refresh ? 'true' : undefined);
$location.search('normalize', $scope.normalize ? 'true' : undefined);
var min = angular.isNumber($scope.min) ? $scope.min.toString() : null;
var max = angular.isNumber($scope.max) ? $scope.max.toString() : null;
$location.search('min', min);
$location.search('max', max);
$route.reload();
};
request = getRequest();
if (!request.queries.length) {
return;
}
var autods = $scope.autods ? '&autods=' + $('#chart').width() : '';
function getMetricMeta(metric) {
$http.get('/alerting/api/metadata/metrics?metric=' + encodeURIComponent(metric))
.success(function (data) {
$scope.meta[metric] = data;
})
.error(function (error) {
console.log("Error getting metadata for metric " + metric);
});
}
function get(noRunning) {
$timeout.cancel(graphRefresh);
if (!noRunning) {
$scope.running = 'Running';
}
var autorate = '';
$scope.meta = {};
for (var i = 0; i < request.queries.length; i++) {
if (request.queries[i].derivative == 'auto') {
autorate += '&autorate=' + i;
}
getMetricMeta(request.queries[i].metric);
}
_.each(request.queries, function (q, qIndex) {
request.queries[qIndex].filters = _.map(q.filters, function (filter) {
var f = new Filter(filter);
if (f.filter && f.type) {
if (f.type == "auto") {
if (f.filter.indexOf("*") > -1) {
f.type = f.filter == "*" ? f.type = "wildcard" : "iwildcard";
}
else {
f.type = "literal_or";
}
}
}
return f;
});
});
var min = angular.isNumber($scope.min) ? '&min=' + encodeURIComponent($scope.min.toString()) : '';
var max = angular.isNumber($scope.max) ? '&max=' + encodeURIComponent($scope.max.toString()) : '';
$scope.animate();
$scope.queryTime = '';
if (request.end && !isRel.exec(request.end)) {
var t = moment.utc(request.end, moment.defaultFormat);
$scope.queryTime = '&date=' + t.format('YYYY-MM-DD');
$scope.queryTime += '&time=' + t.format('HH:mm');
}
$http.get('/alerting/api/graph?' + 'b64=' + encodeURIComponent(btoa(JSON.stringify(request))) + autods + autorate + min + max)
.success(function (data) {
$scope.result = data.Series;
if ($scope.annotateEnabled) {
$scope.annotations = _.sortBy(data.Annotations, function (d) { return d.StartDate; });
}
if (!$scope.result) {
$scope.warning = 'No Results';
}
else {
$scope.warning = '';
}
$scope.queries = data.Queries;
$scope.exprText = "";
_.each($scope.queries, function (q, i) {
$scope.exprText += "$" + alphabet[i] + " = " + q + "\n";
if (i == $scope.queries.length - 1) {
$scope.exprText += "avg($" + alphabet[i] + ")";
}
});
$scope.running = '';
$scope.error = '';
var u = $location.absUrl();
u = u.substr(0, u.indexOf('?')) + '?';
u += 'b64=' + search.b64 + autods + autorate + min + max;
$scope.url = u;
})
.error(function (error) {
$scope.error = error;
$scope.running = '';
})
.finally(function () {
$scope.stop();
if ($scope.refresh) {
graphRefresh = $timeout(function () { get(true); }, 5000);
}
;
});
}
;
get(false);
}]);
bosunApp.directive('tsPopup', function () {
return {
restrict: 'E',
scope: {
url: '='
},
template: '<button class="btn btn-default" data-html="true" data-placement="bottom">embed</button>',
link: function (scope, elem, attrs) {
var button = $('button', elem);
scope.$watch(attrs.url, function (url) {
if (!url) {
return;
}
var text = '<input type="text" onClick="this.select();" readonly="readonly" value="&lt;a href=&quot;' + url + '&quot;&gt;&lt;img src=&quot;' + url + '&.png=png&quot;&gt;&lt;/a&gt;">';
button.popover({
content: text
});
});
}
};
});
bosunApp.directive('tsAlertHistory', function () {
return {
templateUrl: '/partials/alerthistory.html'
};
});
bosunControllers.controller('HistoryCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
var keys = {};
if (angular.isArray(search.key)) {
angular.forEach(search.key, function (v) {
keys[v] = true;
});
}
else {
keys[search.key] = true;
}
var params = Object.keys(keys).map(function (v) { return 'ak=' + encodeURIComponent(v); }).join('&');
$http.get('/alerting/api/status?' + params + "&all=1")
.success(function (data) {
console.log(data);
var selected_alerts = {};
angular.forEach(data, function (v, ak) {
if (!keys[ak]) {
return;
}
v.Events.map(function (h) { h.Time = moment.utc(h.Time); });
angular.forEach(v.Events, function (h, i) {
if (i + 1 < v.Events.length) {
h.EndTime = v.Events[i + 1].Time;
}
else {
h.EndTime = moment.utc();
}
});
selected_alerts[ak] = {
History: v.Events.reverse()
};
});
if (Object.keys(selected_alerts).length > 0) {
$scope.alert_history = selected_alerts;
}
else {
$scope.error = 'No Matching Alerts Found';
}
})
.error(function (err) {
$scope.error = err;
});
}]);
bosunControllers.controller('HostCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
$scope.host = search.host;
$scope.time = search.time;
$scope.tab = search.tab || "stats";
$scope.fsdata = [];
$scope.metrics = [];
var currentURL = $location.url();
$scope.mlink = function (m) {
var r = new Request();
var q = new Query(false);
q.metric = m;
q.tags = { 'host': $scope.host };
r.queries.push(q);
return r;
};
$scope.setTab = function (t) {
$location.search('tab', t);
$scope.tab = t;
};
$http.get('/alerting/api/metric/host/' + $scope.host)
.success(function (data) {
$scope.metrics = data || [];
});
var start = moment().utc().subtract(parseDuration($scope.time));
function parseDuration(v) {
var pattern = /(\d+)(d|y|n|h|m|s)-ago/;
var m = pattern.exec(v);
return moment.duration(parseInt(m[1]), m[2].replace('n', 'M'));
}
$http.get('/alerting/api/metadata/get?tagk=host&tagv=' + encodeURIComponent($scope.host))
.success(function (data) {
$scope.metadata = _.filter(data, function (i) {
return moment.utc(i.Time) > start;
});
});
var autods = '&autods=100';
var cpu_r = new Request();
cpu_r.start = $scope.time;
cpu_r.queries = [
new Query(false, {
metric: 'os.cpu',
derivative: 'counter',
tags: { host: $scope.host }
})
];
$http.get('/alerting/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(cpu_r)) + autods)
.success(function (data) {
if (!data.Series) {
return;
}
data.Series[0].Name = 'Percent Used';
$scope.cpu = data.Series;
});
var mem_r = new Request();
mem_r.start = $scope.time;
mem_r.queries.push(new Query(false, {
metric: "os.mem.total",
tags: { host: $scope.host }
}));
mem_r.queries.push(new Query(false, {
metric: "os.mem.used",
tags: { host: $scope.host }
}));
$http.get('/alerting/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(mem_r)) + autods)
.success(function (data) {
if (!data.Series) {
return;
}
data.Series[1].Name = "Used";
$scope.mem_total = Math.max.apply(null, data.Series[0].Data.map(function (d) { return d[1]; }));
$scope.mem = [data.Series[1]];
});
var net_bytes_r = new Request();
net_bytes_r.start = $scope.time;
net_bytes_r.queries = [
new Query(false, {
metric: "os.net.bytes",
rate: true,
rateOptions: { counter: true, resetValue: 1 },
tags: { host: $scope.host, iface: "*", direction: "*" }
})
];
$http.get('/alerting/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(net_bytes_r)) + autods)
.success(function (data) {
if (!data.Series) {
return;
}
var tmp = [];
var ifaceSeries = {};
angular.forEach(data.Series, function (series, idx) {
series.Data = series.Data.map(function (dp) { return [dp[0], dp[1] * 8]; });
if (series.Tags.direction == "out") {
series.Data = series.Data.map(function (dp) { return [dp[0], dp[1] * -1]; });
}
if (!ifaceSeries.hasOwnProperty(series.Tags.iface)) {
ifaceSeries[series.Tags.iface] = [series];
}
else {
ifaceSeries[series.Tags.iface].push(series);
tmp.push(ifaceSeries[series.Tags.iface]);
}
});
$scope.idata = tmp;
});
var fs_r = new Request();
fs_r.start = $scope.time;
fs_r.queries = [
new Query(false, {
metric: "os.disk.fs.space_total",
tags: { host: $scope.host, disk: "*" }
}),
new Query(false, {
metric: "os.disk.fs.space_used",
tags: { host: $scope.host, disk: "*" }
})
];
$http.get('/alerting/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(fs_r)) + autods)
.success(function (data) {
if (!data.Series) {
return;
}
var tmp = [];
var fsSeries = {};
angular.forEach(data.Series, function (series, idx) {
var stat = series.Data[series.Data.length - 1][1];
var prop = "";
if (series.Metric == "os.disk.fs.space_total") {
prop = "total";
}
else {
prop = "used";
}
if (!fsSeries.hasOwnProperty(series.Tags.disk)) {
fsSeries[series.Tags.disk] = [series];
fsSeries[series.Tags.disk][prop] = stat;
}
else {
fsSeries[series.Tags.disk].push(series);
fsSeries[series.Tags.disk][prop] = stat;
tmp.push(fsSeries[series.Tags.disk]);
}
});
$scope.fsdata = tmp;
});
}]);
bosunControllers.controller('IncidentCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
var id = search.id;
if (!id) {
$scope.error = "must supply incident id as query parameter";
return;
}
$http.get('/alerting/api/incidents/events?id=' + id)
.success(function (data) {
$scope.incident = data;
$scope.actions = data.Actions;
$scope.events = data.Events;
})
.error(function (err) {
$scope.error = err;
});
}]);
bosunControllers.controller('ItemsCtrl', ['$scope', '$http', function ($scope, $http) {
$http.get('/alerting/api/metric')
.success(function (data) {
$scope.metrics = data;
})
.error(function (error) {
$scope.status = 'Unable to fetch metrics: ' + error;
});
$http.get('/alerting/api/tagv/host?since=default')
.success(function (data) {
$scope.hosts = data;
})
.error(function (error) {
$scope.status = 'Unable to fetch hosts: ' + error;
});
}]);
var Tag = (function () {
function Tag() {
}
return Tag;
})();
var DP = (function () {
function DP() {
}
return DP;
})();
bosunControllers.controller('PutCtrl', ['$scope', '$http', '$route', function ($scope, $http, $route) {
$scope.tags = [new Tag];
var dp = new DP;
dp.k = moment().utc().format();
$scope.dps = [dp];
$http.get('/alerting/api/metric')
.success(function (data) {
$scope.metrics = data;
})
.error(function (error) {
$scope.error = 'Unable to fetch metrics: ' + error;
});
$scope.Submit = function () {
var data = [];
var tags = {};
angular.forEach($scope.tags, function (v, k) {
if (v.k || v.v) {
tags[v.k] = v.v;
}
});
angular.forEach($scope.dps, function (v, k) {
if (v.k && v.v) {
var ts = parseInt(moment.utc(v.k, tsdbDateFormat).format('X'));
data.push({
metric: $scope.metric,
timestamp: ts,
value: parseFloat(v.v),
tags: tags
});
}
});
$scope.running = 'submitting data...';
$scope.success = '';
$scope.error = '';
$http.post('/alerting/api/put', data)
.success(function () {
$scope.running = '';
$scope.success = 'Data Submitted';
})
.error(function (error) {
$scope.running = '';
$scope.error = error.error.message;
});
};
$scope.AddTag = function () {
var last = $scope.tags[$scope.tags.length - 1];
if (last.k && last.v) {
$scope.tags.push(new Tag);
}
};
$scope.AddDP = function () {
var last = $scope.dps[$scope.dps.length - 1];
if (last.k && last.v) {
var dp = new DP;
dp.k = moment.utc(last.k, tsdbDateFormat).add(15, 'seconds').format();
$scope.dps.push(dp);
}
};
$scope.GetTagKByMetric = function () {
$http.get('/alerting/api/tagk/' + $scope.metric)
.success(function (data) {
if (!angular.isArray(data)) {
return;
}
$scope.tags = [new Tag];
for (var i = 0; i < data.length; i++) {
var t = new Tag;
t.k = data[i];
$scope.tags.push(t);
}
})
.error(function (error) {
$scope.error = 'Unable to fetch metrics: ' + error;
});
};
}]);
bosunControllers.controller('SilenceCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) {
var search = $location.search();
$scope.start = search.start;
$scope.end = search.end;
$scope.duration = search.duration;
$scope.alert = search.alert;
$scope.hosts = search.hosts;
$scope.tags = search.tags;
$scope.edit = search.edit;
$scope.forget = search.forget;
$scope.user = getUser();
$scope.message = search.message;
if (!$scope.end && !$scope.duration) {
$scope.duration = '1h';
}
function filter(data, startBefore, startAfter, endAfter, endBefore, limit) {
var ret = {};
var count = 0;
_.each(data, function (v, name) {
if (limit && count >= limit) {
return;
}
var s = moment(v.Start).utc();
var e = moment(v.End).utc();
if (startBefore && s > startBefore) {
return;
}
if (startAfter && s < startAfter) {
return;
}
if (endAfter && e < endAfter) {
return;
}
if (endBefore && e > endBefore) {
return;
}
ret[name] = v;
});
return ret;
}
function get() {
$http.get('/alerting/api/silence/get')
.success(function (data) {
$scope.silences = [];
var now = moment.utc();
$scope.silences.push({
name: 'Active',
silences: filter(data, now, null, now, null, 0)
});
$scope.silences.push({
name: 'Upcoming',
silences: filter(data, null, now, null, null, 0)
});
$scope.silences.push({
name: 'Past',
silences: filter(data, null, null, null, now, 25)
});
})
.error(function (error) {
$scope.error = error;
});
}
get();
function getData() {
var tags = ($scope.tags || '').split(',');
if ($scope.hosts) {
tags.push('host=' + $scope.hosts.split(/[ ,|]+/).join('|'));
}
tags = tags.filter(function (v) { return v != ""; });
var data = {
start: $scope.start,
end: $scope.end,
duration: $scope.duration,
alert: $scope.alert,
tags: tags.join(','),
edit: $scope.edit,
forget: $scope.forget ? 'true' : null,
user: $scope.user,
message: $scope.message
};
return data;
}
var any = search.start || search.end || search.duration || search.alert || search.hosts || search.tags || search.forget;
var state = getData();
$scope.change = function () {
$scope.disableConfirm = true;
};
if (any) {
$scope.error = null;
$http.post('/alerting/api/silence/set', state)
.success(function (data) {
if (!data) {
data = { '(none)': false };
}
$scope.testSilences = data;
})
.error(function (error) {
$scope.error = error;
});
}
$scope.test = function () {
setUser($scope.user);
$location.search('start', $scope.start || null);
$location.search('end', $scope.end || null);
$location.search('duration', $scope.duration || null);
$location.search('alert', $scope.alert || null);
$location.search('hosts', $scope.hosts || null);
$location.search('tags', $scope.tags || null);
$location.search('forget', $scope.forget || null);
$location.search('message', $scope.message || null);
$route.reload();
};
$scope.confirm = function () {
$scope.error = null;
$scope.testSilences = null;
$scope.edit = null;
$location.search('edit', null);
state.confirm = 'true';
$http.post('/alerting/api/silence/set', state)
.error(function (error) {
$scope.error = error;
})
.finally(get);
};
$scope.clear = function (id) {
if (!window.confirm('Clear this silence?')) {
return;
}
$scope.error = null;
$http.post('/alerting/api/silence/clear?id=' + id, {})
.error(function (error) {
$scope.error = error;
})
.finally(get);
};
$scope.time = function (v) {
var m = moment(v).utc();
return m.format();
};
}]);
bosunApp.directive('tsAckGroup', ['$location', '$timeout', function ($location, $timeout) {
return {
scope: {
ack: '=',
groups: '=tsAckGroup',
schedule: '=',
timeanddate: '='
},
templateUrl: '/partials/ackgroup.html',
link: function (scope, elem, attrs) {
scope.canAckSelected = scope.ack == 'Needs Acknowledgement';
scope.panelClass = scope.$parent.panelClass;
scope.btoa = scope.$parent.btoa;
scope.encode = scope.$parent.encode;
scope.shown = {};
scope.collapse = function (i) {
scope.shown[i] = !scope.shown[i];
if (scope.shown[i] && scope.groups[i].Children.length == 1) {
$timeout(function () {
scope.$broadcast("onOpen", i);
}, 0);
}
};
scope.click = function ($event, idx) {
scope.collapse(idx);
if ($event.shiftKey && scope.schedule.checkIdx != undefined) {
var checked = scope.groups[scope.schedule.checkIdx].checked;
var start = Math.min(idx, scope.schedule.checkIdx);
var end = Math.max(idx, scope.schedule.checkIdx);
for (var i = start; i <= end; i++) {
if (i == idx) {
continue;
}
scope.groups[i].checked = checked;
}
}
scope.schedule.checkIdx = idx;
scope.update();
};
scope.select = function (checked) {
for (var i = 0; i < scope.groups.length; i++) {
scope.groups[i].checked = checked;
}
scope.update();
};
scope.update = function () {
scope.canCloseSelected = true;
scope.canForgetSelected = true;
scope.anySelected = false;
for (var i = 0; i < scope.groups.length; i++) {
var g = scope.groups[i];
if (!g.checked) {
continue;
}
scope.anySelected = true;
if (g.Active && g.Status != 'unknown' && g.Status != 'error') {
scope.canCloseSelected = false;
}
if (g.Status != 'unknown') {
scope.canForgetSelected = false;
}
}
};
scope.multiaction = function (type) {
var keys = [];
angular.forEach(scope.groups, function (group) {
if (!group.checked) {
return;
}
if (group.AlertKey) {
keys.push(group.AlertKey);
}
angular.forEach(group.Children, function (child) {
keys.push(child.AlertKey);
});
});
scope.$parent.setKey("action-keys", keys);
$location.path("action");
$location.search("type", type);
};
scope.history = function () {
var url = '/alerting/history?';
angular.forEach(scope.groups, function (group) {
if (!group.checked) {
return;
}
if (group.AlertKey) {
url += '&key=' + encodeURIComponent(group.AlertKey);
}
angular.forEach(group.Children, function (child) {
url += '&key=' + encodeURIComponent(child.AlertKey);
});
});
return url;
};
}
};
}]);
bosunApp.directive('tsState', ['$sce', '$http', function ($sce, $http) {
return {
templateUrl: '/partials/alertstate.html',
link: function (scope, elem, attrs) {
var myIdx = attrs["tsGrp"];
scope.currentStatus = attrs["tsGrpstatus"];
scope.name = scope.child.AlertKey;
scope.state = scope.child.State;
scope.action = function (type) {
var key = encodeURIComponent(scope.name);
return '/alerting/action?type=' + type + '&key=' + key;
};
var loadedBody = false;
scope.toggle = function () {
scope.show = !scope.show;
if (scope.show && !loadedBody) {
scope.state.Body = "loading...";
loadedBody = true;
$http.get('/alerting/api/status?ak=' + scope.child.AlertKey)
.success(function (data) {
var body = data[scope.child.AlertKey].Body;
scope.state.Body = $sce.trustAsHtml(body);
})
.error(function (err) {
scope.state.Body = "Error loading template body: " + err;
});
}
};
scope.$on('onOpen', function (e, i) {
if (i == myIdx) {
scope.toggle();
}
});
scope.zws = function (v) {
if (!v) {
return '';
}
return v.replace(/([,{}()])/g, '$1\u200b');
};
scope.state.Touched = moment(scope.state.Touched).utc();
angular.forEach(scope.state.Events, function (v, k) {
v.Time = moment(v.Time).utc();
});
scope.state.last = scope.state.Events[scope.state.Events.length - 1];
if (scope.state.Actions && scope.state.Actions.length > 0) {
scope.state.LastAction = scope.state.Actions[0];
}
scope.state.RuleUrl = '/alerting/config?' +
'alert=' + encodeURIComponent(scope.state.Alert) +
'&fromDate=' + encodeURIComponent(scope.state.last.Time.format("YYYY-MM-DD")) +
'&fromTime=' + encodeURIComponent(scope.state.last.Time.format("HH:mm"));
var groups = [];
angular.forEach(scope.state.Group, function (v, k) {
groups.push(k + "=" + v);
});
if (groups.length > 0) {
scope.state.RuleUrl += '&template_group=' + encodeURIComponent(groups.join(','));
}
scope.state.Body = $sce.trustAsHtml(scope.state.Body);
}
};
}]);
bosunApp.directive('tsAck', function () {
return {
restrict: 'E',
templateUrl: '/partials/ack.html'
};
});
bosunApp.directive('tsClose', function () {
return {
restrict: 'E',
templateUrl: '/partials/close.html'
};
});
bosunApp.directive('tsForget', function () {
return {
restrict: 'E',
templateUrl: '/partials/forget.html'
};
});
bosunApp.directive('tsPurge', function () {
return {
restrict: 'E',
templateUrl: '/partials/purge.html'
};
});
bosunApp.directive('tsForceClose', function () {
return {
restrict: 'E',
templateUrl: '/partials/forceClose.html'
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment