Created
July 10, 2017 13:01
-
-
Save fradaloisio/32d21004d347383464efbe13c4656866 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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="<a href="' + url + '"><img src="' + url + '&.png=png"></a>">'; | |
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