bosun.org@v0.0.0-20250213104149-b8d3e981f37d/cmd/bosun/web/static/js/bosun.js (about) 1 /// <reference path="moment.d.ts" /> 2 /// <reference path="moment-duration-format.d.ts" /> 3 //Represents an auth token 4 var Token = /** @class */ (function () { 5 function Token() { 6 this.Description = ""; 7 this.Role = 0; 8 this.User = ""; 9 } 10 return Token; 11 }()); 12 //metadata about a single role or permission 13 var BitMeta = /** @class */ (function () { 14 function BitMeta() { 15 } 16 return BitMeta; 17 }()); 18 //all roles/permissions for bosun 19 var RoleDefs = /** @class */ (function () { 20 function RoleDefs() { 21 } 22 return RoleDefs; 23 }()); 24 // See models/incident.go Event (can't be event here because JS uses that) 25 var IncidentEvent = /** @class */ (function () { 26 function IncidentEvent(ie) { 27 this.Value = ie.Value; 28 this.Expr = ie.Expr; 29 this.Status = ie.Status; 30 this.Time = ie.Time; 31 this.Unevaluated = ie.Unevaluated; 32 } 33 return IncidentEvent; 34 }()); 35 var Annotation = /** @class */ (function () { 36 function Annotation(a, get) { 37 a = a || {}; 38 this.Id = a.Id || ""; 39 this.Message = a.Message || ""; 40 this.StartDate = a.StartDate || ""; 41 this.EndDate = a.EndDate || ""; 42 this.CreationUser = a.CreationUser || ""; 43 this.Url = a.Url || ""; 44 this.Source = a.Source || "bosun-ui"; 45 this.Host = a.Host || ""; 46 this.Owner = a.Owner || !get && getOwner() || ""; 47 this.Category = a.Category || ""; 48 } 49 Annotation.prototype.setTimeUTC = function () { 50 var now = moment().utc().format(timeFormat); 51 this.StartDate = now; 52 this.EndDate = now; 53 }; 54 Annotation.prototype.setTime = function () { 55 var now = moment().format(timeFormat); 56 this.StartDate = now; 57 this.EndDate = now; 58 }; 59 return Annotation; 60 }()); 61 var Result = /** @class */ (function () { 62 function Result(r) { 63 this.Value = r.Value; 64 this.Expr = r.Expr; 65 } 66 return Result; 67 }()); 68 var Action = /** @class */ (function () { 69 function Action(a) { 70 this.User = a.User; 71 this.Message = a.Message; 72 this.Time = a.Time; 73 this.Type = a.Type; 74 this.Deadline = a.Deadline; 75 this.Cancelled = a.Cancelled; 76 this.Fullfilled = a.Fullfilled; 77 } 78 return Action; 79 }()); 80 // See models/incident.go 81 var IncidentState = /** @class */ (function () { 82 function IncidentState(is) { 83 this.Id = is.Id; 84 this.Start = is.Start; 85 this.End = is.End; 86 this.AlertKey = is.AlertKey; 87 this.Alert = is.Alert; 88 this.Value = is.Value; 89 this.Expr = is.Expr; 90 this.Events = new Array(); 91 if (is.Events) { 92 for (var _i = 0, _a = is.Events; _i < _a.length; _i++) { 93 var e = _a[_i]; 94 this.Events.push(new IncidentEvent(e)); 95 } 96 } 97 this.Actions = new Array(); 98 this.Tags = is.Tags; 99 if (is.Actions) { 100 for (var _b = 0, _c = is.Actions; _b < _c.length; _b++) { 101 var a = _c[_b]; 102 this.Actions.push(new Action(a)); 103 } 104 } 105 this.Subject = is.Subject; 106 this.NeedAck = is.NeedAck; 107 this.Open = is.Open; 108 this.Unevaluated = is.Unevaluated; 109 this.CurrentStatus = is.CurrentStatus; 110 this.WorstStatus = is.WorstStatus; 111 this.LastAbnormalStatus = is.LastAbnormalStatus; 112 this.LastAbnormalTime = is.LastAbnormalTime; 113 this.PreviousIds = new Array(); 114 if (is.PreviousIds) { 115 for (var _d = 0, _e = is.PreviousIds; _d < _e.length; _d++) { 116 var id = _e[_d]; 117 this.PreviousIds.push(id); 118 } 119 } 120 this.NextId = is.NextId; 121 } 122 IncidentState.prototype.IsPendingClose = function () { 123 for (var _i = 0, _a = this.Actions; _i < _a.length; _i++) { 124 var action = _a[_i]; 125 if (action.Deadline != undefined && !(action.Fullfilled || action.Cancelled)) { 126 return true; 127 } 128 } 129 return false; 130 }; 131 return IncidentState; 132 }()); 133 var StateGroup = /** @class */ (function () { 134 function StateGroup(sg) { 135 this.Active = sg.Active; 136 this.Status = sg.Status; 137 this.CurrentStatus = sg.CurrentStatus; 138 this.Silenced = sg.Silenced; 139 this.IsError = sg.IsError; 140 this.Subject = sg.Subject; 141 this.Alert = sg.Alert; 142 this.AlertKey = sg.AlertKey; 143 this.Ago = sg.Ago; 144 if (sg.State) { 145 this.State = new IncidentState(sg.State); 146 } 147 this.Children = new Array(); 148 if (sg.Children) { 149 for (var _i = 0, _a = sg.Children; _i < _a.length; _i++) { 150 var c = _a[_i]; 151 this.Children.push(new StateGroup(c)); 152 } 153 } 154 } 155 return StateGroup; 156 }()); 157 var Groups = /** @class */ (function () { 158 function Groups(g) { 159 this.NeedAck = new Array(); 160 if (g.NeedAck) { 161 for (var _i = 0, _a = g.NeedAck; _i < _a.length; _i++) { 162 var sg = _a[_i]; 163 this.NeedAck.push(new StateGroup(sg)); 164 } 165 } 166 this.Acknowledged = new Array(); 167 if (g.Acknowledged) { 168 for (var _b = 0, _c = g.Acknowledged; _b < _c.length; _b++) { 169 var sg = _c[_b]; 170 this.Acknowledged.push(new StateGroup(sg)); 171 } 172 } 173 } 174 return Groups; 175 }()); 176 var StateGroups = /** @class */ (function () { 177 function StateGroups(sgs) { 178 this.Groups = new Groups(sgs.Groups); 179 this.TimeAndDate = sgs.TimeAndDate; 180 this.FailingAlerts = sgs.FailingAlerts; 181 this.UnclosedErrors = sgs.UnclosedErrors; 182 } 183 return StateGroups; 184 }()); 185 /// <reference path="angular.d.ts" /> 186 /// <reference path="angular-route.d.ts" /> 187 /// <reference path="angular-sanitize.d.ts" /> 188 /// <reference path="bootstrap.d.ts" /> 189 /// <reference path="jquery.d.ts" /> 190 /// <reference path="underscore.d.ts" /> 191 /// <reference path="models.ts" /> 192 var bosunApp = angular.module('bosunApp', [ 193 'ngRoute', 194 'bosunControllers', 195 'mgcrea.ngStrap', 196 'mgcrea.ngStrap.tooltip', 197 'ngSanitize', 198 'ui.ace', 199 'ngclipboard', 200 ]); 201 bosunApp.config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) { 202 $locationProvider.html5Mode({ 203 enabled: true, 204 requireBase: false 205 }); 206 var when = function (u, r) { 207 $routeProvider.when(u, r); 208 }; 209 when('/', { 210 title: 'Dashboard', 211 templateUrl: 'partials/dashboard.html', 212 controller: 'DashboardCtrl', 213 }); 214 when('/items', { 215 title: 'Items', 216 templateUrl: 'partials/items.html', 217 controller: 'ItemsCtrl', 218 }); 219 when('/expr', { 220 title: 'Expression', 221 templateUrl: 'partials/expr.html', 222 controller: 'ExprCtrl', 223 }); 224 when('/errors', { 225 title: 'Errors', 226 templateUrl: 'partials/errors.html', 227 controller: 'ErrorCtrl', 228 }); 229 when('/graph', { 230 title: 'Graph', 231 templateUrl: 'partials/graph.html', 232 controller: 'GraphCtrl', 233 }); 234 when('/host', { 235 title: 'Host View', 236 templateUrl: 'partials/host.html', 237 controller: 'HostCtrl', 238 reloadOnSearch: false, 239 }); 240 when('/silence', { 241 title: 'Silence', 242 templateUrl: 'partials/silence.html', 243 controller: 'SilenceCtrl', 244 }); 245 when('/config', { 246 title: 'Configuration', 247 templateUrl: 'partials/config.html', 248 controller: 'ConfigCtrl', 249 reloadOnSearch: false, 250 }); 251 when('/action', { 252 title: 'Action', 253 templateUrl: 'partials/action.html', 254 controller: 'ActionCtrl', 255 }); 256 when('/history', { 257 title: 'Alert History', 258 templateUrl: 'partials/history.html', 259 controller: 'HistoryCtrl', 260 }); 261 when('/put', { 262 title: 'Data Entry', 263 templateUrl: 'partials/put.html', 264 controller: 'PutCtrl', 265 }); 266 when('/annotation', { 267 title: 'Annotation', 268 templateUrl: 'partials/annotation.html', 269 controller: 'AnnotationCtrl', 270 }); 271 when('/incident', { 272 title: 'Incident', 273 templateUrl: 'partials/incident.html', 274 controller: 'IncidentCtrl', 275 }); 276 when('/tokens', { 277 title: 'Access Tokens', 278 template: "<token-list></token-list>", 279 }); 280 when('/tokens/new', { 281 title: 'New Access Token', 282 template: "<new-token></new-token>", 283 }); 284 $routeProvider.otherwise({ 285 redirectTo: '/', 286 }); 287 $httpProvider.interceptors.push(function ($q) { 288 return { 289 'request': function (config) { 290 config.headers['X-Miniprofiler'] = 'true'; 291 return config; 292 }, 293 }; 294 }); 295 }]); 296 bosunApp.run(['$location', '$rootScope', function ($location, $rootScope) { 297 $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { 298 $rootScope.title = current.$$route.title; 299 $rootScope.shortlink = false; 300 }); 301 }]); 302 var bosunControllers = angular.module('bosunControllers', []); 303 bosunControllers.controller('BosunCtrl', ['$scope', '$route', '$http', '$q', '$rootScope', 'authService', 304 function ($scope, $route, $http, $q, $rootScope, AuthService) { 305 $scope.$on('$routeChangeSuccess', function (event, current, previous) { 306 $scope.stop(true); 307 }); 308 $scope.active = function (v) { 309 if (!$route.current) { 310 return null; 311 } 312 if ($route.current.loadedTemplateUrl == 'partials/' + v + '.html') { 313 return { active: true }; 314 } 315 return null; 316 }; 317 $scope.init = function (settings) { 318 $scope.saveEnabled = settings.SaveEnabled; 319 $scope.annotateEnabled = settings.AnnotateEnabled; 320 $scope.quiet = settings.Quiet; 321 $scope.version = settings.Version; 322 $scope.opentsdbEnabled = $scope.version.Major != 0 && $scope.version.Minor != 0; 323 $scope.exampleExpression = settings.ExampleExpression; 324 $scope.tokensEnabled = settings.TokensEnabled; 325 $scope.auth = AuthService; 326 AuthService.Init(settings.AuthEnabled, settings.Username, settings.Roles, settings.Permissions); 327 }; 328 $scope.json = function (v) { 329 return JSON.stringify(v, null, ' '); 330 }; 331 $scope.btoa = function (v) { 332 return encodeURIComponent(btoa(v)); 333 }; 334 $scope.encode = function (v) { 335 return encodeURIComponent(v); 336 }; 337 $scope.req_from_m = function (m) { 338 var r = new GraphRequest(); 339 var q = new Query(false); 340 q.metric = m; 341 r.queries.push(q); 342 return r; 343 }; 344 $scope.panelClass = function (status, prefix) { 345 if (prefix === void 0) { prefix = "panel-"; } 346 switch (status) { 347 case "critical": return prefix + "danger"; 348 case "unknown": return prefix + "info"; 349 case "warning": return prefix + "warning"; 350 case "normal": return prefix + "success"; 351 case "error": return prefix + "danger"; 352 default: return prefix + "default"; 353 } 354 }; 355 $scope.values = {}; 356 $scope.setKey = function (key, value) { 357 if (value === undefined) { 358 delete $scope.values[key]; 359 } 360 else { 361 $scope.values[key] = value; 362 } 363 }; 364 $scope.getKey = function (key) { 365 return $scope.values[key]; 366 }; 367 var scheduleFilter; 368 $scope.refresh = function (filter) { 369 var d = $q.defer(); 370 scheduleFilter = filter; 371 $scope.animate(); 372 var p = $http.get('/api/alerts?filter=' + encodeURIComponent(filter || "")) 373 .success(function (data) { 374 $scope.schedule = new StateGroups(data); 375 $scope.timeanddate = data.TimeAndDate; 376 d.resolve(); 377 }) 378 .error(function (err) { 379 d.reject(err); 380 }); 381 p.finally($scope.stop); 382 return d.promise; 383 }; 384 // Size of the logo in (width and height) of the Bosun logo in the navbar 385 var sz = 25; 386 var orig = 700; 387 var light = '#4ba2d9'; 388 var dark = '#1f5296'; 389 var med = '#356eb6'; 390 var mult = sz / orig; 391 var bgrad = 25 * mult; 392 var circles = [ 393 [150, 150, dark], 394 [550, 150, dark], 395 [150, 550, light], 396 [550, 550, light], 397 ]; 398 var svg = d3.select('#logo') 399 .append('svg') 400 .attr('height', sz) 401 .attr('width', sz); 402 svg.selectAll('rect.bg') 403 .data([[0, light], [sz / 2, dark]]) 404 .enter() 405 .append('rect') 406 .attr('class', 'bg') 407 .attr('width', sz) 408 .attr('height', sz / 2) 409 .attr('rx', bgrad) 410 .attr('ry', bgrad) 411 .attr('fill', function (d) { return d[1]; }) 412 .attr('y', function (d) { return d[0]; }); 413 svg.selectAll('path.diamond') 414 .data([150, 550]) 415 .enter() 416 .append('path') 417 .attr('d', function (d) { 418 var s = 'M ' + d * mult + ' ' + 150 * mult; 419 var w = 200 * mult; 420 s += ' l ' + w + ' ' + w; 421 s += ' l ' + -w + ' ' + w; 422 s += ' l ' + -w + ' ' + -w + ' Z'; 423 return s; 424 }) 425 .attr('fill', med) 426 .attr('stroke', 'white') 427 .attr('stroke-width', 15 * mult); 428 svg.selectAll('rect.white') 429 .data([150, 350, 550]) 430 .enter() 431 .append('rect') 432 .attr('class', 'white') 433 .attr('width', .5) 434 .attr('height', '100%') 435 .attr('fill', 'white') 436 .attr('x', function (d) { return d * mult; }); 437 svg.selectAll('circle') 438 .data(circles) 439 .enter() 440 .append('circle') 441 .attr('cx', function (d) { return d[0] * mult; }) 442 .attr('cy', function (d) { return d[1] * mult; }) 443 .attr('r', 62.5 * mult) 444 .attr('fill', function (d) { return d[2]; }) 445 .attr('stroke', 'white') 446 .attr('stroke-width', 25 * mult); 447 var transitionDuration = 750; 448 var animateCount = 0; 449 $scope.animate = function () { 450 animateCount++; 451 if (animateCount == 1) { 452 doAnimate(); 453 } 454 }; 455 function doAnimate() { 456 if (!animateCount) { 457 return; 458 } 459 d3.shuffle(circles); 460 svg.selectAll('circle') 461 .data(circles, function (d, i) { return i; }) 462 .transition() 463 .duration(transitionDuration) 464 .attr('cx', function (d) { return d[0] * mult; }) 465 .attr('cy', function (d) { return d[1] * mult; }) 466 .attr('fill', function (d) { return d[2]; }); 467 setTimeout(doAnimate, transitionDuration); 468 } 469 $scope.stop = function (all) { 470 if (all === void 0) { all = false; } 471 if (all) { 472 animateCount = 0; 473 } 474 else if (animateCount > 0) { 475 animateCount--; 476 } 477 }; 478 var short = $('#shortlink')[0]; 479 $scope.shorten = function () { 480 $http.get('/api/shorten').success(function (data) { 481 if (data.id) { 482 short.value = data.id; 483 $rootScope.shortlink = true; 484 setTimeout(function () { 485 short.setSelectionRange(0, data.id.length); 486 }); 487 } 488 }); 489 }; 490 }]); 491 var tsdbDateFormat = 'YYYY/MM/DD-HH:mm:ss'; 492 moment.defaultFormat = tsdbDateFormat; 493 moment.locale('en', { 494 relativeTime: { 495 future: "in %s", 496 past: "%s-ago", 497 s: "%ds", 498 m: "%dm", 499 mm: "%dm", 500 h: "%dh", 501 hh: "%dh", 502 d: "%dd", 503 dd: "%dd", 504 M: "%dn", 505 MM: "%dn", 506 y: "%dy", 507 yy: "%dy" 508 }, 509 }); 510 function ruleUrl(ak, fromTime) { 511 var openBrack = ak.indexOf("{"); 512 var closeBrack = ak.indexOf("}"); 513 var alertName = ak.substr(0, openBrack); 514 var template = ak.substring(openBrack + 1, closeBrack); 515 var url = '/api/rule?' + 516 'alert=' + encodeURIComponent(alertName) + 517 '&from=' + encodeURIComponent(fromTime.format()) + 518 '&template_group=' + encodeURIComponent(template); 519 return url; 520 } 521 function configUrl(ak, fromTime) { 522 var openBrack = ak.indexOf("{"); 523 var closeBrack = ak.indexOf("}"); 524 var alertName = ak.substr(0, openBrack); 525 var template = ak.substring(openBrack + 1, closeBrack); 526 // http://bosun/config?alert=haproxy.server.downtime.ny&fromDate=2016-07-10&fromTime=21%3A03 527 var url = '/config?' + 528 'alert=' + encodeURIComponent(alertName) + 529 '&fromDate=' + encodeURIComponent(fromTime.format("YYYY-MM-DD")) + 530 '&fromTime=' + encodeURIComponent(fromTime.format("HH:mm")); 531 return url; 532 } 533 function createCookie(name, value, days) { 534 var expires; 535 if (days) { 536 var date = new Date(); 537 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 538 expires = "; expires=" + date.toUTCString(); 539 } 540 else { 541 expires = ""; 542 } 543 document.cookie = escape(name) + "=" + escape(value) + expires + "; path=/"; 544 } 545 function readCookie(name) { 546 var nameEQ = escape(name) + "="; 547 var ca = document.cookie.split(';'); 548 for (var i = 0; i < ca.length; i++) { 549 var c = ca[i]; 550 while (c.charAt(0) === ' ') 551 c = c.substring(1, c.length); 552 if (c.indexOf(nameEQ) === 0) 553 return unescape(c.substring(nameEQ.length, c.length)); 554 } 555 return null; 556 } 557 function eraseCookie(name) { 558 createCookie(name, "", -1); 559 } 560 function getOwner() { 561 return readCookie('action-owner'); 562 } 563 function setOwner(name) { 564 createCookie('action-owner', name, 1000); 565 } 566 function getShowAnnotations() { 567 return readCookie('annotations-show'); 568 } 569 function setShowAnnotations(yes) { 570 createCookie('annotations-show', yes, 1000); 571 } 572 // from: http://stackoverflow.com/a/15267754/864236 573 bosunApp.filter('reverse', function () { 574 return function (items) { 575 if (!angular.isArray(items)) { 576 return []; 577 } 578 return items.slice().reverse(); 579 }; 580 }); 581 var timeFormat = 'YYYY-MM-DDTHH:mm:ssZ'; 582 /// <reference path="0-bosun.ts" /> 583 bosunControllers.controller('ExprCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) { 584 var search = $location.search(); 585 var current = ''; 586 try { 587 current = atob(search.expr); 588 } 589 catch (e) { 590 current = ''; 591 } 592 if (!current && $scope.exampleExpression) { 593 $location.search('expr', btoa($scope.exampleExpression)); 594 return; 595 } 596 $scope.date = search.date || ''; 597 $scope.time = search.time || ''; 598 $scope.expr = current; 599 $scope.aceMode = 'bosun'; 600 $scope.aceTheme = 'chrome'; 601 $scope.aceLoaded = function (editor) { 602 $scope.editor = editor; 603 editor.focus(); 604 editor.getSession().setUseWrapMode(true); 605 editor.getSession().setMode({ 606 path: 'ace/mode/' + $scope.aceMode, 607 v: Date.now() 608 }); 609 editor.$blockScrolling = Infinity; 610 }; 611 $scope.$on('$viewContentLoaded', function () { 612 setTimeout(function () { 613 var editor = $scope.editor; 614 var row = editor.session.getLength() - 1; 615 var column = editor.session.getLine(row).length; 616 editor.selection.moveTo(row, column); 617 }); 618 }); 619 $scope.tab = search.tab || 'results'; 620 if ($scope.expr) { 621 $scope.running = $scope.expr; 622 $scope.animate(); 623 $http.post('/api/expr?' + 624 'date=' + encodeURIComponent($scope.date) + 625 '&time=' + encodeURIComponent($scope.time), current) 626 .success(function (data) { 627 $scope.result = data.Results; 628 $scope.queries = data.Queries; 629 $scope.result_type = data.Type; 630 if (data.Type == 'series') { 631 $scope.svg_url = '/api/egraph/' + btoa(current) + '.svg?now=' + Math.floor(Date.now() / 1000); 632 $scope.graph = toChart(data.Results); 633 } 634 if (data.Type == 'number') { 635 angular.forEach(data.Results, function (d) { 636 var name = '{'; 637 angular.forEach(d.Group, function (tagv, tagk) { 638 if (name.length > 1) { 639 name += ','; 640 } 641 name += tagk + '=' + tagv; 642 }); 643 name += '}'; 644 d.name = name; 645 }); 646 $scope.bar = data.Results; 647 } 648 $scope.running = ''; 649 }) 650 .error(function (error) { 651 $scope.error = error; 652 $scope.running = ''; 653 }) 654 .finally(function () { 655 $scope.stop(); 656 }); 657 } 658 $scope.set = function () { 659 $location.search('date', $scope.date || null); 660 $location.search('time', $scope.time || null); 661 if ($scope.expr) { 662 $location.search('expr', btoa($scope.expr)); 663 $route.reload(); 664 } 665 else { 666 $scope.error = "expr: empty"; 667 $scope.result = null; 668 $scope.queries = null; 669 } 670 }; 671 function toChart(res) { 672 var graph = []; 673 angular.forEach(res, function (d, idx) { 674 var data = []; 675 angular.forEach(d.Value, function (val, ts) { 676 data.push([+ts, val]); 677 }); 678 if (data.length == 0) { 679 return; 680 } 681 var name = '{'; 682 angular.forEach(d.Group, function (tagv, tagk) { 683 if (name.length > 1) { 684 name += ','; 685 } 686 name += tagk + '=' + tagv; 687 }); 688 name += '}'; 689 var series = { 690 Data: data, 691 Name: name, 692 }; 693 graph[idx] = series; 694 }); 695 return graph; 696 } 697 $scope.keydown = function ($event) { 698 if ($event.shiftKey && $event.keyCode == 13) { 699 $scope.set(); 700 $event.preventDefault(); 701 } 702 }; 703 }]); 704 /// <reference path="expr.ts" /> 705 bosunControllers.controller('ActionCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) { 706 var search = $location.search(); 707 $scope.type = search.type; 708 $scope.activeIncidents = search.active == "true"; 709 $scope.notify = true; 710 $scope.msgValid = true; 711 $scope.message = ""; 712 $scope.duration = ""; 713 $scope.validateMsg = function () { 714 $scope.msgValid = (!$scope.notify) || ($scope.message != ""); 715 }; 716 $scope.durationValid = true; 717 $scope.validateDuration = function () { 718 $scope.durationValid = $scope.duration == "" || parseDuration($scope.duration).asMilliseconds() != 0; 719 }; 720 if (search.key) { 721 var keys = search.key; 722 if (!angular.isArray(search.key)) { 723 keys = [search.key]; 724 } 725 $location.search('key', null); 726 $scope.setKey('action-keys', keys); 727 } 728 else { 729 $scope.keys = $scope.getKey('action-keys'); 730 } 731 $scope.submit = function () { 732 $scope.validateMsg(); 733 $scope.validateDuration(); 734 if (!$scope.msgValid || ($scope.user == "") || !$scope.durationValid) { 735 return; 736 } 737 var data = { 738 Type: $scope.type, 739 Message: $scope.message, 740 Keys: $scope.keys, 741 Notify: $scope.notify, 742 }; 743 if ($scope.duration != "") { 744 data['Time'] = moment.utc().add(parseDuration($scope.duration)); 745 } 746 $http.post('/api/action', data) 747 .success(function (data) { 748 $location.url('/'); 749 }) 750 .error(function (error) { 751 alert(error); 752 }); 753 }; 754 }]); 755 /// <reference path="0-bosun.ts" /> 756 bosunControllers.controller('AnnotationCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) { 757 var search = $location.search(); 758 $scope.id = search.id; 759 if ($scope.id && $scope.id != "") { 760 $http.get('/api/annotation/' + $scope.id) 761 .success(function (data) { 762 $scope.annotation = new Annotation(data, true); 763 $scope.error = ""; 764 }) 765 .error(function (data) { 766 $scope.error = "failed to get annotation with id: " + $scope.id + ", error: " + data; 767 }); 768 } 769 else { 770 $scope.annotation = new Annotation(); 771 $scope.annotation.setTimeUTC(); 772 } 773 $http.get('/api/annotation/values/Owner') 774 .success(function (data) { 775 $scope.owners = data; 776 }); 777 $http.get('/api/annotation/values/Category') 778 .success(function (data) { 779 $scope.categories = data; 780 }); 781 $http.get('/api/annotation/values/Host') 782 .success(function (data) { 783 $scope.hosts = data; 784 }); 785 $scope.submitAnnotation = function () { 786 $scope.animate(); 787 $scope.annotation.CreationUser = $scope.auth.GetUsername(); 788 $http.post('/api/annotation', $scope.annotation) 789 .success(function (data) { 790 $scope.annotation = new Annotation(data, true); 791 $scope.error = ""; 792 $scope.submitSuccess = true; 793 $scope.deleteSuccess = false; 794 }) 795 .error(function (error) { 796 $scope.error = "failed to create annotation: " + error.error; 797 $scope.submitSuccess = false; 798 }) 799 .finally(function () { 800 $scope.stop(); 801 }); 802 }; 803 $scope.deleteAnnotation = function () { 804 $scope.animate(); 805 $http.delete('/api/annotation/' + $scope.annotation.Id) 806 .success(function (data) { 807 $scope.error = ""; 808 $scope.deleteSuccess = true; 809 $scope.submitSuccess = false; 810 $scope.annotation = new (Annotation); 811 $scope.annotation.setTimeUTC(); 812 }) 813 .error(function (error) { 814 $scope.error = "failed to delete annotation with id: " + $scope.annotation.Id + ", error: " + error.error; 815 $scope.deleteSuccess = false; 816 }) 817 .finally(function () { 818 $scope.stop(); 819 }); 820 }; 821 }]); 822 /// <reference path="0-bosun.ts" /> 823 var AuthService = /** @class */ (function () { 824 function AuthService() { 825 } 826 AuthService.prototype.Init = function (authEnabled, username, roles, userPerms) { 827 this.roles = roles; 828 this.username = username; 829 this.userPerms = userPerms; 830 this.authEnabled = authEnabled; 831 this.cleanRoles(); 832 if (!authEnabled) { 833 var cookVal = readCookie("action-user"); 834 if (cookVal) { 835 this.username = cookVal; 836 } 837 } 838 }; 839 AuthService.prototype.HasPermission = function (s) { 840 for (var _i = 0, _a = this.roles.Permissions; _i < _a.length; _i++) { 841 var p = _a[_i]; 842 if (p.Name == s) { 843 return (p.Bits & this.userPerms) != 0; 844 } 845 } 846 return true; 847 }; 848 AuthService.prototype.PermissionsFor = function (bits) { 849 if (bits == null) { 850 bits = this.userPerms; 851 } 852 var perms = []; 853 for (var _i = 0, _a = this.roles.Permissions; _i < _a.length; _i++) { 854 var p = _a[_i]; 855 if (p.Bits & bits) { 856 perms.push(p.Name); 857 } 858 } 859 return perms; 860 }; 861 AuthService.prototype.RoleFor = function (bits) { 862 if (bits == null) { 863 bits = this.userPerms; 864 } 865 var perms = []; 866 for (var _i = 0, _a = this.roles.Roles; _i < _a.length; _i++) { 867 var r = _a[_i]; 868 if (r.Bits == bits) { 869 return r.Name; 870 } 871 } 872 return null; 873 }; 874 AuthService.prototype.GetRoles = function () { 875 return this.roles; 876 }; 877 AuthService.prototype.Username = function (u) { 878 if (!this.authEnabled && angular.isDefined(u)) { 879 this.username = u; 880 createCookie("action-user", u, 90); 881 } 882 return this.username; 883 }; 884 AuthService.prototype.GetUsername = function () { 885 return this.username; 886 }; 887 AuthService.prototype.Enabled = function () { 888 return this.authEnabled; 889 }; 890 AuthService.prototype.cleanRoles = function () { 891 var _this = this; 892 //fix admin role that has extra bits corresponding to future permissions. 893 //causes bit math to go crazy and overflow. 894 //prevents easily making tokens that grant unknown future perms too. 895 _(this.roles.Roles).each(function (role) { 896 var mask = 0; 897 _(_this.roles.Permissions).each(function (p) { 898 if ((p.Bits & role.Bits) != 0) { 899 mask |= p.Bits; 900 } 901 }); 902 role.Bits = mask; 903 }); 904 }; 905 return AuthService; 906 }()); 907 bosunApp.service("authService", AuthService); 908 //simple component to show a <username-input> easily 909 var UsernameInputController = /** @class */ (function () { 910 function UsernameInputController(auth) { 911 this.auth = auth; 912 } 913 UsernameInputController.$inject = ['authService']; 914 return UsernameInputController; 915 }()); 916 bosunApp.component("usernameInput", { 917 controller: UsernameInputController, 918 controllerAs: "ct", 919 template: '<input type="text"class="form-control" ng-disabled="ct.auth.Enabled()" ng-model="ct.auth.Username" ng-model-options="{ getterSetter: true }">', 920 }); 921 /// <reference path="0-bosun.ts" /> 922 bosunControllers.controller('ConfigCtrl', ['$scope', '$http', '$location', '$route', '$timeout', '$sce', function ($scope, $http, $location, $route, $timeout, $sce) { 923 var search = $location.search(); 924 $scope.fromDate = search.fromDate || ''; 925 $scope.fromTime = search.fromTime || ''; 926 $scope.toDate = search.toDate || ''; 927 $scope.toTime = search.toTime || ''; 928 $scope.intervals = +search.intervals || 5; 929 $scope.duration = +search.duration || null; 930 $scope.runningHash = search.runningHash || null; 931 $scope.runningChanged = search.runningChanged || false; 932 $scope.config_text = 'Loading config...'; 933 $scope.selected_alert = search.alert || ''; 934 $scope.email = search.email || ''; 935 $scope.template_group = search.template_group || ''; 936 $scope.items = parseItems(); 937 $scope.tab = search.tab || 'results'; 938 $scope.aceTheme = 'chrome'; 939 $scope.actionTypeToShow = "Acknowledged"; 940 $scope.incidentId = 42; 941 $scope.aceMode = 'bosun'; 942 $scope.expandDiff = false; 943 $scope.customTemplates = {}; 944 $scope.runningChangedHelp = "The running config has been changed. This means you are in danger of overwriting someone else's changes. To view the changes open the 'Save Dialogue' and you will see a unified diff. The only way to get rid of the error panel is to open a new instance of the rule editor and copy your changes into it. You are still permitted to save without doing this, but then you must be very careful not to overwrite anyone else's changes."; 945 $scope.sectionToDocs = { 946 "alert": "https://bosun.org/definitions#alert-definitions", 947 "template": "https://bosun.org/definitions#templates", 948 "lookup": "https://bosun.org/definitions#lookup-tables", 949 "notification": "https://bosun.org/definitions#notifications", 950 "macro": "https://bosun.org/definitions#macros" 951 }; 952 var expr = search.expr; 953 function buildAlertFromExpr() { 954 if (!expr) 955 return; 956 var newAlertName = "test"; 957 var idx = 1; 958 //find a unique alert name 959 while ($scope.items["alert"].indexOf(newAlertName) != -1 || $scope.items["template"].indexOf(newAlertName) != -1) { 960 newAlertName = "test" + idx; 961 idx++; 962 } 963 var text = '\n\ntemplate ' + newAlertName + ' {\n' + 964 ' subject = {{.Last.Status}}: {{.Alert.Name}} on {{.Group.host}}\n' + 965 ' body = `<p>Name: {{.Alert.Name}}\n' + 966 ' <p>Tags:\n' + 967 ' <table>\n' + 968 ' {{range $k, $v := .Group}}\n' + 969 ' <tr><td>{{$k}}</td><td>{{$v}}</td></tr>\n' + 970 ' {{end}}\n' + 971 ' </table>`\n' + 972 '}\n\n'; 973 var expression = atob(expr); 974 var lines = expression.split("\n").map(function (l) { return l.trim(); }); 975 lines[lines.length - 1] = "crit = " + lines[lines.length - 1]; 976 expression = lines.join("\n "); 977 text += 'alert ' + newAlertName + ' {\n' + 978 ' template = ' + newAlertName + '\n' + 979 ' ' + expression + '\n' + 980 '}\n'; 981 $scope.config_text += text; 982 $scope.items = parseItems(); 983 $timeout(function () { 984 //can't scroll editor until after control is updated. Defer it. 985 $scope.scrollTo("alert", newAlertName); 986 }); 987 } 988 function parseItems() { 989 var configText = $scope.config_text; 990 var re = /^\s*(alert|template|notification|lookup|macro)\s+([\w\-\.\$]+)\s*\{/gm; 991 var match; 992 var items = {}; 993 items["alert"] = []; 994 items["template"] = []; 995 items["lookup"] = []; 996 items["notification"] = []; 997 items["macro"] = []; 998 while (match = re.exec(configText)) { 999 var type = match[1]; 1000 var name = match[2]; 1001 var list = items[type]; 1002 if (!list) { 1003 list = []; 1004 items[type] = list; 1005 } 1006 list.push(name); 1007 } 1008 return items; 1009 } 1010 $http.get('/api/config?hash=' + encodeURIComponent(search.hash || '')) 1011 .success(function (data) { 1012 $scope.config_text = data; 1013 $scope.items = parseItems(); 1014 buildAlertFromExpr(); 1015 if (!$scope.selected_alert && $scope.items["alert"].length) { 1016 $scope.selected_alert = $scope.items["alert"][0]; 1017 } 1018 $timeout(function () { 1019 //can't scroll editor until after control is updated. Defer it. 1020 $scope.scrollTo("alert", $scope.selected_alert); 1021 }); 1022 }) 1023 .error(function (data) { 1024 $scope.validationResult = "Error fetching config: " + data; 1025 }); 1026 $scope.reparse = function () { 1027 $scope.items = parseItems(); 1028 }; 1029 var editor; 1030 $scope.aceLoaded = function (_editor) { 1031 editor = _editor; 1032 $scope.editor = editor; 1033 editor.focus(); 1034 editor.getSession().setUseWrapMode(true); 1035 editor.on("blur", function () { 1036 $scope.$apply(function () { 1037 $scope.items = parseItems(); 1038 }); 1039 }); 1040 }; 1041 var syntax = true; 1042 $scope.aceToggleHighlight = function () { 1043 if (syntax) { 1044 editor.getSession().setMode(); 1045 syntax = false; 1046 return; 1047 } 1048 syntax = true; 1049 editor.getSession().setMode({ 1050 path: 'ace/mode/' + $scope.aceMode, 1051 v: Date.now() 1052 }); 1053 }; 1054 $scope.scrollTo = function (type, name) { 1055 var searchRegex = new RegExp("^\\s*" + type + "\\s+" + name, "g"); 1056 editor.find(searchRegex, { 1057 backwards: false, 1058 wrap: true, 1059 caseSensitive: false, 1060 wholeWord: false, 1061 regExp: true, 1062 }); 1063 if (type == "alert") { 1064 $scope.selectAlert(name); 1065 } 1066 }; 1067 $scope.scrollToInterval = function (id) { 1068 document.getElementById('time-' + id).scrollIntoView(); 1069 $scope.show($scope.sets[id]); 1070 }; 1071 $scope.show = function (set) { 1072 set.show = 'loading...'; 1073 $scope.animate(); 1074 var url = '/api/rule?' + 1075 'alert=' + encodeURIComponent($scope.selected_alert) + 1076 '&from=' + encodeURIComponent(set.Time); 1077 $http.post(url, $scope.config_text) 1078 .success(function (data) { 1079 procResults(data); 1080 set.Results = data.Sets[0].Results; 1081 }) 1082 .error(function (error) { 1083 $scope.errors = [error]; 1084 }) 1085 .finally(function () { 1086 $scope.stop(); 1087 delete (set.show); 1088 }); 1089 }; 1090 $scope.getRunningHash = function () { 1091 if (!$scope.saveEnabled) { 1092 return; 1093 } 1094 (function tick() { 1095 $http.get('/api/config/running_hash') 1096 .success(function (data) { 1097 $scope.runningHashResult = ''; 1098 $timeout(tick, 15 * 1000); 1099 if ($scope.runningHash) { 1100 if (data.Hash != $scope.runningHash) { 1101 $scope.runningChanged = true; 1102 return; 1103 } 1104 } 1105 $scope.runningHash = data.Hash; 1106 $scope.runningChanged = false; 1107 }) 1108 .error(function (data) { 1109 $scope.runningHashResult = "Error getting running config hash: " + data; 1110 }); 1111 })(); 1112 }; 1113 $scope.getRunningHash(); 1114 $scope.setInterval = function () { 1115 var from = moment.utc($scope.fromDate + ' ' + $scope.fromTime); 1116 var to = moment.utc($scope.toDate + ' ' + $scope.toTime); 1117 if (!from.isValid() || !to.isValid()) { 1118 return; 1119 } 1120 var diff = from.diff(to); 1121 if (!diff) { 1122 return; 1123 } 1124 var intervals = +$scope.intervals; 1125 if (intervals < 2) { 1126 return; 1127 } 1128 diff /= 1000 * 60; 1129 var d = Math.abs(Math.round(diff / intervals)); 1130 if (d < 1) { 1131 d = 1; 1132 } 1133 $scope.duration = d; 1134 }; 1135 $scope.setDuration = function () { 1136 var from = moment.utc($scope.fromDate + ' ' + $scope.fromTime); 1137 var to = moment.utc($scope.toDate + ' ' + $scope.toTime); 1138 if (!from.isValid() || !to.isValid()) { 1139 return; 1140 } 1141 var diff = from.diff(to); 1142 if (!diff) { 1143 return; 1144 } 1145 var duration = +$scope.duration; 1146 if (duration < 1) { 1147 return; 1148 } 1149 $scope.intervals = Math.abs(Math.round(diff / duration / 1000 / 60)); 1150 }; 1151 $scope.selectAlert = function (alert) { 1152 $scope.selected_alert = alert; 1153 $location.search("alert", alert); 1154 // Attempt to find `template = foo` in order to set up quick jump between template and alert 1155 var searchRegex = new RegExp("^\\s*alert\\s+" + alert, "g"); 1156 var lines = $scope.config_text.split("\n"); 1157 $scope.quickJumpTarget = null; 1158 for (var i = 0; i < lines.length; i++) { 1159 if (searchRegex.test(lines[i])) { 1160 for (var j = i + 1; j < lines.length; j++) { 1161 // Close bracket at start of line means end of alert. 1162 if (/^\s*\}/m.test(lines[j])) { 1163 return; 1164 } 1165 var found = /^\s*template\s*=\s*([\w\-\.\$]+)/m.exec(lines[j]); 1166 if (found) { 1167 $scope.quickJumpTarget = "template " + found[1]; 1168 } 1169 } 1170 } 1171 } 1172 }; 1173 $scope.quickJump = function () { 1174 var parts = $scope.quickJumpTarget.split(" "); 1175 if (parts.length != 2) { 1176 return; 1177 } 1178 $scope.scrollTo(parts[0], parts[1]); 1179 if (parts[0] == "template" && $scope.selected_alert) { 1180 $scope.quickJumpTarget = "alert " + $scope.selected_alert; 1181 } 1182 }; 1183 $scope.setTemplateGroup = function (group) { 1184 var match = group.match(/{(.*)}/); 1185 if (match) { 1186 $scope.template_group = match[1]; 1187 } 1188 }; 1189 $scope.setNotificationToShow = function (n) { 1190 $scope.notificationToShow = n; 1191 }; 1192 var line_re = /test:(\d+)/; 1193 $scope.validate = function () { 1194 $http.post('/api/config_test', $scope.config_text) 1195 .success(function (data) { 1196 if (data == "") { 1197 $scope.validationResult = "Valid"; 1198 $timeout(function () { 1199 $scope.validationResult = ""; 1200 }, 2000); 1201 } 1202 else { 1203 $scope.validationResult = data; 1204 var m = data.match(line_re); 1205 if (angular.isArray(m) && (m.length > 1)) { 1206 editor.gotoLine(m[1]); 1207 } 1208 } 1209 }) 1210 .error(function (error) { 1211 $scope.validationResult = 'Error validating: ' + error; 1212 }); 1213 }; 1214 $scope.test = function () { 1215 $scope.errors = []; 1216 $scope.running = true; 1217 $scope.warning = []; 1218 $location.search('fromDate', $scope.fromDate || null); 1219 $location.search('fromTime', $scope.fromTime || null); 1220 $location.search('toDate', $scope.toDate || null); 1221 $location.search('toTime', $scope.toTime || null); 1222 $location.search('intervals', String($scope.intervals) || null); 1223 $location.search('duration', String($scope.duration) || null); 1224 $location.search('email', $scope.email || null); 1225 $location.search('template_group', $scope.template_group || null); 1226 $location.search('runningHash', $scope.runningHash); 1227 $location.search('runningChanged', $scope.runningChanged); 1228 $scope.animate(); 1229 var from = moment.utc($scope.fromDate + ' ' + $scope.fromTime); 1230 var to = moment.utc($scope.toDate + ' ' + $scope.toTime); 1231 if (!from.isValid()) { 1232 from = to; 1233 } 1234 if (!to.isValid()) { 1235 to = from; 1236 } 1237 if (!from.isValid() && !to.isValid()) { 1238 from = to = moment.utc(); 1239 } 1240 var diff = from.diff(to); 1241 var intervals; 1242 if (diff == 0) { 1243 intervals = 1; 1244 } 1245 else if (Math.abs(diff) < 60 * 1000) { // 1 minute 1246 intervals = 2; 1247 } 1248 else { 1249 intervals = +$scope.intervals; 1250 } 1251 var url = '/api/rule?' + 1252 'alert=' + encodeURIComponent($scope.selected_alert) + 1253 '&from=' + encodeURIComponent(from.format()) + 1254 '&to=' + encodeURIComponent(to.format()) + 1255 '&intervals=' + encodeURIComponent(intervals) + 1256 '&email=' + encodeURIComponent($scope.email) + 1257 '&incidentId=' + $scope.incidentId + 1258 '&template_group=' + encodeURIComponent($scope.template_group); 1259 $http.post(url, $scope.config_text) 1260 .success(function (data) { 1261 $scope.sets = data.Sets; 1262 $scope.alert_history = data.AlertHistory; 1263 if (data.Hash) { 1264 $location.search('hash', data.Hash); 1265 } 1266 procResults(data); 1267 }) 1268 .error(function (error) { 1269 $scope.errors = [error]; 1270 }) 1271 .finally(function () { 1272 $scope.running = false; 1273 $scope.stop(); 1274 }); 1275 }; 1276 $scope.zws = function (v) { 1277 return v.replace(/([,{}()])/g, '$1\u200b'); 1278 }; 1279 $scope.loadTimelinePanel = function (entry, v) { 1280 if (v.doneLoading && !v.error) { 1281 return; 1282 } 1283 v.error = null; 1284 v.doneLoading = false; 1285 var ak = entry.key; 1286 var openBrack = ak.indexOf("{"); 1287 var closeBrack = ak.indexOf("}"); 1288 var alertName = ak.substr(0, openBrack); 1289 var template = ak.substring(openBrack + 1, closeBrack); 1290 var url = '/api/rule?' + 1291 'alert=' + encodeURIComponent(alertName) + 1292 '&from=' + encodeURIComponent(moment.utc(v.Time).format()) + 1293 '&template_group=' + encodeURIComponent(template); 1294 $http.post(url, $scope.config_text) 1295 .success(function (data) { 1296 v.subject = data.Subject; 1297 v.body = $sce.trustAsHtml(data.Body); 1298 }) 1299 .error(function (error) { 1300 v.error = error; 1301 }) 1302 .finally(function () { 1303 v.doneLoading = true; 1304 }); 1305 }; 1306 function procResults(data) { 1307 $scope.subject = data.Subject; 1308 $scope.body = $sce.trustAsHtml(data.Body); 1309 if (data.EmailSubject) { 1310 data.EmailSubject = atob(data.EmailSubject); 1311 } 1312 $scope.emailSubject = data.EmailSubject; 1313 if (data.EmailBody) { 1314 data.EmailBody = atob(data.EmailBody); 1315 } 1316 $scope.emailBody = $sce.trustAsHtml(data.EmailBody); 1317 $scope.customTemplates = {}; 1318 for (var k in data.Custom) { 1319 $scope.customTemplates[k] = data.Custom[k]; 1320 } 1321 var nots = {}; 1322 _(data.Notifications).each(function (val, n) { 1323 if (val.Email) { 1324 nots["Email " + n] = val.Email; 1325 } 1326 if (val.Print != "") { 1327 nots["Print " + n] = { Print: val.Print }; 1328 } 1329 _(val.HTTP).each(function (hp) { 1330 nots[hp.Method + " " + n] = hp; 1331 }); 1332 }); 1333 $scope.notifications = nots; 1334 var aNots = {}; 1335 _(data.ActionNotifications).each(function (ts, n) { 1336 $scope.notificationToShow = "" + n; 1337 aNots[n] = {}; 1338 _(ts).each(function (val, at) { 1339 if (val.Email) { 1340 aNots[n]["Email (" + at + ")"] = val.Email; 1341 } 1342 _(val.HTTP).each(function (hp) { 1343 aNots[n][hp.Method + " (" + at + ")"] = hp; 1344 }); 1345 }); 1346 }); 1347 $scope.actionNotifications = aNots; 1348 $scope.data = JSON.stringify(data.Data, null, ' '); 1349 $scope.errors = data.Errors; 1350 $scope.warning = data.Warnings; 1351 } 1352 $scope.downloadConfig = function () { 1353 var blob = new Blob([$scope.config_text], { type: "text/plain;charset=utf-8" }); 1354 saveAs(blob, "bosun.conf"); 1355 }; 1356 $scope.diffConfig = function () { 1357 $http.post('/api/config/diff', { 1358 "Config": $scope.config_text, 1359 "Message": $scope.message 1360 }) 1361 .success(function (data) { 1362 $scope.diff = data || "No Diff"; 1363 // Reset running hash if there is no difference? 1364 }) 1365 .error(function (error) { 1366 $scope.diff = "Failed to load diff: " + error; 1367 }); 1368 }; 1369 $scope.saveConfig = function () { 1370 if (!$scope.saveEnabled) { 1371 return; 1372 } 1373 $scope.saveResult = "Saving; Please Wait"; 1374 $http.post('/api/config/save', { 1375 "Config": $scope.config_text, 1376 "Diff": $scope.diff, 1377 "Message": $scope.message 1378 }) 1379 .success(function (data) { 1380 $scope.saveResult = "Config Saved; Reloading"; 1381 $scope.runningHash = undefined; 1382 }) 1383 .error(function (error) { 1384 $scope.saveResult = error; 1385 }); 1386 }; 1387 $scope.saveClass = function () { 1388 if ($scope.saveResult == "Saving; Please Wait") { 1389 return "alert-warning"; 1390 } 1391 if ($scope.saveResult == "Config Saved; Reloading") { 1392 return "alert-success"; 1393 } 1394 return "alert-danger"; 1395 }; 1396 return $scope; 1397 }]); 1398 var NotificationController = /** @class */ (function () { 1399 function NotificationController($http) { 1400 var _this = this; 1401 this.$http = $http; 1402 this.test = function () { 1403 _this.dat.msg = "sending"; 1404 _this.$http.post('/api/rule/notification/test', _this.dat) 1405 .success(function (rDat) { 1406 if (rDat.Error) { 1407 _this.dat.msg = "Error: " + rDat.Error; 1408 } 1409 else { 1410 _this.dat.msg = "Success! Status Code: " + rDat.Status; 1411 } 1412 }) 1413 .error(function (error) { 1414 _this.dat.msg = "Error: " + error; 1415 }); 1416 }; 1417 } 1418 NotificationController.$inject = ['$http']; 1419 return NotificationController; 1420 }()); 1421 bosunApp.component('notification', { 1422 bindings: { 1423 dat: "<", 1424 }, 1425 controller: NotificationController, 1426 controllerAs: 'ct', 1427 templateUrl: '/static/partials/notification.html', 1428 }); 1429 bosunControllers.controller('DashboardCtrl', ['$scope', '$http', '$location', function ($scope, $http, $location) { 1430 var search = $location.search(); 1431 $scope.loading = 'Loading'; 1432 $scope.error = ''; 1433 $scope.filter = search.filter; 1434 if (!$scope.filter) { 1435 $scope.filter = readCookie("filter"); 1436 } 1437 $location.search('filter', $scope.filter || null); 1438 reload(); 1439 function reload() { 1440 $scope.refresh($scope.filter).then(function () { 1441 $scope.loading = ''; 1442 $scope.error = ''; 1443 }, function (err) { 1444 $scope.loading = ''; 1445 $scope.error = 'Unable to fetch alerts: ' + err; 1446 }); 1447 } 1448 $scope.keydown = function ($event) { 1449 if ($event.keyCode == 13) { 1450 createCookie("filter", $scope.filter || "", 1000); 1451 $location.search('filter', $scope.filter || null); 1452 } 1453 }; 1454 }]); 1455 /// <reference path="0-bosun.ts" /> 1456 bosunApp.directive('tsResults', function () { 1457 return { 1458 templateUrl: '/partials/results.html', 1459 link: function (scope, elem, attrs) { 1460 scope.isSeries = function (v) { 1461 return typeof (v) === 'object'; 1462 }; 1463 }, 1464 }; 1465 }); 1466 bosunApp.directive('tsComputations', function () { 1467 return { 1468 scope: { 1469 computations: '=tsComputations', 1470 time: '=', 1471 header: '=', 1472 }, 1473 templateUrl: '/partials/computations.html', 1474 link: function (scope, elem, attrs) { 1475 if (scope.time) { 1476 var m = moment.utc(scope.time); 1477 scope.timeParam = "&date=" + encodeURIComponent(m.format("YYYY-MM-DD")) + "&time=" + encodeURIComponent(m.format("HH:mm")); 1478 } 1479 scope.btoa = function (v) { 1480 return encodeURIComponent(btoa(v)); 1481 }; 1482 }, 1483 }; 1484 }); 1485 function fmtDuration(v) { 1486 var diff = (moment.duration(v, 'milliseconds')); 1487 var f; 1488 if (Math.abs(v) < 60000) { 1489 return diff.format('ss[s]'); 1490 } 1491 return diff.format('d[d]hh[h]mm[m]ss[s]'); 1492 } 1493 function fmtTime(v) { 1494 var m = moment(v).utc(); 1495 var now = moment().utc(); 1496 var msdiff = now.diff(m); 1497 var ago = ''; 1498 var inn = ''; 1499 if (msdiff >= 0) { 1500 ago = ' ago'; 1501 } 1502 else { 1503 inn = 'in '; 1504 } 1505 return m.format() + ' UTC (' + inn + fmtDuration(Math.abs(msdiff)) + ago + ')'; 1506 } 1507 function parseDuration(v) { 1508 var pattern = /(\d+)(d|y|n|h|m|s)(-ago)?/; 1509 var m = pattern.exec(v); 1510 if (m) { 1511 return moment.duration(parseInt(m[1]), m[2].replace('n', 'M')); 1512 } 1513 return moment.duration(0); 1514 } 1515 bosunApp.directive("tsTime", function () { 1516 return { 1517 link: function (scope, elem, attrs) { 1518 scope.$watch(attrs.tsTime, function (v) { 1519 var m = moment(v).utc(); 1520 var text = fmtTime(v); 1521 if (attrs.tsEndTime) { 1522 var diff = moment(scope.$eval(attrs.tsEndTime)).diff(m); 1523 var duration = fmtDuration(diff); 1524 text += " for " + duration; 1525 } 1526 if (attrs.noLink) { 1527 elem.text(text); 1528 } 1529 else { 1530 var el = document.createElement('a'); 1531 el.text = text; 1532 el.href = 'http://www.timeanddate.com/worldclock/converted.html?iso='; 1533 el.href += m.format('YYYYMMDDTHHmm'); 1534 el.href += '&p1=0'; 1535 angular.forEach(scope.timeanddate, function (v, k) { 1536 el.href += '&p' + (k + 2) + '=' + v; 1537 }); 1538 elem.html(el); 1539 } 1540 }); 1541 }, 1542 }; 1543 }); 1544 bosunApp.directive("tsTimeUnix", function () { 1545 return { 1546 link: function (scope, elem, attrs) { 1547 scope.$watch(attrs.tsTimeUnix, function (v) { 1548 var m = moment(v * 1000).utc(); 1549 var text = fmtTime(m); 1550 if (attrs.tsEndTime) { 1551 var diff = moment(scope.$eval(attrs.tsEndTime)).diff(m); 1552 var duration = fmtDuration(diff); 1553 text += " for " + duration; 1554 } 1555 if (attrs.noLink) { 1556 elem.text(text); 1557 } 1558 else { 1559 var el = document.createElement('a'); 1560 el.text = text; 1561 el.href = 'http://www.timeanddate.com/worldclock/converted.html?iso='; 1562 el.href += m.format('YYYYMMDDTHHmm'); 1563 el.href += '&p1=0'; 1564 angular.forEach(scope.timeanddate, function (v, k) { 1565 el.href += '&p' + (k + 2) + '=' + v; 1566 }); 1567 elem.html(el); 1568 } 1569 }); 1570 }, 1571 }; 1572 }); 1573 bosunApp.directive("tsSince", function () { 1574 return { 1575 link: function (scope, elem, attrs) { 1576 scope.$watch(attrs.tsSince, function (v) { 1577 var m = moment(v).utc(); 1578 elem.text(m.fromNow()); 1579 }); 1580 }, 1581 }; 1582 }); 1583 bosunApp.directive("tooltip", function () { 1584 return { 1585 link: function (scope, elem, attrs) { 1586 angular.element(elem[0]).tooltip({ placement: "bottom" }); 1587 }, 1588 }; 1589 }); 1590 bosunApp.directive('tsTab', function () { 1591 return { 1592 link: function (scope, elem, attrs) { 1593 var ta = elem[0]; 1594 elem.keydown(function (evt) { 1595 if (evt.ctrlKey) { 1596 return; 1597 } 1598 // This is so shift-enter can be caught to run a rule when tsTab is called from 1599 // the rule page 1600 if (evt.keyCode == 13 && evt.shiftKey) { 1601 return; 1602 } 1603 switch (evt.keyCode) { 1604 case 9: // tab 1605 evt.preventDefault(); 1606 var v = ta.value; 1607 var start = ta.selectionStart; 1608 ta.value = v.substr(0, start) + "\t" + v.substr(start); 1609 ta.selectionStart = ta.selectionEnd = start + 1; 1610 return; 1611 case 13: // enter 1612 if (ta.selectionStart != ta.selectionEnd) { 1613 return; 1614 } 1615 evt.preventDefault(); 1616 var v = ta.value; 1617 var start = ta.selectionStart; 1618 var sub = v.substr(0, start); 1619 var last = sub.lastIndexOf("\n") + 1; 1620 for (var i = last; i < sub.length && /[ \t]/.test(sub[i]); i++) 1621 ; 1622 var ws = sub.substr(last, i - last); 1623 ta.value = v.substr(0, start) + "\n" + ws + v.substr(start); 1624 ta.selectionStart = ta.selectionEnd = start + 1 + ws.length; 1625 } 1626 }); 1627 }, 1628 }; 1629 }); 1630 bosunApp.directive('tsresizable', function () { 1631 return { 1632 restrict: 'A', 1633 scope: { 1634 callback: '&onResize' 1635 }, 1636 link: function postLink(scope, elem, attrs) { 1637 elem.resizable(); 1638 elem.on('resizestop', function (evt, ui) { 1639 if (scope.callback) { 1640 scope.callback(); 1641 } 1642 }); 1643 } 1644 }; 1645 }); 1646 bosunApp.directive('tsTableSort', ['$timeout', function ($timeout) { 1647 return { 1648 link: function (scope, elem, attrs) { 1649 $timeout(function () { 1650 $(elem).tablesorter({ 1651 sortList: scope.$eval(attrs.tsTableSort), 1652 }); 1653 }); 1654 }, 1655 }; 1656 }]); 1657 // https://gist.github.com/mlynch/dd407b93ed288d499778 1658 bosunApp.directive('autofocus', ['$timeout', function ($timeout) { 1659 return { 1660 restrict: 'A', 1661 link: function ($scope, $element) { 1662 $timeout(function () { 1663 $element[0].focus(); 1664 }); 1665 } 1666 }; 1667 }]); 1668 bosunApp.directive('tsTimeLine', function () { 1669 var tsdbFormat = d3.time.format.utc("%Y/%m/%d-%X"); 1670 function parseDate(s) { 1671 return moment.utc(s).toDate(); 1672 } 1673 var margin = { 1674 top: 10, 1675 right: 10, 1676 bottom: 30, 1677 left: 250, 1678 }; 1679 return { 1680 link: function (scope, elem, attrs) { 1681 scope.shown = {}; 1682 scope.collapse = function (i, entry, v) { 1683 scope.shown[i] = !scope.shown[i]; 1684 if (scope.loadTimelinePanel && entry && scope.shown[i]) { 1685 scope.loadTimelinePanel(entry, v); 1686 } 1687 }; 1688 scope.$watch('alert_history', update); 1689 function update(history) { 1690 if (!history) { 1691 return; 1692 } 1693 var entries = d3.entries(history); 1694 if (!entries.length) { 1695 return; 1696 } 1697 entries.sort(function (a, b) { 1698 return a.key.localeCompare(b.key); 1699 }); 1700 scope.entries = entries; 1701 var values = entries.map(function (v) { return v.value; }); 1702 var keys = entries.map(function (v) { return v.key; }); 1703 var barheight = 500 / values.length; 1704 barheight = Math.min(barheight, 45); 1705 barheight = Math.max(barheight, 15); 1706 var svgHeight = values.length * barheight + margin.top + margin.bottom; 1707 var height = svgHeight - margin.top - margin.bottom; 1708 var svgWidth = elem.width(); 1709 var width = svgWidth - margin.left - margin.right; 1710 var xScale = d3.time.scale.utc().range([0, width]); 1711 var xAxis = d3.svg.axis() 1712 .scale(xScale) 1713 .orient('bottom'); 1714 elem.empty(); 1715 var svg = d3.select(elem[0]) 1716 .append('svg') 1717 .attr('width', svgWidth) 1718 .attr('height', svgHeight) 1719 .append('g') 1720 .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 1721 svg.append('g') 1722 .attr('class', 'x axis tl-axis') 1723 .attr('transform', 'translate(0,' + height + ')'); 1724 xScale.domain([ 1725 d3.min(values, function (d) { return d3.min(d.History, function (c) { return parseDate(c.Time); }); }), 1726 d3.max(values, function (d) { return d3.max(d.History, function (c) { return parseDate(c.EndTime); }); }), 1727 ]); 1728 var legend = d3.select(elem[0]) 1729 .append('div') 1730 .attr('class', 'tl-legend'); 1731 var time_legend = legend 1732 .append('div') 1733 .text(values[0].History[0].Time); 1734 var alert_legend = legend 1735 .append('div') 1736 .text(keys[0]); 1737 svg.select('.x.axis') 1738 .transition() 1739 .call(xAxis); 1740 var chart = svg.append('g'); 1741 angular.forEach(entries, function (entry, i) { 1742 chart.selectAll('.bars') 1743 .data(entry.value.History) 1744 .enter() 1745 .append('rect') 1746 .attr('class', function (d) { return 'tl-' + d.Status; }) 1747 .attr('x', function (d) { return xScale(parseDate(d.Time)); }) 1748 .attr('y', i * barheight) 1749 .attr('height', barheight) 1750 .attr('width', function (d) { 1751 return xScale(parseDate(d.EndTime)) - xScale(parseDate(d.Time)); 1752 }) 1753 .on('mousemove.x', mousemove_x) 1754 .on('mousemove.y', function (d) { 1755 alert_legend.text(entry.key); 1756 }) 1757 .on('click', function (d, j) { 1758 var id = 'panel' + i + '-' + j; 1759 scope.shown['group' + i] = true; 1760 scope.shown[id] = true; 1761 if (scope.loadTimelinePanel) { 1762 scope.loadTimelinePanel(entry, d); 1763 } 1764 scope.$apply(); 1765 setTimeout(function () { 1766 var e = $("#" + id); 1767 if (!e) { 1768 console.log('no', id, e); 1769 return; 1770 } 1771 $('html, body').scrollTop(e.offset().top); 1772 }); 1773 }); 1774 }); 1775 chart.selectAll('.labels') 1776 .data(keys) 1777 .enter() 1778 .append('text') 1779 .attr('text-anchor', 'end') 1780 .attr('x', 0) 1781 .attr('dx', '-.5em') 1782 .attr('dy', '.25em') 1783 .attr('y', function (d, i) { return (i + .5) * barheight; }) 1784 .text(function (d) { return d; }); 1785 chart.selectAll('.sep') 1786 .data(values) 1787 .enter() 1788 .append('rect') 1789 .attr('y', function (d, i) { return (i + 1) * barheight; }) 1790 .attr('height', 1) 1791 .attr('x', 0) 1792 .attr('width', width) 1793 .on('mousemove.x', mousemove_x); 1794 function mousemove_x() { 1795 var x = xScale.invert(d3.mouse(this)[0]); 1796 time_legend 1797 .text(tsdbFormat(x)); 1798 } 1799 } 1800 ; 1801 }, 1802 }; 1803 }); 1804 var fmtUnits = ['', 'k', 'M', 'G', 'T', 'P', 'E']; 1805 function nfmt(s, mult, suffix, opts) { 1806 opts = opts || {}; 1807 var n = parseFloat(s); 1808 if (isNaN(n) && typeof s === 'string') { 1809 return s; 1810 } 1811 if (opts.round) 1812 n = Math.round(n); 1813 if (!n) 1814 return suffix ? '0 ' + suffix : '0'; 1815 if (isNaN(n) || !isFinite(n)) 1816 return '-'; 1817 var a = Math.abs(n); 1818 if (a >= 1) { 1819 var number = Math.floor(Math.log(a) / Math.log(mult)); 1820 a /= Math.pow(mult, Math.floor(number)); 1821 if (fmtUnits[number]) { 1822 suffix = fmtUnits[number] + suffix; 1823 } 1824 } 1825 var r = a.toFixed(5); 1826 if (a < 1e-5) { 1827 r = a.toString(); 1828 } 1829 var neg = n < 0 ? '-' : ''; 1830 return neg + (+r) + suffix; 1831 } 1832 bosunApp.filter('nfmt', function () { 1833 return function (s) { 1834 return nfmt(s, 1000, '', {}); 1835 }; 1836 }); 1837 bosunApp.filter('bytes', function () { 1838 return function (s) { 1839 return nfmt(s, 1024, 'B', { round: true }); 1840 }; 1841 }); 1842 bosunApp.filter('bits', function () { 1843 return function (s) { 1844 return nfmt(s, 1024, 'b', { round: true }); 1845 }; 1846 }); 1847 bosunApp.directive('elastic', [ 1848 '$timeout', 1849 function ($timeout) { 1850 return { 1851 restrict: 'A', 1852 link: function ($scope, element) { 1853 $scope.initialHeight = $scope.initialHeight || element[0].style.height; 1854 var resize = function () { 1855 element[0].style.height = $scope.initialHeight; 1856 element[0].style.height = "" + element[0].scrollHeight + "px"; 1857 }; 1858 element.on("input change", resize); 1859 $timeout(resize, 0); 1860 } 1861 }; 1862 } 1863 ]); 1864 bosunApp.directive('tsBar', ['$window', 'nfmtFilter', function ($window, fmtfilter) { 1865 var margin = { 1866 top: 20, 1867 right: 20, 1868 bottom: 0, 1869 left: 200, 1870 }; 1871 return { 1872 scope: { 1873 data: '=', 1874 height: '=', 1875 }, 1876 link: function (scope, elem, attrs) { 1877 var svgHeight = +scope.height || 150; 1878 var height = svgHeight - margin.top - margin.bottom; 1879 var svgWidth; 1880 var width; 1881 var xScale = d3.scale.linear(); 1882 var yScale = d3.scale.ordinal(); 1883 var top = d3.select(elem[0]) 1884 .append('svg') 1885 .attr('height', svgHeight) 1886 .attr('width', '100%'); 1887 var svg = top 1888 .append('g'); 1889 //.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 1890 var xAxis = d3.svg.axis() 1891 .scale(xScale) 1892 .orient("top"); 1893 var yAxis = d3.svg.axis() 1894 .scale(yScale) 1895 .orient("left"); 1896 scope.$watch('data', update); 1897 var w = angular.element($window); 1898 scope.$watch(function () { 1899 return w.width(); 1900 }, resize, true); 1901 w.bind('resize', function () { 1902 scope.$apply(); 1903 }); 1904 function resize() { 1905 if (!scope.data) { 1906 return; 1907 } 1908 svgWidth = elem.width(); 1909 if (svgWidth <= 0) { 1910 return; 1911 } 1912 margin.left = d3.max(scope.data, function (d) { return d.name.length * 8; }); 1913 width = svgWidth - margin.left - margin.right; 1914 svgHeight = scope.data.length * 15; 1915 height = svgHeight - margin.top - margin.bottom; 1916 xScale.range([0, width]); 1917 yScale.rangeRoundBands([0, height], .1); 1918 yAxis.scale(yScale); 1919 svg.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 1920 svg.attr('width', svgWidth); 1921 svg.attr('height', height); 1922 top.attr('height', svgHeight); 1923 xAxis.ticks(width / 60); 1924 draw(); 1925 } 1926 function update(v) { 1927 if (!angular.isArray(v) || v.length == 0) { 1928 return; 1929 } 1930 resize(); 1931 } 1932 function draw() { 1933 if (!scope.data) { 1934 return; 1935 } 1936 yScale.domain(scope.data.map(function (d) { return d.name; })); 1937 xScale.domain([0, d3.max(scope.data, function (d) { return d.Value; })]); 1938 svg.selectAll('g.axis').remove(); 1939 //X axis 1940 svg.append("g") 1941 .attr("class", "x axis") 1942 .call(xAxis); 1943 svg.append("g") 1944 .attr("class", "y axis") 1945 .call(yAxis) 1946 .selectAll("text") 1947 .style("text-anchor", "end"); 1948 var bars = svg.selectAll(".bar").data(scope.data); 1949 bars.enter() 1950 .append("rect") 1951 .attr("class", "bar") 1952 .attr("y", function (d) { return yScale(d.name); }) 1953 .attr("height", yScale.rangeBand()) 1954 .attr('width', function (d) { return xScale(d.Value); }); 1955 } 1956 ; 1957 }, 1958 }; 1959 }]); 1960 bosunApp.directive('tsGraph', ['$window', 'nfmtFilter', function ($window, fmtfilter) { 1961 var margin = { 1962 top: 10, 1963 right: 10, 1964 bottom: 30, 1965 left: 80, 1966 }; 1967 return { 1968 scope: { 1969 data: '=', 1970 annotations: '=', 1971 height: '=', 1972 generator: '=', 1973 brushStart: '=bstart', 1974 brushEnd: '=bend', 1975 enableBrush: '@', 1976 max: '=', 1977 min: '=', 1978 normalize: '=', 1979 annotation: '=', 1980 annotateEnabled: '=', 1981 showAnnotations: '=', 1982 }, 1983 template: '<div class="row"></div>' + // chartElemt 1984 '<div class="row col-lg-12"></div>' + // timeElem 1985 '<div class"row">' + // legendAnnContainer 1986 '<div class="col-lg-6"></div>' + // legendElem 1987 '<div class="col-lg-6"></div>' + // annElem 1988 '</div>', 1989 link: function (scope, elem, attrs, $compile) { 1990 var chartElem = d3.select(elem.children()[0]); 1991 var timeElem = d3.select(elem.children()[1]); 1992 var legendAnnContainer = angular.element(elem.children()[2]); 1993 var legendElem = d3.select(legendAnnContainer.children()[0]); 1994 if (scope.annotateEnabled) { 1995 var annElem = d3.select(legendAnnContainer.children()[1]); 1996 } 1997 var valueIdx = 1; 1998 if (scope.normalize) { 1999 valueIdx = 2; 2000 } 2001 var svgHeight = +scope.height || 150; 2002 var height = svgHeight - margin.top - margin.bottom; 2003 var svgWidth; 2004 var width; 2005 var yScale = d3.scale.linear().range([height, 0]); 2006 var xScale = d3.time.scale.utc(); 2007 var xAxis = d3.svg.axis() 2008 .orient('bottom'); 2009 var yAxis = d3.svg.axis() 2010 .scale(yScale) 2011 .orient('left') 2012 .ticks(Math.min(10, height / 20)) 2013 .tickFormat(fmtfilter); 2014 var line; 2015 switch (scope.generator) { 2016 case 'area': 2017 line = d3.svg.area(); 2018 break; 2019 default: 2020 line = d3.svg.line(); 2021 } 2022 var brush = d3.svg.brush() 2023 .x(xScale) 2024 .on('brush', brushed) 2025 .on('brushend', annotateBrushed); 2026 var top = chartElem 2027 .append('svg') 2028 .attr('height', svgHeight) 2029 .attr('width', '100%'); 2030 var svg = top 2031 .append('g') 2032 .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 2033 var defs = svg.append('defs') 2034 .append('clipPath') 2035 .attr('id', 'clip') 2036 .append('rect') 2037 .attr('height', height); 2038 var chart = svg.append('g') 2039 .attr('pointer-events', 'all') 2040 .attr('clip-path', 'url(#clip)'); 2041 svg.append('g') 2042 .attr('class', 'x axis') 2043 .attr('transform', 'translate(0,' + height + ')'); 2044 svg.append('g') 2045 .attr('class', 'y axis'); 2046 var paths = chart.append('g'); 2047 chart.append('g') 2048 .attr('class', 'x brush'); 2049 if (scope.annotateEnabled) { 2050 var ann = chart.append('g'); 2051 } 2052 top.append('rect') 2053 .style('opacity', 0) 2054 .attr('x', 0) 2055 .attr('y', 0) 2056 .attr('height', height) 2057 .attr('width', margin.left) 2058 .style('cursor', 'pointer') 2059 .on('click', yaxisToggle); 2060 var xloc = timeElem.append('div').attr("class", "col-lg-6"); 2061 xloc.style('float', 'left'); 2062 var brushText = timeElem.append('div').attr("class", "col-lg-6").append('p').attr("class", "text-right"); 2063 var legend = legendElem; 2064 var aLegend = annElem; 2065 var color = d3.scale.ordinal().range([ 2066 '#e41a1c', 2067 '#377eb8', 2068 '#4daf4a', 2069 '#984ea3', 2070 '#ff7f00', 2071 '#a65628', 2072 '#f781bf', 2073 '#999999', 2074 ]); 2075 var annColor = d3.scale.ordinal().range([ 2076 '#e41a1c', 2077 '#377eb8', 2078 '#4daf4a', 2079 '#984ea3', 2080 '#ff7f00', 2081 '#a65628', 2082 '#f781bf', 2083 '#999999', 2084 ]); 2085 var mousex = 0; 2086 var mousey = 0; 2087 var oldx = 0; 2088 var hover = svg.append('g') 2089 .attr('class', 'hover') 2090 .style('pointer-events', 'none') 2091 .style('display', 'none'); 2092 var hoverPoint = hover.append('svg:circle') 2093 .attr('r', 5); 2094 var hoverRect = hover.append('svg:rect') 2095 .attr('fill', 'white'); 2096 var hoverText = hover.append('svg:text') 2097 .style('font-size', '12px'); 2098 var focus = svg.append('g') 2099 .attr('class', 'focus') 2100 .style('pointer-events', 'none'); 2101 focus.append('line'); 2102 var yaxisZero = false; 2103 function yaxisToggle() { 2104 yaxisZero = !yaxisZero; 2105 draw(); 2106 } 2107 var drawAnnLegend = function () { 2108 if (scope.annotation) { 2109 aLegend.html(''); 2110 var a = scope.annotation; 2111 //var table = aLegend.append('table').attr("class", "table table-condensed") 2112 var table = aLegend.append("div"); 2113 var row = table.append("div").attr("class", "row"); 2114 row.append("div").attr("class", "col-lg-2").text("CreationUser"); 2115 row.append("div").attr("class", "col-lg-10").text(a.CreationUser); 2116 row = table.append("div").attr("class", "row"); 2117 row.append("div").attr("class", "col-lg-2").text("Owner"); 2118 row.append("div").attr("class", "col-lg-10").text(a.Owner); 2119 row = table.append("div").attr("class", "row"); 2120 row.append("div").attr("class", "col-lg-2").text("Url"); 2121 row.append("div").attr("class", "col-lg-10").append('a') 2122 .attr("xlink:href", a.Url).text(a.Url).on("click", function (d) { 2123 window.open(a.Url, "_blank"); 2124 }); 2125 row = table.append("div").attr("class", "row"); 2126 row.append("div").attr("class", "col-lg-2").text("Category"); 2127 row.append("div").attr("class", "col-lg-10").text(a.Category); 2128 row = table.append("div").attr("class", "row"); 2129 row.append("div").attr("class", "col-lg-2").text("Host"); 2130 row.append("div").attr("class", "col-lg-10").text(a.Host); 2131 row = table.append("div").attr("class", "row"); 2132 row.append("div").attr("class", "col-lg-2").text("Message"); 2133 row.append("div").attr("class", "col-lg-10").text(a.Message); 2134 } // 2135 }; 2136 var drawLegend = _.throttle(function (normalizeIdx) { 2137 var names = legend.selectAll('.series') 2138 .data(scope.data, function (d) { return d.Name; }); 2139 names.enter() 2140 .append('div') 2141 .attr('class', 'series'); 2142 names.exit() 2143 .remove(); 2144 var xi = xScale.invert(mousex); 2145 xloc.text('Time: ' + fmtTime(xi)); 2146 var t = xi.getTime() / 1000; 2147 var minDist = width + height; 2148 var minName, minColor; 2149 var minX, minY; 2150 names 2151 .each(function (d) { 2152 var idx = bisect(d.Data, t); 2153 if (idx >= d.Data.length) { 2154 idx = d.Data.length - 1; 2155 } 2156 var e = d3.select(this); 2157 var pt = d.Data[idx]; 2158 if (pt) { 2159 e.attr('title', pt[normalizeIdx]); 2160 e.text(d.Name + ': ' + fmtfilter(pt[1])); 2161 var ptx = xScale(pt[0] * 1000); 2162 var pty = yScale(pt[normalizeIdx]); 2163 var ptd = Math.sqrt(Math.pow(ptx - mousex, 2) + 2164 Math.pow(pty - mousey, 2)); 2165 if (ptd < minDist) { 2166 minDist = ptd; 2167 minX = ptx; 2168 minY = pty; 2169 minName = d.Name + ': ' + pt[1]; 2170 minColor = color(d.Name); 2171 } 2172 } 2173 }) 2174 .style('color', function (d) { return color(d.Name); }); 2175 hover 2176 .attr('transform', 'translate(' + minX + ',' + minY + ')'); 2177 hoverPoint.style('fill', minColor); 2178 hoverText 2179 .text(minName) 2180 .style('fill', minColor); 2181 var isRight = minX > width / 2; 2182 var isBottom = minY > height / 2; 2183 hoverText 2184 .attr('x', isRight ? -5 : 5) 2185 .attr('y', isBottom ? -8 : 15) 2186 .attr('text-anchor', isRight ? 'end' : 'start'); 2187 var node = hoverText.node(); 2188 var bb = node.getBBox(); 2189 hoverRect 2190 .attr('x', bb.x - 1) 2191 .attr('y', bb.y - 1) 2192 .attr('height', bb.height + 2) 2193 .attr('width', bb.width + 2); 2194 var x = mousex; 2195 if (x > width) { 2196 x = 0; 2197 } 2198 focus.select('line') 2199 .attr('x1', x) 2200 .attr('x2', x) 2201 .attr('y1', 0) 2202 .attr('y2', height); 2203 if (extentStart) { 2204 var s = extentStart; 2205 if (extentEnd != extentStart) { 2206 s += ' - ' + extentEnd; 2207 s += ' (' + extentDiff + ')'; 2208 } 2209 brushText.text(s); 2210 } 2211 }, 50); 2212 scope.$watchCollection('[data, annotations, showAnnotations]', update); 2213 var showAnnotations = function (show) { 2214 if (show) { 2215 ann.attr("visibility", "visible"); 2216 return; 2217 } 2218 ann.attr("visibility", "hidden"); 2219 aLegend.html(''); 2220 }; 2221 var w = angular.element($window); 2222 scope.$watch(function () { 2223 return w.width(); 2224 }, resize, true); 2225 w.bind('resize', function () { 2226 scope.$apply(); 2227 }); 2228 function resize() { 2229 svgWidth = elem.width(); 2230 if (svgWidth <= 0) { 2231 return; 2232 } 2233 width = svgWidth - margin.left - margin.right; 2234 xScale.range([0, width]); 2235 xAxis.scale(xScale); 2236 if (!mousex) { 2237 mousex = width + 1; 2238 } 2239 svg.attr('width', svgWidth); 2240 defs.attr('width', width); 2241 xAxis.ticks(width / 60); 2242 draw(); 2243 } 2244 var oldx = 0; 2245 var bisect = d3.bisector(function (d) { return d[0]; }).left; 2246 var bisectA = d3.bisector(function (d) { return moment(d.StartDate).unix(); }).left; 2247 function update(v) { 2248 if (!angular.isArray(v) || v.length == 0) { 2249 return; 2250 } 2251 d3.selectAll(".x.brush").call(brush.clear()); 2252 if (scope.annotateEnabled) { 2253 showAnnotations(scope.showAnnotations); 2254 } 2255 resize(); 2256 } 2257 function draw() { 2258 if (!scope.data) { 2259 return; 2260 } 2261 if (scope.normalize) { 2262 valueIdx = 2; 2263 } 2264 function mousemove() { 2265 var pt = d3.mouse(this); 2266 mousex = pt[0]; 2267 mousey = pt[1]; 2268 drawLegend(valueIdx); 2269 } 2270 scope.data.map(function (data, i) { 2271 var max = d3.max(data.Data, function (d) { return d[1]; }); 2272 data.Data.map(function (d, j) { 2273 d.push(d[1] / max * 100 || 0); 2274 }); 2275 }); 2276 line.y(function (d) { return yScale(d[valueIdx]); }); 2277 line.x(function (d) { return xScale(d[0] * 1000); }); 2278 var xdomain = [ 2279 d3.min(scope.data, function (d) { return d3.min(d.Data, function (c) { return c[0]; }); }) * 1000, 2280 d3.max(scope.data, function (d) { return d3.max(d.Data, function (c) { return c[0]; }); }) * 1000, 2281 ]; 2282 if (!oldx) { 2283 oldx = xdomain[1]; 2284 } 2285 xScale.domain(xdomain); 2286 var ymin = d3.min(scope.data, function (d) { return d3.min(d.Data, function (c) { return c[1]; }); }); 2287 var ymax = d3.max(scope.data, function (d) { return d3.max(d.Data, function (c) { return c[valueIdx]; }); }); 2288 var diff = (ymax - ymin) / 50; 2289 if (!diff) { 2290 diff = 1; 2291 } 2292 ymin -= diff; 2293 ymax += diff; 2294 if (yaxisZero) { 2295 if (ymin > 0) { 2296 ymin = 0; 2297 } 2298 else if (ymax < 0) { 2299 ymax = 0; 2300 } 2301 } 2302 var ydomain = [ymin, ymax]; 2303 if (angular.isNumber(scope.min)) { 2304 ydomain[0] = +scope.min; 2305 } 2306 if (angular.isNumber(scope.max)) { 2307 ydomain[valueIdx] = +scope.max; 2308 } 2309 yScale.domain(ydomain); 2310 if (scope.generator == 'area') { 2311 line.y0(yScale(0)); 2312 } 2313 svg.select('.x.axis') 2314 .transition() 2315 .call(xAxis); 2316 svg.select('.y.axis') 2317 .transition() 2318 .call(yAxis); 2319 svg.append('text') 2320 .attr("class", "ylabel") 2321 .attr("transform", "rotate(-90)") 2322 .attr("y", -margin.left) 2323 .attr("x", -(height / 2)) 2324 .attr("dy", "1em") 2325 .text(_.uniq(scope.data.map(function (v) { return v.Unit; })).join("; ")); 2326 if (scope.annotateEnabled) { 2327 var rowId = {}; // annotation Id -> rowId 2328 var rowEndDate = {}; // rowId -> EndDate 2329 var maxRow = 0; 2330 for (var i = 0; i < scope.annotations.length; i++) { 2331 if (i == 0) { 2332 rowId[scope.annotations[i].Id] = 0; 2333 rowEndDate[0] = scope.annotations[0].EndDate; 2334 continue; 2335 } 2336 for (var row = 0; row <= maxRow + 1; row++) { 2337 if (row == maxRow + 1) { 2338 rowId[scope.annotations[i].Id] = row; 2339 rowEndDate[row] = scope.annotations[i].EndDate; 2340 maxRow += 1; 2341 break; 2342 } 2343 if (rowEndDate[row] < scope.annotations[i].StartDate) { 2344 rowId[scope.annotations[i].Id] = row; 2345 rowEndDate[row] = scope.annotations[i].EndDate; 2346 break; 2347 } 2348 } 2349 } 2350 var annotations = ann.selectAll('.annotation') 2351 .data(scope.annotations, function (d) { return d.Id; }); 2352 annotations.enter() 2353 .append("svg:a") 2354 .append('rect') 2355 .attr('visilibity', function () { 2356 if (scope.showAnnotations) { 2357 return "visible"; 2358 } 2359 return "hidden"; 2360 }) 2361 .attr("y", function (d) { return rowId[d.Id] * ((height * .05) + 2); }) 2362 .attr("height", height * .05) 2363 .attr("class", "annotation") 2364 .attr("stroke", function (d) { return annColor(d.Id); }) 2365 .attr("stroke-opacity", .5) 2366 .attr("fill", function (d) { return annColor(d.Id); }) 2367 .attr("fill-opacity", 0.1) 2368 .attr("stroke-width", 1) 2369 .attr("x", function (d) { return xScale(moment(d.StartDate).utc().unix() * 1000); }) 2370 .attr("width", function (d) { 2371 var startT = moment(d.StartDate).utc().unix() * 1000; 2372 var endT = moment(d.EndDate).utc().unix() * 1000; 2373 var calcWidth = xScale(endT) - xScale(startT); 2374 // Never render boxes with less than 8 pixels are they are difficult to click 2375 if (calcWidth < 8) { 2376 return 8; 2377 } 2378 return calcWidth; 2379 }) 2380 .on("mouseenter", function (ann) { 2381 if (!scope.showAnnotations) { 2382 return; 2383 } 2384 if (ann) { 2385 scope.annotation = ann; 2386 drawAnnLegend(); 2387 } 2388 scope.$apply(); 2389 }) 2390 .on("click", function () { 2391 if (!scope.showAnnotations) { 2392 return; 2393 } 2394 angular.element('#modalShower').trigger('click'); 2395 }); 2396 annotations.exit().remove(); 2397 } 2398 var queries = paths.selectAll('.line') 2399 .data(scope.data, function (d) { return d.Name; }); 2400 switch (scope.generator) { 2401 case 'area': 2402 queries.enter() 2403 .append('path') 2404 .attr('stroke', function (d) { return color(d.Name); }) 2405 .attr('class', 'line') 2406 .style('fill', function (d) { return color(d.Name); }); 2407 break; 2408 default: 2409 queries.enter() 2410 .append('path') 2411 .attr('stroke', function (d) { return color(d.Name); }) 2412 .attr('class', 'line'); 2413 } 2414 queries.exit() 2415 .remove(); 2416 queries 2417 .attr('d', function (d) { return line(d.Data); }) 2418 .attr('transform', null) 2419 .transition() 2420 .ease('linear') 2421 .attr('transform', 'translate(' + (xScale(oldx) - xScale(xdomain[1])) + ')'); 2422 chart.select('.x.brush') 2423 .call(brush) 2424 .selectAll('rect') 2425 .attr('height', height) 2426 .on('mouseover', function () { 2427 hover.style('display', 'block'); 2428 }) 2429 .on('mouseout', function () { 2430 hover.style('display', 'none'); 2431 }) 2432 .on('mousemove', mousemove); 2433 chart.select('.x.brush .extent') 2434 .style('stroke', '#fff') 2435 .style('fill-opacity', '.125') 2436 .style('shape-rendering', 'crispEdges'); 2437 oldx = xdomain[1]; 2438 drawLegend(valueIdx); 2439 } 2440 ; 2441 var extentStart; 2442 var extentEnd; 2443 var extentDiff; 2444 function brushed() { 2445 var e; 2446 e = d3.event.sourceEvent; 2447 if (e.shiftKey) { 2448 return; 2449 } 2450 var extent = brush.extent(); 2451 extentStart = datefmt(extent[0]); 2452 extentEnd = datefmt(extent[1]); 2453 extentDiff = fmtDuration(moment(extent[1]).diff(moment(extent[0]))); 2454 drawLegend(valueIdx); 2455 if (scope.enableBrush && extentEnd != extentStart) { 2456 scope.brushStart = extentStart; 2457 scope.brushEnd = extentEnd; 2458 scope.$apply(); 2459 } 2460 } 2461 function annotateBrushed() { 2462 if (!scope.annotateEnabled) { 2463 return; 2464 } 2465 var e; 2466 e = d3.event.sourceEvent; 2467 if (!e.shiftKey) { 2468 return; 2469 } 2470 var extent = brush.extent(); 2471 scope.annotation = new Annotation(); 2472 scope.annotation.StartDate = moment(extent[0]).utc().format(timeFormat); 2473 scope.annotation.EndDate = moment(extent[1]).utc().format(timeFormat); 2474 scope.$apply(); // This logs a console type error, but also works .. odd. 2475 angular.element('#modalShower').trigger('click'); 2476 } 2477 var mfmt = 'YYYY/MM/DD-HH:mm:ss'; 2478 function datefmt(d) { 2479 return moment(d).utc().format(mfmt); 2480 } 2481 }, 2482 }; 2483 }]); 2484 bosunControllers.controller('ErrorCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) { 2485 $scope.loading = true; 2486 $http.get('/api/errors') 2487 .success(function (data) { 2488 $scope.errors = []; 2489 _(data).forEach(function (err, name) { 2490 err.Name = name; 2491 err.Sum = 0; 2492 err.Shown = true; 2493 _(err.Errors).forEach(function (line) { 2494 err.Sum += line.Count; 2495 line.FirstTime = moment.utc(line.FirstTime); 2496 line.LastTime = moment.utc(line.LastTime); 2497 }); 2498 $scope.errors.push(err); 2499 }); 2500 }) 2501 .error(function (data) { 2502 $scope.error = "Error fetching data: " + data; 2503 }) 2504 .finally(function () { $scope.loading = false; }); 2505 $scope.click = function (err, event) { 2506 event.stopPropagation(); 2507 }; 2508 $scope.totalLines = function () { 2509 return $scope.errors.length; 2510 }; 2511 $scope.selectedLines = function () { 2512 var t = 0; 2513 _($scope.errors).forEach(function (err) { 2514 if (err.checked) { 2515 t++; 2516 } 2517 }); 2518 return t; 2519 }; 2520 var getChecked = function () { 2521 var keys = []; 2522 _($scope.errors).forEach(function (err) { 2523 if (err.checked) { 2524 keys.push(err.Name); 2525 } 2526 }); 2527 return keys; 2528 }; 2529 var clear = function (keys) { 2530 $http.post('/api/errors', keys) 2531 .success(function (data) { 2532 $route.reload(); 2533 }) 2534 .error(function (data) { 2535 $scope.error = "Error Clearing Errors: " + data; 2536 }); 2537 }; 2538 $scope.clearAll = function () { 2539 clear(["all"]); 2540 }; 2541 $scope.clearSelected = function () { 2542 var keys = getChecked(); 2543 clear(keys); 2544 }; 2545 $scope.ruleLink = function (line, err) { 2546 var url = "/config?alert=" + err.Name; 2547 var fromDate = moment.utc(line.FirstTime); 2548 url += "&fromDate=" + fromDate.format("YYYY-MM-DD"); 2549 url += "&fromTime=" + fromDate.format("hh:mm"); 2550 var toDate = moment.utc(line.LastTime); 2551 url += "&toDate=" + toDate.format("YYYY-MM-DD"); 2552 url += "&toTime=" + toDate.format("hh:mm"); 2553 return url; 2554 }; 2555 }]); 2556 /// <reference path="0-bosun.ts" /> 2557 var TagSet = /** @class */ (function () { 2558 function TagSet() { 2559 } 2560 return TagSet; 2561 }()); 2562 var TagV = /** @class */ (function () { 2563 function TagV() { 2564 } 2565 return TagV; 2566 }()); 2567 var RateOptions = /** @class */ (function () { 2568 function RateOptions() { 2569 } 2570 return RateOptions; 2571 }()); 2572 var Filter = /** @class */ (function () { 2573 function Filter(f) { 2574 this.type = f && f.type || "auto"; 2575 this.tagk = f && f.tagk || ""; 2576 this.filter = f && f.filter || ""; 2577 this.groupBy = f && f.groupBy || false; 2578 } 2579 return Filter; 2580 }()); 2581 var FilterMap = /** @class */ (function () { 2582 function FilterMap() { 2583 } 2584 return FilterMap; 2585 }()); 2586 var Query = /** @class */ (function () { 2587 function Query(filterSupport, q) { 2588 this.aggregator = q && q.aggregator || 'sum'; 2589 this.metric = q && q.metric || ''; 2590 this.rate = q && q.rate || false; 2591 this.rateOptions = q && q.rateOptions || new RateOptions; 2592 if (q && !q.derivative) { 2593 // back compute derivative from q 2594 if (!this.rate) { 2595 this.derivative = 'gauge'; 2596 } 2597 else if (this.rateOptions.counter) { 2598 this.derivative = 'counter'; 2599 } 2600 else { 2601 this.derivative = 'rate'; 2602 } 2603 } 2604 else { 2605 this.derivative = q && q.derivative || 'auto'; 2606 } 2607 this.ds = q && q.ds || ''; 2608 this.dstime = q && q.dstime || ''; 2609 this.tags = q && q.tags || new TagSet; 2610 this.gbFilters = q && q.gbFilters || new FilterMap; 2611 this.nGbFilters = q && q.nGbFilters || new FilterMap; 2612 var that = this; 2613 // Copy tags with values to group by filters so old links work 2614 if (filterSupport) { 2615 _.each(this.tags, function (v, k) { 2616 if (v === "") { 2617 return; 2618 } 2619 var f = new (Filter); 2620 f.filter = v; 2621 f.groupBy = true; 2622 f.tagk = k; 2623 that.gbFilters[k] = f; 2624 }); 2625 // Load filters from raw query and turn them into gb and nGbFilters. 2626 // This makes links from other pages work (i.e. the expr page) 2627 if (_.has(q, 'filters')) { 2628 _.each(q.filters, function (filter) { 2629 if (filter.groupBy) { 2630 that.gbFilters[filter.tagk] = filter; 2631 return; 2632 } 2633 that.nGbFilters[filter.tagk] = filter; 2634 }); 2635 } 2636 } 2637 this.setFilters(); 2638 this.setDs(); 2639 this.setDerivative(); 2640 } 2641 Query.prototype.setFilters = function () { 2642 this.filters = []; 2643 var that = this; 2644 _.each(this.gbFilters, function (filter, tagk) { 2645 if (filter.filter && filter.type) { 2646 that.filters.push(filter); 2647 } 2648 }); 2649 _.each(this.nGbFilters, function (filter, tagk) { 2650 if (filter.filter && filter.type) { 2651 that.filters.push(filter); 2652 } 2653 }); 2654 }; 2655 Query.prototype.setDs = function () { 2656 if (this.dstime && this.ds) { 2657 this.downsample = this.dstime + '-' + this.ds; 2658 } 2659 else { 2660 this.downsample = ''; 2661 } 2662 }; 2663 Query.prototype.setDerivative = function () { 2664 var max = this.rateOptions.counterMax; 2665 this.rate = false; 2666 this.rateOptions = new RateOptions(); 2667 switch (this.derivative) { 2668 case "rate": 2669 this.rate = true; 2670 break; 2671 case "counter": 2672 this.rate = true; 2673 this.rateOptions.counter = true; 2674 this.rateOptions.counterMax = max; 2675 this.rateOptions.resetValue = 1; 2676 break; 2677 case "gauge": 2678 this.rate = false; 2679 break; 2680 } 2681 }; 2682 return Query; 2683 }()); 2684 var GraphRequest = /** @class */ (function () { 2685 function GraphRequest() { 2686 this.start = '1h-ago'; 2687 this.queries = []; 2688 } 2689 GraphRequest.prototype.prune = function () { 2690 var _this = this; 2691 for (var i = 0; i < this.queries.length; i++) { 2692 angular.forEach(this.queries[i], function (v, k) { 2693 var qi = _this.queries[i]; 2694 switch (typeof v) { 2695 case "string": 2696 if (!v) { 2697 delete qi[k]; 2698 } 2699 break; 2700 case "boolean": 2701 if (!v) { 2702 delete qi[k]; 2703 } 2704 break; 2705 case "object": 2706 if (Object.keys(v).length == 0) { 2707 delete qi[k]; 2708 } 2709 break; 2710 } 2711 }); 2712 } 2713 }; 2714 return GraphRequest; 2715 }()); 2716 var graphRefresh; 2717 var Version = /** @class */ (function () { 2718 function Version() { 2719 } 2720 return Version; 2721 }()); 2722 bosunControllers.controller('GraphCtrl', ['$scope', '$http', '$location', '$route', '$timeout', 'authService', function ($scope, $http, $location, $route, $timeout, auth) { 2723 $scope.aggregators = ["sum", "min", "max", "avg", "dev", "zimsum", "mimmin", "mimmax"]; 2724 $scope.dsaggregators = ["", "sum", "min", "max", "avg", "dev", "zimsum", "mimmin", "mimmax"]; 2725 $scope.filters = ["auto", "iliteral_or", "iwildcard", "literal_or", "not_iliteral_or", "not_literal_or", "regexp", "wildcard"]; 2726 if ($scope.version.Major >= 2 && $scope.version.Minor >= 2) { 2727 $scope.filterSupport = true; 2728 } 2729 $scope.rate_options = ["auto", "gauge", "counter", "rate"]; 2730 $scope.canAuto = {}; 2731 $scope.showAnnotations = (getShowAnnotations() == "true"); 2732 $scope.setShowAnnotations = function () { 2733 if ($scope.showAnnotations) { 2734 setShowAnnotations("true"); 2735 return; 2736 } 2737 setShowAnnotations("false"); 2738 }; 2739 var search = $location.search(); 2740 var j = search.json; 2741 if (search.b64) { 2742 j = atob(search.b64); 2743 } 2744 $scope.annotation = new Annotation(); 2745 var request = j ? JSON.parse(j) : new GraphRequest; 2746 $scope.index = parseInt($location.hash()) || 0; 2747 $scope.tagvs = []; 2748 $scope.sorted_tagks = []; 2749 $scope.query_p = []; 2750 angular.forEach(request.queries, function (q, i) { 2751 $scope.query_p[i] = new Query($scope.filterSupport, q); 2752 }); 2753 $scope.start = request.start; 2754 $scope.end = request.end; 2755 $scope.autods = search.autods != 'false'; 2756 $scope.refresh = search.refresh == 'true'; 2757 $scope.normalize = search.normalize == 'true'; 2758 if (search.min) { 2759 $scope.min = +search.min; 2760 } 2761 if (search.max) { 2762 $scope.max = +search.max; 2763 } 2764 var duration_map = { 2765 "s": "s", 2766 "m": "m", 2767 "h": "h", 2768 "d": "d", 2769 "w": "w", 2770 "n": "M", 2771 "y": "y", 2772 }; 2773 var isRel = /^(\d+)(\w)-ago$/; 2774 function RelToAbs(m) { 2775 return moment().utc().subtract(parseFloat(m[1]), duration_map[m[2]]).format(); 2776 } 2777 function AbsToRel(s) { 2778 //Not strict parsing of the time format. For example, just "2014" will be valid 2779 var t = moment.utc(s, moment.defaultFormat).fromNow(); 2780 return t; 2781 } 2782 function SwapTime(s) { 2783 if (!s) { 2784 return moment().utc().format(); 2785 } 2786 var m = isRel.exec(s); 2787 if (m) { 2788 return RelToAbs(m); 2789 } 2790 return AbsToRel(s); 2791 } 2792 $scope.submitAnnotation = function () { 2793 $scope.annotation.CreationUser = auth.GetUsername(); 2794 $http.post('/api/annotation', $scope.annotation) 2795 .success(function (data) { 2796 //debugger; 2797 if ($scope.annotation.Id == "" && $scope.annotation.Owner != "") { 2798 setOwner($scope.annotation.Owner); 2799 } 2800 $scope.annotation = new Annotation(data); 2801 $scope.error = ""; 2802 // This seems to make angular refresh, where a push doesn't 2803 $scope.annotations = $scope.annotations.concat($scope.annotation); 2804 }) 2805 .error(function (error) { 2806 $scope.error = error; 2807 }); 2808 }; 2809 $scope.deleteAnnotation = function () { return $http.delete('/api/annotation/' + $scope.annotation.Id) 2810 .success(function (data) { 2811 $scope.error = ""; 2812 $scope.annotations = _.without($scope.annotations, _.findWhere($scope.annotations, { Id: $scope.annotation.Id })); 2813 }) 2814 .error(function (error) { 2815 $scope.error = error; 2816 }); }; 2817 $scope.SwitchTimes = function () { 2818 $scope.start = SwapTime($scope.start); 2819 $scope.end = SwapTime($scope.end); 2820 }; 2821 $scope.AddTab = function () { 2822 $scope.index = $scope.query_p.length; 2823 $scope.query_p.push(new Query($scope.filterSupport)); 2824 }; 2825 $scope.setIndex = function (i) { 2826 $scope.index = i; 2827 }; 2828 var alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); 2829 if ($scope.annotateEnabled) { 2830 $http.get('/api/annotation/values/Owner') 2831 .success(function (data) { 2832 $scope.owners = data; 2833 }); 2834 $http.get('/api/annotation/values/Category') 2835 .success(function (data) { 2836 $scope.categories = data; 2837 }); 2838 $http.get('/api/annotation/values/Host') 2839 .success(function (data) { 2840 $scope.hosts = data; 2841 }); 2842 } 2843 $scope.GetTagKByMetric = function (index) { 2844 $scope.tagvs[index] = new TagV; 2845 var metric = $scope.query_p[index].metric; 2846 if (!metric) { 2847 $scope.canAuto[metric] = true; 2848 return; 2849 } 2850 $http.get('/api/tagk/' + encodeURIComponent(metric)) 2851 .success(function (data) { 2852 var q = $scope.query_p[index]; 2853 var tags = new TagSet; 2854 q.metric_tags = {}; 2855 if (!q.gbFilters) { 2856 q.gbFilters = new FilterMap; 2857 } 2858 if (!q.nGbFilters) { 2859 q.nGbFilters = new FilterMap; 2860 } 2861 for (var i = 0; i < data.length; i++) { 2862 var d = data[i]; 2863 if ($scope.filterSupport) { 2864 if (!q.gbFilters[d]) { 2865 var filter = new Filter; 2866 filter.tagk = d; 2867 filter.groupBy = true; 2868 q.gbFilters[d] = filter; 2869 } 2870 if (!q.nGbFilters[d]) { 2871 var filter = new Filter; 2872 filter.tagk = d; 2873 q.nGbFilters[d] = filter; 2874 } 2875 } 2876 if (q.tags) { 2877 tags[d] = q.tags[d]; 2878 } 2879 if (!tags[d]) { 2880 tags[d] = ''; 2881 } 2882 q.metric_tags[d] = true; 2883 GetTagVs(d, index); 2884 } 2885 angular.forEach(q.tags, function (val, key) { 2886 if (val) { 2887 tags[key] = val; 2888 } 2889 }); 2890 q.tags = tags; 2891 // Make sure host is always the first tag. 2892 $scope.sorted_tagks[index] = Object.keys(tags); 2893 $scope.sorted_tagks[index].sort(function (a, b) { 2894 if (a == 'host') { 2895 return -1; 2896 } 2897 else if (b == 'host') { 2898 return 1; 2899 } 2900 return a.localeCompare(b); 2901 }); 2902 }) 2903 .error(function (error) { 2904 $scope.error = 'Unable to fetch metrics: ' + error; 2905 }); 2906 $http.get('/api/metadata/metrics?metric=' + encodeURIComponent(metric)) 2907 .success(function (data) { 2908 var canAuto = data && data.Rate; 2909 $scope.canAuto[metric] = canAuto; 2910 }) 2911 .error(function (err) { 2912 $scope.error = err; 2913 }); 2914 }; 2915 if ($scope.query_p.length == 0) { 2916 $scope.AddTab(); 2917 } 2918 $http.get('/api/metric' + "?since=" + moment().utc().subtract(2, "days").unix()) 2919 .success(function (data) { 2920 $scope.metrics = data; 2921 }) 2922 .error(function (error) { 2923 $scope.error = 'Unable to fetch metrics: ' + error; 2924 }); 2925 function GetTagVs(k, index) { 2926 $http.get('/api/tagv/' + encodeURIComponent(k) + '/' + $scope.query_p[index].metric) 2927 .success(function (data) { 2928 data.sort(); 2929 $scope.tagvs[index][k] = data; 2930 }) 2931 .error(function (error) { 2932 $scope.error = 'Unable to fetch metrics: ' + error; 2933 }); 2934 } 2935 function getRequest() { 2936 request = new GraphRequest; 2937 request.start = $scope.start; 2938 request.end = $scope.end; 2939 angular.forEach($scope.query_p, function (p) { 2940 if (!p.metric) { 2941 return; 2942 } 2943 var q = new Query($scope.filterSupport, p); 2944 var tags = q.tags; 2945 q.tags = new TagSet; 2946 if (!$scope.filterSupport) { 2947 angular.forEach(tags, function (v, k) { 2948 if (v && k) { 2949 q.tags[k] = v; 2950 } 2951 }); 2952 } 2953 request.queries.push(q); 2954 }); 2955 return request; 2956 } 2957 $scope.keydown = function ($event) { 2958 if ($event.shiftKey && $event.keyCode == 13) { 2959 $scope.Query(); 2960 } 2961 }; 2962 $scope.Query = function () { 2963 var r = getRequest(); 2964 angular.forEach($scope.query_p, function (q, index) { 2965 var m = q.metric_tags; 2966 if (!m) { 2967 return; 2968 } 2969 if (!r.queries[index]) { 2970 return; 2971 } 2972 angular.forEach(q.tags, function (key, tag) { 2973 if (m[tag]) { 2974 return; 2975 } 2976 delete r.queries[index].tags[tag]; 2977 }); 2978 if ($scope.filterSupport) { 2979 _.each(r.queries[index].filters, function (f) { 2980 if (m[f.tagk]) { 2981 return; 2982 } 2983 delete r.queries[index].nGbFilters[f.tagk]; 2984 delete r.queries[index].gbFilters[f.tagk]; 2985 r.queries[index].filters = _.without(r.queries[index].filters, _.findWhere(r.queries[index].filters, { tagk: f.tagk })); 2986 }); 2987 } 2988 }); 2989 r.prune(); 2990 $location.search('b64', btoa(JSON.stringify(r))); 2991 $location.search('autods', $scope.autods ? undefined : 'false'); 2992 $location.search('refresh', $scope.refresh ? 'true' : undefined); 2993 $location.search('normalize', $scope.normalize ? 'true' : undefined); 2994 var min = angular.isNumber($scope.min) ? $scope.min.toString() : null; 2995 var max = angular.isNumber($scope.max) ? $scope.max.toString() : null; 2996 $location.search('min', min); 2997 $location.search('max', max); 2998 $route.reload(); 2999 }; 3000 request = getRequest(); 3001 if (!request.queries.length) { 3002 return; 3003 } 3004 var autods = $scope.autods ? '&autods=' + $('#chart').width() : ''; 3005 function getMetricMeta(metric) { 3006 $http.get('/api/metadata/metrics?metric=' + encodeURIComponent(metric)) 3007 .success(function (data) { 3008 $scope.meta[metric] = data; 3009 }) 3010 .error(function (error) { 3011 console.log("Error getting metadata for metric " + metric); 3012 }); 3013 } 3014 function get(noRunning) { 3015 $timeout.cancel(graphRefresh); 3016 if (!noRunning) { 3017 $scope.running = 'Running'; 3018 } 3019 var autorate = ''; 3020 $scope.meta = {}; 3021 for (var i = 0; i < request.queries.length; i++) { 3022 if (request.queries[i].derivative == 'auto') { 3023 autorate += '&autorate=' + i; 3024 } 3025 getMetricMeta(request.queries[i].metric); 3026 } 3027 _.each(request.queries, function (q, qIndex) { 3028 request.queries[qIndex].filters = _.map(q.filters, function (filter) { 3029 var f = new Filter(filter); 3030 if (f.filter && f.type) { 3031 if (f.type == "auto") { 3032 if (f.filter.indexOf("*") > -1) { 3033 f.type = f.filter == "*" ? f.type = "wildcard" : "iwildcard"; 3034 } 3035 else { 3036 f.type = "literal_or"; 3037 } 3038 } 3039 } 3040 return f; 3041 }); 3042 }); 3043 var min = angular.isNumber($scope.min) ? '&min=' + encodeURIComponent($scope.min.toString()) : ''; 3044 var max = angular.isNumber($scope.max) ? '&max=' + encodeURIComponent($scope.max.toString()) : ''; 3045 $scope.animate(); 3046 $scope.queryTime = ''; 3047 if (request.end && !isRel.exec(request.end)) { 3048 var t = moment.utc(request.end, moment.defaultFormat); 3049 $scope.queryTime = '&date=' + t.format('YYYY-MM-DD'); 3050 $scope.queryTime += '&time=' + t.format('HH:mm'); 3051 } 3052 $http.get('/api/graph?' + 'b64=' + encodeURIComponent(btoa(JSON.stringify(request))) + autods + autorate + min + max) 3053 .success(function (data) { 3054 $scope.result = data.Series; 3055 if ($scope.annotateEnabled) { 3056 $scope.annotations = _.sortBy(data.Annotations, function (d) { return d.StartDate; }); 3057 } 3058 $scope.warning = ''; 3059 if (!$scope.result) { 3060 $scope.warning = 'No Results'; 3061 } 3062 if (data.Warnings.length > 0) { 3063 $scope.warning += data.Warnings.join(" "); 3064 } 3065 $scope.queries = data.Queries; 3066 $scope.exprText = ""; 3067 _.each($scope.queries, function (q, i) { 3068 $scope.exprText += "$" + alphabet[i] + " = " + q + "\n"; 3069 if (i == $scope.queries.length - 1) { 3070 $scope.exprText += "avg($" + alphabet[i] + ")"; 3071 } 3072 }); 3073 $scope.running = ''; 3074 $scope.error = ''; 3075 var u = $location.absUrl(); 3076 u = u.substr(0, u.indexOf('?')) + '?'; 3077 u += 'b64=' + search.b64 + autods + autorate + min + max; 3078 $scope.url = u; 3079 }) 3080 .error(function (error) { 3081 $scope.error = error; 3082 $scope.running = ''; 3083 }) 3084 .finally(function () { 3085 $scope.stop(); 3086 if ($scope.refresh) { 3087 graphRefresh = $timeout(function () { get(true); }, 5000); 3088 } 3089 ; 3090 }); 3091 } 3092 ; 3093 get(false); 3094 }]); 3095 bosunApp.directive('tsPopup', function () { 3096 return { 3097 restrict: 'E', 3098 scope: { 3099 url: '=', 3100 }, 3101 template: '<button class="btn btn-default" data-html="true" data-placement="bottom">embed</button>', 3102 link: function (scope, elem, attrs) { 3103 var button = $('button', elem); 3104 scope.$watch(attrs.url, function (url) { 3105 if (!url) { 3106 return; 3107 } 3108 var text = '<input type="text" onClick="this.select();" readonly="readonly" value="<a href="' + url + '"><img src="' + url + '&.png=png"></a>">'; 3109 button.popover({ 3110 content: text, 3111 }); 3112 }); 3113 }, 3114 }; 3115 }); 3116 bosunApp.directive('tsAlertHistory', function () { 3117 return { 3118 templateUrl: '/partials/alerthistory.html', 3119 }; 3120 }); 3121 bosunControllers.controller('HistoryCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) { 3122 var search = $location.search(); 3123 var keys = {}; 3124 if (angular.isArray(search.key)) { 3125 angular.forEach(search.key, function (v) { 3126 keys[v] = true; 3127 }); 3128 } 3129 else { 3130 keys[search.key] = true; 3131 } 3132 var params = Object.keys(keys).map(function (v) { return 'ak=' + encodeURIComponent(v); }).join('&'); 3133 $http.get('/api/status?' + params + "&all=1") 3134 .success(function (data) { 3135 console.log(data); 3136 var selected_alerts = {}; 3137 angular.forEach(data, function (v, ak) { 3138 if (!keys[ak]) { 3139 return; 3140 } 3141 v.Events.map(function (h) { h.Time = moment.utc(h.Time); }); 3142 angular.forEach(v.Events, function (h, i) { 3143 if (i + 1 < v.Events.length) { 3144 h.EndTime = v.Events[i + 1].Time; 3145 } 3146 else { 3147 h.EndTime = moment.utc(); 3148 } 3149 }); 3150 selected_alerts[ak] = { 3151 History: v.Events.reverse(), 3152 }; 3153 }); 3154 if (Object.keys(selected_alerts).length > 0) { 3155 $scope.alert_history = selected_alerts; 3156 } 3157 else { 3158 $scope.error = 'No Matching Alerts Found'; 3159 } 3160 }) 3161 .error(function (err) { 3162 $scope.error = err; 3163 }); 3164 }]); 3165 bosunControllers.controller('HostCtrl', ['$scope', '$http', '$location', '$route', function ($scope, $http, $location, $route) { 3166 var search = $location.search(); 3167 $scope.host = search.host; 3168 $scope.time = search.time; 3169 $scope.tab = search.tab || "stats"; 3170 $scope.fsdata = []; 3171 $scope.metrics = []; 3172 var currentURL = $location.url(); 3173 $scope.mlink = function (m) { 3174 var r = new GraphRequest(); 3175 var q = new Query(false); 3176 q.metric = m; 3177 q.tags = { 'host': $scope.host }; 3178 r.queries.push(q); 3179 return r; 3180 }; 3181 $scope.setTab = function (t) { 3182 $location.search('tab', t); 3183 $scope.tab = t; 3184 }; 3185 $http.get('/api/metric/host/' + $scope.host) 3186 .success(function (data) { 3187 $scope.metrics = data || []; 3188 }); 3189 var start = moment().utc().subtract(parseDuration($scope.time)); 3190 function parseDuration(v) { 3191 var pattern = /(\d+)(d|y|n|h|m|s)-ago/; 3192 var m = pattern.exec(v); 3193 return moment.duration(parseInt(m[1]), m[2].replace('n', 'M')); 3194 } 3195 $http.get('/api/metadata/get?tagk=host&tagv=' + encodeURIComponent($scope.host)) 3196 .success(function (data) { 3197 $scope.metadata = _.filter(data, function (i) { 3198 return moment.utc(i.Time) > start; 3199 }); 3200 }); 3201 var autods = '&autods=100'; 3202 var cpu_r = new GraphRequest(); 3203 cpu_r.start = $scope.time; 3204 cpu_r.queries = [ 3205 new Query(false, { 3206 metric: 'os.cpu', 3207 derivative: 'counter', 3208 tags: { host: $scope.host }, 3209 }) 3210 ]; 3211 $http.get('/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(cpu_r)) + autods) 3212 .success(function (data) { 3213 if (!data.Series) { 3214 return; 3215 } 3216 data.Series[0].Name = 'Percent Used'; 3217 $scope.cpu = data.Series; 3218 }); 3219 var mem_r = new GraphRequest(); 3220 mem_r.start = $scope.time; 3221 mem_r.queries.push(new Query(false, { 3222 metric: "os.mem.total", 3223 tags: { host: $scope.host }, 3224 })); 3225 mem_r.queries.push(new Query(false, { 3226 metric: "os.mem.used", 3227 tags: { host: $scope.host }, 3228 })); 3229 $http.get('/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(mem_r)) + autods) 3230 .success(function (data) { 3231 if (!data.Series) { 3232 return; 3233 } 3234 data.Series[1].Name = "Used"; 3235 $scope.mem_total = Math.max.apply(null, data.Series[0].Data.map(function (d) { return d[1]; })); 3236 $scope.mem = [data.Series[1]]; 3237 }); 3238 var net_bytes_r = new GraphRequest(); 3239 net_bytes_r.start = $scope.time; 3240 net_bytes_r.queries = [ 3241 new Query(false, { 3242 metric: "os.net.bytes", 3243 rate: true, 3244 rateOptions: { counter: true, resetValue: 1 }, 3245 tags: { host: $scope.host, iface: "*", direction: "*" }, 3246 }) 3247 ]; 3248 $http.get('/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(net_bytes_r)) + autods) 3249 .success(function (data) { 3250 if (!data.Series) { 3251 return; 3252 } 3253 var tmp = []; 3254 var ifaceSeries = {}; 3255 angular.forEach(data.Series, function (series, idx) { 3256 series.Data = series.Data.map(function (dp) { return [dp[0], dp[1] * 8]; }); 3257 if (series.Tags.direction == "out") { 3258 series.Data = series.Data.map(function (dp) { return [dp[0], dp[1] * -1]; }); 3259 } 3260 if (!ifaceSeries.hasOwnProperty(series.Tags.iface)) { 3261 ifaceSeries[series.Tags.iface] = [series]; 3262 } 3263 else { 3264 ifaceSeries[series.Tags.iface].push(series); 3265 tmp.push(ifaceSeries[series.Tags.iface]); 3266 } 3267 }); 3268 $scope.idata = tmp; 3269 }); 3270 var fs_r = new GraphRequest(); 3271 fs_r.start = $scope.time; 3272 fs_r.queries = [ 3273 new Query(false, { 3274 metric: "os.disk.fs.space_total", 3275 tags: { host: $scope.host, disk: "*" }, 3276 }), 3277 new Query(false, { 3278 metric: "os.disk.fs.space_used", 3279 tags: { host: $scope.host, disk: "*" }, 3280 }) 3281 ]; 3282 $http.get('/api/graph?' + 'json=' + encodeURIComponent(JSON.stringify(fs_r)) + autods) 3283 .success(function (data) { 3284 if (!data.Series) { 3285 return; 3286 } 3287 var tmp = []; 3288 var fsSeries = {}; 3289 angular.forEach(data.Series, function (series, idx) { 3290 var stat = series.Data[series.Data.length - 1][1]; 3291 var prop = ""; 3292 if (series.Metric == "os.disk.fs.space_total") { 3293 prop = "total"; 3294 } 3295 else { 3296 prop = "used"; 3297 } 3298 if (!fsSeries.hasOwnProperty(series.Tags.disk)) { 3299 fsSeries[series.Tags.disk] = [series]; 3300 fsSeries[series.Tags.disk][prop] = stat; 3301 } 3302 else { 3303 fsSeries[series.Tags.disk].push(series); 3304 fsSeries[series.Tags.disk][prop] = stat; 3305 tmp.push(fsSeries[series.Tags.disk]); 3306 } 3307 }); 3308 $scope.fsdata = tmp; 3309 }); 3310 }]); 3311 bosunControllers.controller('IncidentCtrl', ['$scope', '$http', '$location', '$route', '$sce', 'linkService', function ($scope, $http, $location, $route, $sce, linkService) { 3312 var search = $location.search(); 3313 var id = search.id; 3314 if (!id) { 3315 $scope.error = "must supply incident id as query parameter"; 3316 return; 3317 } 3318 $http.get('/api/config') 3319 .success(function (data) { 3320 $scope.config_text = data; 3321 }); 3322 $scope.action = function (type) { 3323 var key = encodeURIComponent($scope.state.AlertKey); 3324 return '/action?type=' + type + '&key=' + key; 3325 }; 3326 $scope.getEditSilenceLink = function () { 3327 return linkService.GetEditSilenceLink($scope.silence, $scope.silenceId); 3328 }; 3329 $scope.loadTimelinePanel = function (v, i) { 3330 if (v.doneLoading && !v.error) { 3331 return; 3332 } 3333 v.error = null; 3334 v.doneLoading = false; 3335 if (i == $scope.lastNonUnknownAbnormalIdx && $scope.body) { 3336 v.subject = $scope.incident.Subject; 3337 v.body = $scope.body; 3338 v.doneLoading = true; 3339 return; 3340 } 3341 var ak = $scope.incident.AlertKey; 3342 var url = ruleUrl(ak, moment(v.Time)); 3343 $http.post(url, $scope.config_text) 3344 .success(function (data) { 3345 v.subject = data.Subject; 3346 v.body = $sce.trustAsHtml(data.Body); 3347 }) 3348 .error(function (error) { 3349 v.error = error; 3350 }) 3351 .finally(function () { 3352 v.doneLoading = true; 3353 }); 3354 }; 3355 $scope.shown = {}; 3356 $scope.collapse = function (i, v) { 3357 $scope.shown[i] = !$scope.shown[i]; 3358 if ($scope.loadTimelinePanel && $scope.shown[i]) { 3359 $scope.loadTimelinePanel(v, i); 3360 } 3361 }; 3362 $scope.time = function (v) { 3363 var m = moment(v).utc(); 3364 return m.format(); 3365 }; 3366 $http.get('/api/incidents/events?id=' + id) 3367 .success(function (data) { 3368 $scope.incident = data; 3369 $scope.state = $scope.incident; 3370 $scope.actions = data.Actions; 3371 $scope.body = $sce.trustAsHtml(data.Body); 3372 $scope.events = data.Events.reverse(); 3373 $scope.configLink = configUrl($scope.incident.AlertKey, moment.unix($scope.incident.LastAbnormalTime)); 3374 $scope.isActive = data.IsActive; 3375 $scope.silence = data.Silence; 3376 $scope.silenceId = data.SilenceId; 3377 $scope.editSilenceLink = linkService.GetEditSilenceLink($scope.silence, $scope.silenceId); 3378 for (var i = 0; i < $scope.events.length; i++) { 3379 var e = $scope.events[i]; 3380 if (e.Status != 'normal' && e.Status != 'unknown' && $scope.body) { 3381 $scope.lastNonUnknownAbnormalIdx = i; 3382 $scope.collapse(i, e); // Expand the panel of the current body 3383 break; 3384 } 3385 } 3386 $scope.collapse; 3387 }) 3388 .error(function (err) { 3389 $scope.error = err; 3390 }); 3391 }]); 3392 /// <reference path="0-bosun.ts" /> 3393 bosunControllers.controller('ItemsCtrl', ['$scope', '$http', function ($scope, $http) { 3394 $http.get('/api/metric') 3395 .success(function (data) { 3396 $scope.metrics = data; 3397 }) 3398 .error(function (error) { 3399 $scope.status = 'Unable to fetch metrics: ' + error; 3400 }); 3401 $http.get('/api/tagv/host?since=default') 3402 .success(function (data) { 3403 $scope.hosts = data; 3404 }) 3405 .error(function (error) { 3406 $scope.status = 'Unable to fetch hosts: ' + error; 3407 }); 3408 }]); 3409 /// <reference path="0-bosun.ts" /> 3410 var LinkService = /** @class */ (function () { 3411 function LinkService() { 3412 } 3413 LinkService.prototype.GetEditSilenceLink = function (silence, silenceId) { 3414 if (!(silence && silenceId)) { 3415 return ""; 3416 } 3417 var forget = silence.Forget ? '&forget' : ''; 3418 return "/silence?start=" + this.time(silence.Start) + 3419 "&end=" + this.time(silence.End) + 3420 "&alert=" + silence.Alert + 3421 "&tags=" + encodeURIComponent(silence.TagString) + 3422 forget + 3423 "&edit=" + silenceId; 3424 }; 3425 LinkService.prototype.time = function (v) { 3426 var m = moment(v).utc(); 3427 return m.format(); 3428 }; 3429 return LinkService; 3430 }()); 3431 bosunApp.service("linkService", LinkService); 3432 var Tag = /** @class */ (function () { 3433 function Tag() { 3434 } 3435 return Tag; 3436 }()); 3437 var DP = /** @class */ (function () { 3438 function DP() { 3439 } 3440 return DP; 3441 }()); 3442 bosunControllers.controller('PutCtrl', ['$scope', '$http', '$route', function ($scope, $http, $route) { 3443 $scope.tags = [new Tag]; 3444 var dp = new DP; 3445 dp.k = moment().utc().format(); 3446 $scope.dps = [dp]; 3447 $http.get('/api/metric') 3448 .success(function (data) { 3449 $scope.metrics = data; 3450 }) 3451 .error(function (error) { 3452 $scope.error = 'Unable to fetch metrics: ' + error; 3453 }); 3454 $scope.Submit = function () { 3455 var data = []; 3456 var tags = {}; 3457 angular.forEach($scope.tags, function (v, k) { 3458 if (v.k || v.v) { 3459 tags[v.k] = v.v; 3460 } 3461 }); 3462 angular.forEach($scope.dps, function (v, k) { 3463 if (v.k && v.v) { 3464 var ts = parseInt(moment.utc(v.k, tsdbDateFormat).format('X')); 3465 data.push({ 3466 metric: $scope.metric, 3467 timestamp: ts, 3468 value: parseFloat(v.v), 3469 tags: tags, 3470 }); 3471 } 3472 }); 3473 $scope.running = 'submitting data...'; 3474 $scope.success = ''; 3475 $scope.error = ''; 3476 $http.post('/api/put', data) 3477 .success(function () { 3478 $scope.running = ''; 3479 $scope.success = 'Data Submitted'; 3480 }) 3481 .error(function (error) { 3482 $scope.running = ''; 3483 $scope.error = error.error.message; 3484 }); 3485 }; 3486 $scope.AddTag = function () { 3487 var last = $scope.tags[$scope.tags.length - 1]; 3488 if (last.k && last.v) { 3489 $scope.tags.push(new Tag); 3490 } 3491 }; 3492 $scope.AddDP = function () { 3493 var last = $scope.dps[$scope.dps.length - 1]; 3494 if (last.k && last.v) { 3495 var dp = new DP; 3496 dp.k = moment.utc(last.k, tsdbDateFormat).add(15, 'seconds').format(); 3497 $scope.dps.push(dp); 3498 } 3499 }; 3500 $scope.GetTagKByMetric = function () { 3501 $http.get('/api/tagk/' + $scope.metric) 3502 .success(function (data) { 3503 if (!angular.isArray(data)) { 3504 return; 3505 } 3506 $scope.tags = [new Tag]; 3507 for (var i = 0; i < data.length; i++) { 3508 var t = new Tag; 3509 t.k = data[i]; 3510 $scope.tags.push(t); 3511 } 3512 }) 3513 .error(function (error) { 3514 $scope.error = 'Unable to fetch metrics: ' + error; 3515 }); 3516 }; 3517 }]); 3518 /// <reference path="0-bosun.ts" /> 3519 bosunControllers.controller('SilenceCtrl', ['$scope', '$http', '$location', '$route', 'linkService', function ($scope, $http, $location, $route, linkService) { 3520 var search = $location.search(); 3521 $scope.start = search.start; 3522 $scope.end = search.end; 3523 $scope.duration = search.duration; 3524 $scope.alert = search.alert; 3525 $scope.hosts = search.hosts; 3526 $scope.tags = search.tags; 3527 $scope.edit = search.edit; 3528 $scope.forget = search.forget; 3529 $scope.message = search.message; 3530 if (!$scope.end && !$scope.duration) { 3531 $scope.duration = '1h'; 3532 } 3533 function filter(data, startBefore, startAfter, endAfter, endBefore, limit) { 3534 var ret = {}; 3535 var count = 0; 3536 _.each(data, function (v, name) { 3537 if (limit && count >= limit) { 3538 return; 3539 } 3540 var s = moment(v.Start).utc(); 3541 var e = moment(v.End).utc(); 3542 if (startBefore && s > startBefore) { 3543 return; 3544 } 3545 if (startAfter && s < startAfter) { 3546 return; 3547 } 3548 if (endAfter && e < endAfter) { 3549 return; 3550 } 3551 if (endBefore && e > endBefore) { 3552 return; 3553 } 3554 ret[name] = v; 3555 }); 3556 return ret; 3557 } 3558 function get() { 3559 $http.get('/api/silence/get') 3560 .success(function (data) { 3561 $scope.silences = []; 3562 var now = moment.utc(); 3563 $scope.silences.push({ 3564 name: 'Active', 3565 silences: filter(data, now, null, now, null, 0) 3566 }); 3567 $scope.silences.push({ 3568 name: 'Upcoming', 3569 silences: filter(data, null, now, null, null, 0) 3570 }); 3571 $scope.silences.push({ 3572 name: 'Past', 3573 silences: filter(data, null, null, null, now, 25) 3574 }); 3575 }) 3576 .error(function (error) { 3577 $scope.error = error; 3578 }); 3579 } 3580 get(); 3581 function getData() { 3582 var tags = ($scope.tags || '').split(','); 3583 if ($scope.hosts) { 3584 tags.push('host=' + $scope.hosts.split(/[ ,|]+/).join('|')); 3585 } 3586 tags = tags.filter(function (v) { return v != ""; }); 3587 var data = { 3588 start: $scope.start, 3589 end: $scope.end, 3590 duration: $scope.duration, 3591 alert: $scope.alert, 3592 tags: tags.join(','), 3593 edit: $scope.edit, 3594 forget: $scope.forget ? 'true' : null, 3595 message: $scope.message, 3596 }; 3597 return data; 3598 } 3599 var any = search.start || search.end || search.duration || search.alert || search.hosts || search.tags || search.forget; 3600 var state = getData(); 3601 $scope.change = function () { 3602 $scope.disableConfirm = true; 3603 }; 3604 if (any) { 3605 $scope.error = null; 3606 $http.post('/api/silence/set', state) 3607 .success(function (data) { 3608 if (!data) { 3609 data = { '(none)': false }; 3610 } 3611 $scope.testSilences = data; 3612 }) 3613 .error(function (error) { 3614 $scope.error = error; 3615 }); 3616 } 3617 $scope.test = function () { 3618 $location.search('start', $scope.start || null); 3619 $location.search('end', $scope.end || null); 3620 $location.search('duration', $scope.duration || null); 3621 $location.search('alert', $scope.alert || null); 3622 $location.search('hosts', $scope.hosts || null); 3623 $location.search('tags', $scope.tags || null); 3624 $location.search('forget', $scope.forget || null); 3625 $location.search('message', $scope.message || null); 3626 $route.reload(); 3627 }; 3628 $scope.confirm = function () { 3629 $scope.error = null; 3630 $scope.testSilences = null; 3631 $scope.edit = null; 3632 $location.search('edit', null); 3633 state.confirm = 'true'; 3634 $http.post('/api/silence/set', state) 3635 .error(function (error) { 3636 $scope.error = error; 3637 }) 3638 .finally(get); 3639 }; 3640 $scope.clear = function (id) { 3641 if (!window.confirm('Clear this silence?')) { 3642 return; 3643 } 3644 $scope.error = null; 3645 $http.post('/api/silence/clear?id=' + id, {}) 3646 .error(function (error) { 3647 $scope.error = error; 3648 }) 3649 .finally(get); 3650 }; 3651 $scope.time = function (v) { 3652 var m = moment(v).utc(); 3653 return m.format(); 3654 }; 3655 $scope.getEditSilenceLink = function (silence, silenceId) { 3656 return linkService.GetEditSilenceLink(silence, silenceId); 3657 }; 3658 }]); 3659 bosunApp.directive('tsAckGroup', ['$location', '$timeout', function ($location, $timeout) { 3660 return { 3661 scope: { 3662 ack: '=', 3663 groups: '=tsAckGroup', 3664 schedule: '=', 3665 timeanddate: '=', 3666 }, 3667 templateUrl: '/partials/ackgroup.html', 3668 link: function (scope, elem, attrs) { 3669 scope.canAckSelected = scope.ack == 'Needs Acknowledgement'; 3670 scope.panelClass = scope.$parent.panelClass; 3671 scope.btoa = scope.$parent.btoa; 3672 scope.encode = scope.$parent.encode; 3673 scope.shown = {}; 3674 scope.collapse = function (i) { 3675 scope.shown[i] = !scope.shown[i]; 3676 if (scope.shown[i] && scope.groups[i].Children.length == 1) { 3677 $timeout(function () { 3678 scope.$broadcast("onOpen", i); 3679 }, 0); 3680 } 3681 }; 3682 scope.click = function ($event, idx) { 3683 scope.collapse(idx); 3684 if ($event.shiftKey && scope.schedule.checkIdx != undefined) { 3685 var checked = scope.groups[scope.schedule.checkIdx].checked; 3686 var start = Math.min(idx, scope.schedule.checkIdx); 3687 var end = Math.max(idx, scope.schedule.checkIdx); 3688 for (var i = start; i <= end; i++) { 3689 if (i == idx) { 3690 continue; 3691 } 3692 scope.groups[i].checked = checked; 3693 } 3694 } 3695 scope.schedule.checkIdx = idx; 3696 scope.update(); 3697 }; 3698 scope.select = function (checked) { 3699 for (var i = 0; i < scope.groups.length; i++) { 3700 scope.groups[i].checked = checked; 3701 } 3702 scope.update(); 3703 }; 3704 scope.update = function () { 3705 scope.canCloseSelected = true; 3706 scope.canForgetSelected = true; 3707 scope.anySelected = false; 3708 for (var i = 0; i < scope.groups.length; i++) { 3709 var g = scope.groups[i]; 3710 if (!g.checked) { 3711 continue; 3712 } 3713 scope.anySelected = true; 3714 if (g.Status == 'error') { 3715 scope.canCloseSelected = false; 3716 } 3717 if (g.Status != 'unknown') { 3718 scope.canForgetSelected = false; 3719 } 3720 } 3721 }; 3722 scope.multiaction = function (type) { 3723 var keys = []; 3724 var active = false; 3725 angular.forEach(scope.groups, function (group) { 3726 if (!group.checked) { 3727 return; 3728 } 3729 if (group.AlertKey) { 3730 if (group.State.CurrentStatus != 'normal') { 3731 active = true; 3732 } 3733 keys.push(group.AlertKey); 3734 } 3735 angular.forEach(group.Children, function (child) { 3736 if (child.State.CurrentStatus != 'normal') { 3737 active = true; 3738 } 3739 keys.push(child.AlertKey); 3740 }); 3741 }); 3742 scope.$parent.setKey("action-keys", keys); 3743 $location.path("action"); 3744 $location.search("type", type); 3745 $location.search("active", active ? 'true' : 'false'); 3746 }; 3747 scope.history = function () { 3748 var url = '/history?'; 3749 angular.forEach(scope.groups, function (group) { 3750 if (!group.checked) { 3751 return; 3752 } 3753 if (group.AlertKey) { 3754 url += '&key=' + encodeURIComponent(group.AlertKey); 3755 } 3756 angular.forEach(group.Children, function (child) { 3757 url += '&key=' + encodeURIComponent(child.AlertKey); 3758 }); 3759 }); 3760 return url; 3761 }; 3762 }, 3763 }; 3764 }]); 3765 bosunApp.directive('tsState', ['$sce', '$http', function ($sce, $http) { 3766 return { 3767 templateUrl: '/partials/alertstate.html', 3768 link: function (scope, elem, attrs) { 3769 var myIdx = attrs["tsGrp"]; 3770 scope.currentStatus = attrs["tsGrpstatus"]; 3771 scope.name = scope.child.AlertKey; 3772 scope.state = scope.child.State; 3773 scope.action = function (type) { 3774 var key = encodeURIComponent(scope.name); 3775 var active = scope.state.CurrentStatus != 'normal'; 3776 return '/action?type=' + type + '&key=' + key + '&active=' + active; 3777 }; 3778 var loadedBody = false; 3779 scope.toggle = function () { 3780 scope.show = !scope.show; 3781 if (scope.show && !loadedBody) { 3782 scope.state.Body = "loading..."; 3783 loadedBody = true; 3784 $http.get('/api/status?ak=' + scope.child.AlertKey) 3785 .success(function (data) { 3786 var body = data[scope.child.AlertKey].Body; 3787 scope.state.Body = $sce.trustAsHtml(body); 3788 }) 3789 .error(function (err) { 3790 scope.state.Body = "Error loading template body: " + err; 3791 }); 3792 } 3793 }; 3794 scope.$on('onOpen', function (e, i) { 3795 if (i == myIdx) { 3796 scope.toggle(); 3797 } 3798 }); 3799 scope.zws = function (v) { 3800 if (!v) { 3801 return ''; 3802 } 3803 return v.replace(/([,{}()])/g, '$1\u200b'); 3804 }; 3805 scope.state.Touched = moment(scope.state.Touched).utc(); 3806 angular.forEach(scope.state.Events, function (v, k) { 3807 v.Time = moment(v.Time).utc(); 3808 }); 3809 scope.state.last = scope.state.Events[scope.state.Events.length - 1]; 3810 if (scope.state.Actions && scope.state.Actions.length > 0) { 3811 scope.state.LastAction = scope.state.Actions[scope.state.Actions.length - 1]; 3812 } 3813 scope.state.RuleUrl = '/config?' + 3814 'alert=' + encodeURIComponent(scope.state.Alert) + 3815 '&fromDate=' + encodeURIComponent(scope.state.last.Time.format("YYYY-MM-DD")) + 3816 '&fromTime=' + encodeURIComponent(scope.state.last.Time.format("HH:mm")); 3817 var groups = []; 3818 angular.forEach(scope.state.Group, function (v, k) { 3819 groups.push(k + "=" + v); 3820 }); 3821 if (groups.length > 0) { 3822 scope.state.RuleUrl += '&template_group=' + encodeURIComponent(groups.join(',')); 3823 } 3824 scope.state.Body = $sce.trustAsHtml(scope.state.Body); 3825 }, 3826 }; 3827 }]); 3828 bosunApp.directive('tsNote', function () { 3829 return { 3830 restrict: 'E', 3831 templateUrl: '/partials/note.html', 3832 }; 3833 }); 3834 bosunApp.directive('tsAck', function () { 3835 return { 3836 restrict: 'E', 3837 templateUrl: '/partials/ack.html', 3838 }; 3839 }); 3840 bosunApp.directive('tsClose', function () { 3841 return { 3842 restrict: 'E', 3843 templateUrl: '/partials/close.html', 3844 }; 3845 }); 3846 bosunApp.directive('tsCancelClose', function () { 3847 return { 3848 restrict: 'E', 3849 templateUrl: '/partials/cancelClose.html', 3850 }; 3851 }); 3852 bosunApp.directive('tsForget', function () { 3853 return { 3854 restrict: 'E', 3855 templateUrl: '/partials/forget.html', 3856 }; 3857 }); 3858 bosunApp.directive('tsPurge', function () { 3859 return { 3860 restrict: 'E', 3861 templateUrl: '/partials/purge.html', 3862 }; 3863 }); 3864 bosunApp.directive('tsForceClose', function () { 3865 return { 3866 restrict: 'E', 3867 templateUrl: '/partials/forceClose.html', 3868 }; 3869 }); 3870 /// <reference path="0-bosun.ts" /> 3871 var TokenListController = /** @class */ (function () { 3872 function TokenListController($http, auth) { 3873 var _this = this; 3874 this.$http = $http; 3875 this.auth = auth; 3876 this.delete = function () { 3877 _this.status = "Deleting..."; 3878 _this.$http.delete("/api/tokens?hash=" + encodeURIComponent(_this.deleteTarget)) 3879 .then(function () { 3880 _this.status = ""; 3881 _this.load(); 3882 }, function (err) { 3883 _this.status = 'Unable to delete token: ' + err; 3884 }); 3885 }; 3886 this.load = function () { 3887 _this.status = "Loading..."; 3888 _this.$http.get("/api/tokens").then(function (resp) { 3889 _(resp.data).forEach(function (tok) { 3890 tok.LastUsed = moment.utc(tok.LastUsed); 3891 tok.Permissions = _this.auth.PermissionsFor(tok.Role); 3892 tok.RoleName = _this.auth.RoleFor(tok.Role) || ("" + tok.Permissions.length + " Permissions"); 3893 }); 3894 _this.tokens = resp.data; 3895 _this.status = ""; 3896 }, function (err) { 3897 _this.status = 'Unable to fetch tokens: ' + err; 3898 }); 3899 }; 3900 this.permList = function (tok) { 3901 //HACK: return html string for popover. angular-strap has bad api for this 3902 var h = "<div class=\"popover\" tabindex=\"-1\">\n <div class=\"arrow\"></div>\n <div class=\"popover-content\"><ul>"; 3903 var perms = _this.auth.PermissionsFor(tok.Role); 3904 for (var i = 0; i < perms.length; i++) { 3905 var p = perms[i]; 3906 var open = "<strong>"; 3907 var close = "</strong>"; 3908 h += "<li>" + open + p + close + "</li>"; 3909 } 3910 h += "</ul></div></div>"; 3911 return h; 3912 }; 3913 this.load(); 3914 } 3915 TokenListController.$inject = ['$http', "authService"]; 3916 return TokenListController; 3917 }()); 3918 bosunApp.component('tokenList', { 3919 controller: TokenListController, 3920 controllerAs: "ct", 3921 templateUrl: '/static/partials/tokenList.html' 3922 }); 3923 /// <reference path="0-bosun.ts" /> 3924 var NewTokenController = /** @class */ (function () { 3925 function NewTokenController($http, auth) { 3926 var _this = this; 3927 this.$http = $http; 3928 this.auth = auth; 3929 this.token = new Token(); 3930 this.hasBits = function (bits) { 3931 return (bits & _this.token.Role) != 0; 3932 }; 3933 this.setRole = function (bits, event) { 3934 _(_this.permissions).each(function (perm) { 3935 if (!event.currentTarget.checked) { 3936 perm.Active = false; 3937 } 3938 else { 3939 perm.Active = (perm.Bits & bits) != 0; 3940 } 3941 }); 3942 }; 3943 this.getBits = function () { 3944 return _(_this.permissions).reduce(function (sum, p) { return sum + (p.Active ? p.Bits : 0); }, 0); 3945 }; 3946 var defs = auth.GetRoles(); 3947 this.permissions = defs.Permissions; 3948 this.roles = defs.Roles; 3949 } 3950 NewTokenController.prototype.create = function () { 3951 var _this = this; 3952 this.token.Role = this.getBits(); 3953 this.status = "Creating..."; 3954 this.$http.post("/api/tokens", this.token).then(function (resp) { 3955 _this.status = ""; 3956 _this.createdToken = resp.data.replace(/"/g, ""); 3957 }, function (err) { _this.status = 'Unable to load roles: ' + err; }); 3958 }; 3959 NewTokenController.prototype.encoded = function () { 3960 return encodeURIComponent(this.createdToken); 3961 }; 3962 NewTokenController.$inject = ['$http', 'authService']; 3963 return NewTokenController; 3964 }()); 3965 bosunApp.component("newToken", { 3966 controller: NewTokenController, 3967 controllerAs: "ct", 3968 templateUrl: "/partials/tokenNew.html" 3969 });