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