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="&lt;a href=&quot;' + url + '&quot;&gt;&lt;img src=&quot;' + url + '&.png=png&quot;&gt;&lt;/a&gt;">';
  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  });