puppet-herald 0.2.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +3 -0
  3. data/.jshintrc +19 -0
  4. data/Gemfile +25 -12
  5. data/Gemfile.local.example +3 -0
  6. data/Guardfile +57 -0
  7. data/README.md +1 -1
  8. data/Rakefile +100 -20
  9. data/db/migrate/20141218200108_remove_no_of_reports_from_nodes.rb +7 -0
  10. data/lib/puppet-herald.rb +95 -65
  11. data/lib/puppet-herald/app/api.rb +79 -12
  12. data/lib/puppet-herald/app/configuration.rb +47 -15
  13. data/lib/puppet-herald/app/frontend.rb +6 -6
  14. data/lib/puppet-herald/app/views/app.erb +14 -20
  15. data/lib/puppet-herald/app/views/err500.erb +8 -5
  16. data/lib/puppet-herald/application.rb +9 -1
  17. data/lib/puppet-herald/cli.rb +5 -11
  18. data/lib/puppet-herald/database.rb +1 -7
  19. data/lib/puppet-herald/javascript.rb +16 -10
  20. data/lib/puppet-herald/models.rb +47 -0
  21. data/lib/puppet-herald/models/log-entry.rb +2 -0
  22. data/lib/puppet-herald/models/node.rb +49 -2
  23. data/lib/puppet-herald/models/report.rb +33 -9
  24. data/lib/puppet-herald/project.js +46 -0
  25. data/lib/puppet-herald/public/app.js +11 -9
  26. data/lib/puppet-herald/public/bower.json +10 -3
  27. data/lib/puppet-herald/public/components/artifact/artifact-directive.js +4 -0
  28. data/lib/puppet-herald/public/components/artifact/artifact.js +1 -3
  29. data/lib/puppet-herald/public/components/directives/directives.js +5 -1
  30. data/lib/puppet-herald/public/components/directives/status-button.html +1 -1
  31. data/lib/puppet-herald/public/components/directives/status-button.js +11 -11
  32. data/lib/puppet-herald/public/components/filters/filters.js +6 -2
  33. data/lib/puppet-herald/public/components/page.js +2 -2
  34. data/lib/puppet-herald/public/components/pagination.js +142 -0
  35. data/lib/puppet-herald/public/components/settings.js +25 -0
  36. data/lib/puppet-herald/public/css/herald.css +100 -3
  37. data/lib/puppet-herald/public/general/app.html +73 -0
  38. data/lib/puppet-herald/public/img/shield97-white.svg +53 -0
  39. data/lib/puppet-herald/public/img/shield97.png +0 -0
  40. data/lib/puppet-herald/public/node/node.html +27 -9
  41. data/lib/puppet-herald/public/node/node.js +43 -15
  42. data/lib/puppet-herald/public/nodes/nodes.html +25 -7
  43. data/lib/puppet-herald/public/nodes/nodes.js +29 -14
  44. data/lib/puppet-herald/public/report/report.html +60 -13
  45. data/lib/puppet-herald/public/report/report.js +21 -14
  46. data/lib/puppet-herald/public/router.js +55 -0
  47. data/lib/puppet-herald/purgecronjob.rb +35 -0
  48. data/lib/puppet-herald/version.rb +2 -2
  49. data/package.json +14 -16
  50. data/puppet-herald.gemspec +12 -7
  51. data/spec/integration/app/configuration_spec.rb +33 -0
  52. data/spec/integration/application_spec.rb +139 -20
  53. data/spec/integration/fixtures/nodes.yml +13 -0
  54. data/spec/integration/fixtures/pending-notify.yaml +346 -0
  55. data/spec/integration/fixtures/reports.yml +61 -0
  56. data/spec/integration/models/node_spec.rb +12 -3
  57. data/spec/integration/models/report_spec.rb +60 -4
  58. data/spec/spec_helper.rb +6 -10
  59. data/spec/support/active_record.rb +1 -0
  60. data/spec/support/fixtures.rb +16 -0
  61. data/spec/unit/puppet-herald/cli_spec.rb +4 -4
  62. data/spec/unit/puppet-herald/database_spec.rb +5 -3
  63. data/spec/unit/puppet-herald/purgecronjob_spec.rb +37 -0
  64. data/test/javascript/.bowerrc +3 -0
  65. data/test/javascript/bower.json +21 -0
  66. data/test/javascript/karma.conf.js +17 -22
  67. data/test/javascript/src/app_test.js +10 -61
  68. data/test/javascript/src/components/directives/status-button_test.js +10 -10
  69. data/test/javascript/src/components/paginate_test.js +183 -0
  70. data/test/javascript/src/node/node_test.js +16 -6
  71. data/test/javascript/src/nodes/nodes_test.js +14 -2
  72. data/test/javascript/src/report/report_test.js +6 -6
  73. data/test/javascript/src/router_test.js +79 -0
  74. metadata +642 -23
@@ -1,7 +1,13 @@
1
- <div class="panel panel-primary" ng-controller="NodeController as ctrl">
1
+ <div ui-view>
2
+
3
+ <div class="panel panel-default">
2
4
  <!-- Default panel contents -->
3
- <div class="panel-heading">Reports for
4
- <samp>{{ ctrl.node.name }}</samp> <span class="badge">{{ ctrl.node.reports.length }}</span>
5
+ <div class="panel-heading">All reports for:
6
+ <samp>{{ ctrl.node.name }}</samp> <span class="badge" ng-pluralize
7
+ count="ctrl.pagination.elements()"
8
+ when="{'0': 'no reports',
9
+ '1': '1 report',
10
+ 'other': '{} reports'}"></span>
5
11
  </div>
6
12
 
7
13
  <!-- Table -->
@@ -16,17 +22,29 @@
16
22
  </tr>
17
23
  </thead>
18
24
  <tbody>
19
- <tr ng-repeat="report in ctrl.node.reports | orderBy:'-time'">
25
+ <tr
26
+ dir-paginate="report in ctrl.node.reports | itemsPerPage: ctrl.pagination.limit() | orderBy: '-time'"
27
+ total-items="ctrl.pagination.elements()">
20
28
  <td>{{ $index + 1 }}</td>
21
- <td>{{ report.configuration_version }}</td>
22
- <td><ng-status-button
29
+ <td><samp>{{ report.configuration_version }}</samp></td>
30
+ <td><ws-status-button
23
31
  status="report.status"
24
32
  id="report.id"
25
- route="'/report/:id'"
26
- ></ng-status-button></td>
33
+ idname="'reportId'"
34
+ route="'report'"
35
+ ></ws-status-button></td>
27
36
  <td>{{ report.environment }}</td>
28
- <td am-time-ago="report.time"></td>
37
+ <td title="{{ report.time }}">{{ report.time | amCalendar }}</td>
29
38
  </tr>
30
39
  </tbody>
31
40
  </table>
41
+ </div>
42
+
43
+ <nav class="text-center">
44
+ <dir-pagination-controls
45
+ on-page-change="ctrl.onPageChange(newPageNumber)"
46
+ boundary-links="true"
47
+ ></dir-pagination-controls>
48
+ </nav>
49
+
32
50
  </div>
@@ -1,27 +1,55 @@
1
+ (function() {
2
+
1
3
  'use strict';
2
4
 
3
5
  angular.module('herald.node', [
4
- 'ngRoute',
6
+ 'ui.router',
5
7
  'herald.page',
6
8
  'herald.directives',
9
+ 'herald.pagination',
7
10
  'angularMoment'
8
11
  ])
9
12
 
10
- .config(['$routeProvider', function($routeProvider) {
11
- $routeProvider.when('/node/:nodeId', {
12
- templateUrl: 'node/node.html',
13
- controller: 'NodeController'
14
- });
15
- }])
13
+ .controller('NodeController',
14
+ ['$http', '$stateParams', 'Page', 'PaginationFactory',
15
+ function($http, $stateParams, Page, PaginationFactory) {
16
16
 
17
- .controller('NodeController', ['$http', '$routeParams', 'Page', function($http, $routeParams, Page) {
18
17
  var ctrl = this;
18
+ Page.title('Reports');
19
+ ctrl.pagination = PaginationFactory.DEFAULT;
20
+ ctrl.cache = PaginationFactory.createPageCache(60); // 60 seconds cache
19
21
  ctrl.node = null;
20
- Page.title('Node');
21
- this.nodeId = $routeParams.nodeId;
22
+ ctrl.nav = {
23
+ node: null,
24
+ report: null
25
+ };
26
+ this.nodeId = $stateParams.nodeId;
27
+ var gateway = '/api/v1/nodes/' + this.nodeId;
28
+
29
+ function setNode(nodeData) {
30
+ ctrl.node = nodeData;
31
+ ctrl.nav.node = ctrl.node.name;
32
+ }
22
33
 
23
- $http.get('/api/v1/nodes/' + this.nodeId).success(function(data) {
24
- ctrl.node = data;
25
- Page.title('Node', data.name);
26
- });
27
- }]);
34
+ function getResultsPage(pageNumber) {
35
+ ctrl.pagination.page(pageNumber);
36
+ if (ctrl.cache.isLoaded(pageNumber)) {
37
+ setNode(ctrl.cache.get(pageNumber));
38
+ return;
39
+ }
40
+ var config = { headers: ctrl.pagination.toHeaders() };
41
+ $http.get(gateway, config).success(function(data, status, headers, config) {
42
+ setNode(data);
43
+ ctrl.pagination = PaginationFactory.fromHeaders(headers);
44
+ var loadedPage = ctrl.pagination.page();
45
+ Page.title('Reports for', data.name);
46
+ ctrl.cache.set(loadedPage, data);
47
+ });
48
+ }
49
+ getResultsPage(ctrl.pagination.page());
50
+ ctrl.onPageChange = function(newPage) {
51
+ getResultsPage(newPage);
52
+ };
53
+ }]);
54
+
55
+ })();
@@ -1,6 +1,11 @@
1
- <div class="panel panel-primary" ng-controller="NodesController as nodes">
1
+ <div ui-view>
2
+ <div class="panel panel-default">
2
3
  <!-- Default panel contents -->
3
- <div class="panel-heading">Nodes <span class="badge">{{ nodes.all.length }}</span></div>
4
+ <div class="panel-heading">All Puppet nodes <span class="badge" ng-pluralize
5
+ count="ctrl.pagination.elements()"
6
+ when="{'0': 'none',
7
+ '1': '1 node',
8
+ 'other': '{} nodes'}"></span></div>
4
9
 
5
10
  <!-- Table -->
6
11
  <table class="table table-striped table-hover">
@@ -14,16 +19,29 @@
14
19
  </tr>
15
20
  </thead>
16
21
  <tbody>
17
- <tr ng-repeat="node in nodes.all | orderBy:'last_run'">
22
+ <tr
23
+ dir-paginate="node in ctrl.all | itemsPerPage: ctrl.pagination.limit() | orderBy:'-last_run'"
24
+ total-items="ctrl.pagination.elements()"
25
+ >
18
26
  <td>{{ $index + 1 }}</td>
19
- <td>{{ node.name }}</td>
20
- <td><ng-status-button
27
+ <td><samp>{{ node.name }}</samp></td>
28
+ <td><ws-status-button
21
29
  status="node.status"
22
30
  id="node.id"
23
- route="'/node/:id'"></ng-status-button></td>
31
+ idname="'nodeId'"
32
+ route="'node'"></ws-status-button></td>
24
33
  <td>{{ node.no_of_reports }}</td>
25
- <td am-time-ago="node.last_run"></td>
34
+ <td title="{{ node.last_run }}">{{ node.last_run | amCalendar }}</td>
26
35
  </tr>
27
36
  </tbody>
28
37
  </table>
38
+ </div>
39
+
40
+ <nav class="text-center">
41
+ <dir-pagination-controls
42
+ on-page-change="ctrl.onPageChange(newPageNumber)"
43
+ boundary-links="true"
44
+ ></dir-pagination-controls>
45
+ </nav>
46
+
29
47
  </div>
@@ -1,25 +1,40 @@
1
+ (function() {
2
+
1
3
  'use strict';
2
4
 
3
5
  angular.module('herald.nodes', [
4
- 'ngRoute',
5
6
  'herald.directives',
6
7
  'herald.page',
8
+ 'herald.pagination',
7
9
  'angularMoment'
8
10
  ])
9
11
 
10
- .config(['$routeProvider', function($routeProvider) {
11
- $routeProvider.when('/nodes', {
12
- templateUrl: 'nodes/nodes.html',
13
- controller: 'NodesController'
14
- });
15
- }])
16
-
17
- .controller('NodesController', ['$http', 'Page', function($http, Page) {
18
- Page.title('All nodes');
12
+ .controller('NodesController', ['$http', 'Page', 'PaginationFactory', function($http, Page, PaginationFactory) {
13
+ Page.title('All Puppet nodes');
19
14
  var ctrl = this;
15
+ ctrl.title =
16
+ ctrl.pagination = PaginationFactory.DEFAULT;
17
+ ctrl.cache = PaginationFactory.createPageCache(60); // 60 seconds cache
20
18
  ctrl.all = [];
21
19
 
22
- $http.get('/api/v1/nodes').success(function(data) {
23
- ctrl.all = data;
24
- });
25
- }]);
20
+ function getResultsPage(pageNumber) {
21
+ ctrl.pagination.page(pageNumber);
22
+ if (ctrl.cache.isLoaded(pageNumber)) {
23
+ ctrl.all = ctrl.cache.get(pageNumber);
24
+ return;
25
+ }
26
+ var config = { headers: ctrl.pagination.toHeaders() };
27
+ $http.get('/api/v1/nodes', config).success(function(data, status, headers, config) {
28
+ ctrl.all = data;
29
+ ctrl.pagination = PaginationFactory.fromHeaders(headers);
30
+ var loadedPage = ctrl.pagination.page();
31
+ ctrl.cache.set(loadedPage, data);
32
+ });
33
+ }
34
+ getResultsPage(ctrl.pagination.page());
35
+ ctrl.onPageChange = function(newPage) {
36
+ getResultsPage(newPage);
37
+ };
38
+ }]);
39
+
40
+ })();
@@ -1,25 +1,72 @@
1
- <div class="panel panel-primary" ng-controller="ReportController as ctrl">
1
+ <div class="panel panel-default">
2
2
  <!-- Default panel contents -->
3
- <div class="panel-heading">Report:
4
- <samp>{{ ctrl.report.configuration_version }}</samp>
5
- <span class="badge">{{ ctrl.report.log_entries.length }}</span>
3
+ <div class="panel-heading">
4
+
5
+ <span class="">
6
+ Report:
7
+ <samp>{{ ctrl.report.configuration_version }}</samp>
8
+ <span class="badge" ng-pluralize
9
+ count="ctrl.report.log_entries.length"
10
+ when="{'0': 'no output',
11
+ '1': '1 line',
12
+ 'other': '{} lines'}"></span>
13
+ </span>
14
+
15
+ <div class="btn-group">&nbsp;&nbsp;&nbsp;&nbsp;</div>
16
+ Show more information:
17
+ <div class="btn-group">
18
+ <button
19
+ type="button"
20
+ class="btn btn-default btn-sm dropdown-toggle"
21
+ ng-init="dropdown = false"
22
+ ng-click="dropdown = !dropdown">
23
+ {{ settings.report.info ? 'Yes' : 'No' }} <span class="caret"></span>
24
+ </button>
25
+ <ul
26
+ class="dropdown-menu"
27
+ role="menu"
28
+ ng-click="event.preverntDefault();"
29
+ ng-class="{'ws-show-block': dropdown}">
30
+ <li><a href="" ng-click="settings.report.info = true; dropdown = !dropdown">Yes</a></li>
31
+ <li><a href="" ng-click="settings.report.info = false; dropdown = !dropdown">No</a></li>
32
+ </ul>
33
+ </div>
6
34
  </div>
7
35
 
8
- <!-- Table -->
9
- <table class="table table-condensed table-hover herald-table-report">
10
- <thead>
36
+ <table class="table table-hover" ng-show="settings.report.info">
37
+ <tbody>
38
+ <tr>
39
+ <th>Host</th>
40
+ <td><samp>{{ ctrl.report.host }}</samp></td>
41
+ <th>Status</th>
42
+ <td><span class="label label-{{ ctrl.report.status | colorizeStatus }}">{{ ctrl.report.status | uppercase }}</span></td>
43
+ </tr>
11
44
  <tr>
12
- <th>#</th>
13
- <th>Line</th>
45
+ <th>Date of report</th>
46
+ <td title="{{ ctrl.report.time }}">{{ ctrl.report.time | amCalendar }}</td>
47
+ <th>Configuration version</th>
48
+ <td><samp>{{ ctrl.report.configuration_version }}</samp></td>
14
49
  </tr>
15
- </thead>
50
+ <tr>
51
+ <th>Environment</th>
52
+ <td>{{ ctrl.report.environment }}</td>
53
+ <th>Puppet version</th>
54
+ <td><samp>{{ ctrl.report.puppet_version }}</samp></td>
55
+ </tr>
56
+ </tbody>
57
+ </table>
58
+
59
+ <!-- Table -->
60
+ <table
61
+ class="table table-condensed table-hover herald-table-report"
62
+ ng-class="{puppet3: settings.colorSyntax.selected.value == 'puppet3'}">
16
63
  <tbody>
17
64
  <tr>
18
- <td></td>
19
- <td># puppet {{ ctrl.report.kind }} [...]</td>
65
+ <td class="unselectable"></td>
66
+ <td class="unselectable"># puppet {{ ctrl.report.kind }} [...]</td>
20
67
  </tr>
21
68
  <tr ng-repeat="entry in ctrl.report.log_entries | orderBy:'time'">
22
- <td>{{ $index + 1 }}</td>
69
+ <td class="unselectable">{{ $index + 1 }}</td>
23
70
  <td class="herald-log-{{ entry.level }}">{{ entry.level | capitalize }}: {{ entry.source }}: {{ entry.message }}</td>
24
71
  </tr>
25
72
  </tbody>
@@ -1,26 +1,33 @@
1
+ (function() {
2
+
1
3
  'use strict';
2
4
 
3
5
  angular.module('herald.report', [
4
- 'ngRoute',
6
+ 'ui.router',
5
7
  'herald.page',
6
8
  'herald.directives'
7
9
  ])
8
10
 
9
- .config(['$routeProvider', function($routeProvider) {
10
- $routeProvider.when('/report/:reportId', {
11
- templateUrl: 'report/report.html',
12
- controller: 'ReportController'
13
- });
14
- }])
15
-
16
- .controller('ReportController', ['$http', '$routeParams', 'Page', function($http, $routeParams, Page) {
17
- Page.title('Report');
11
+ .controller('ReportController', ['$http', '$stateParams', 'Page', function($http, $stateParams, Page) {
18
12
  var ctrl = this;
13
+ Page.title('Puppet report');
19
14
  ctrl.report = null;
20
- this.reportId = $routeParams.reportId;
15
+ ctrl.nav = {
16
+ node: null,
17
+ report: null
18
+ };
19
+ this.reportId = $stateParams.reportId;
20
+
21
+ function setReport(reportData) {
22
+ ctrl.report = reportData;
23
+ ctrl.nav.node = ctrl.report.host;
24
+ ctrl.nav.report = ctrl.report.configuration_version;
25
+ }
21
26
 
22
27
  $http.get('/api/v1/reports/' + this.reportId).success(function(data) {
23
- ctrl.report = data;
24
- Page.title('Report', data.configuration_version);
28
+ setReport(data);
29
+ Page.title('Puppet report', data.configuration_version);
25
30
  });
26
- }]);
31
+ }]);
32
+
33
+ })();
@@ -0,0 +1,55 @@
1
+ (function(){
2
+ 'use strict';
3
+
4
+ var router = angular.module('herald.router', [
5
+ 'ui.router',
6
+ 'herald.nodes',
7
+ 'herald.node',
8
+ 'herald.report',
9
+ 'ncy-angular-breadcrumb'
10
+ ]);
11
+
12
+ router.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
13
+ $urlRouterProvider.otherwise('/');
14
+ $stateProvider
15
+
16
+ .state('home', {
17
+ url: '/',
18
+ abstract: true,
19
+ templateUrl: 'general/app.html'
20
+ })
21
+
22
+ .state('nodes', {
23
+ url: '',
24
+ templateUrl: 'nodes/nodes.html',
25
+ controller: 'NodesController as ctrl',
26
+ parent: 'home',
27
+ ncyBreadcrumb: {
28
+ label: ' '
29
+ }
30
+ })
31
+
32
+ .state('node', {
33
+ url: 'node-{nodeId:int}',
34
+ templateUrl: 'node/node.html',
35
+ controller: 'NodeController as ctrl',
36
+ parent: 'nodes',
37
+ ncyBreadcrumb: {
38
+ label: "All reports for: {{ ctrl.nav.node }}"
39
+ }
40
+ })
41
+
42
+ .state('report', {
43
+ parent: 'node',
44
+ url: '/report-{reportId:int}',
45
+ templateUrl: 'report/report.html',
46
+ controller: 'ReportController as ctrl',
47
+ ncyBreadcrumb: {
48
+ label: 'Report: {{ ctrl.nav.report }}'
49
+ }
50
+ })
51
+
52
+ ;
53
+ }]);
54
+
55
+ })();
@@ -0,0 +1,35 @@
1
+ # A module for Herald
2
+ module PuppetHerald
3
+ # A cron job for Herald
4
+ class PurgeCronJob
5
+ # Number of seconds in a day
6
+ #
7
+ # @return [Integer]
8
+ SECONDS_IN_DAY = 86_400
9
+
10
+ # Run a purge job
11
+ #
12
+ # @return [nil]
13
+ def run!
14
+ require 'puppet-herald'
15
+ require 'puppet-herald/models/report'
16
+ limit = ENV['PUPPET_HERALD_PURGE_LIMIT'] || '90d'
17
+ date = parse_limit limit
18
+ PuppetHerald.logger.info "Running a purge reports job with limit: `#{limit}` that is `#{date}`..."
19
+ reports = PuppetHerald::Models::Report.purge_older_then(date)
20
+ PuppetHerald.logger.info "Job completed. Purged: #{reports} reports."
21
+ nil
22
+ end
23
+
24
+ # Parse a limit and returns number of seconds
25
+ #
26
+ # @param limit [String] a limit as string
27
+ # @return [DateTime] a date in the past - now minus limit
28
+ def parse_limit(limit)
29
+ require 'rufus/scheduler'
30
+ seconds = Rufus::Scheduler.parse limit
31
+ now = DateTime.now
32
+ now - Rational(seconds, SECONDS_IN_DAY)
33
+ end
34
+ end
35
+ end