puppet-herald 0.2.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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