diff --git a/.travis.yml b/.travis.yml index f7340a9f..b51c5e4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,6 @@ before_script: - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" - sleep 5 # give xvfb some time to start - - "npm install karma-cli karma-ng-scenario karma-jasmine karma-phantomjs-launcher --quiet --global" + - "npm install --quiet" script: - "mvn clean verify -Pkarma" diff --git a/README.md b/README.md index 9dd8a6ec..7884bf05 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,10 @@ docker-compose up and open [http://localhost:8080/seyren](http://localhost:8080/seyren). Graphite is available on [http://localhost:8081/](http://localhost:8081/) with Basic Auth guest/guest. +If you prefer to run only Seyren's dependencies within Docker, for example so that you can run Seyren from an IDE, use: + + docker-compose -f docker-compose.yml up + ## Init script [Seyren-Init](https://github.com/wingZero21/Seyren-Init) is an init.d script which allows you to start the service. @@ -180,13 +184,13 @@ To run the acceptance tests with Maven: ``` mvn clean verify ``` -To run the integration tests with Maven: +To run the integration tests with Maven, install the necessary Node modules (Karma, etc.), then run tests with the +"karma" profile: -``` -mvn clean verify -Pkarma -``` + npm install + mvn clean verify -Pkarma -To fire-up the app using Maven and wait (meaning you can run the tests separately from your IDE): +To fire up the app using Maven and wait (meaning you can run the tests separately from your IDE): ``` mvn clean verify -Dwait diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..bb617552 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,18 @@ +web: + build: seyren-web + links: + - mongodb + - graphite + command: /bin/bash -c "mvn clean install -am -pl seyren-web -Dmaven.test.skip && /usr/share/maven/bin/mvn tomcat7:run-war -pl seyren-web" + environment: + MONGO_URL: mongodb://mongodb:27017/seyren + GRAPHITE_URL: http://graphite + GRAPHITE_USERNAME: guest + GRAPHITE_PASSWORD: guest + SEYREN_LOG_PATH: /root + volumes: + - ./:/data + - ~/.m2:/root/.m2 + ports: + - 8080:8080 + - 8000:8000 diff --git a/docker-compose.yml b/docker-compose.yml index c5b50489..f75a5e62 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,22 +19,3 @@ collectd: GRAPHITE_HOST: graphite GRAPHITE_PORT: 2003 INSTANCE_ID: seyren - -web: - build: seyren-web - links: - - mongodb - - graphite - command: /bin/bash -c "/usr/share/maven/bin/mvn clean install -am -pl seyren-web -Dmaven.test.skip && /usr/share/maven/bin/mvn tomcat7:run-war -pl seyren-web" - environment: - MONGO_URL: mongodb://mongodb:27017/seyren - GRAPHITE_URL: http://graphite - GRAPHITE_USERNAME: guest - GRAPHITE_PASSWORD: guest - SEYREN_LOG_PATH: /root - volumes: - - ./:/data - - ~/.m2:/root/.m2 - ports: - - 8080:8080 - - 8000:8000 diff --git a/package.json b/package.json new file mode 100644 index 00000000..c16d45d1 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "seyren", + "version": "1.0.0", + "private": true, + "dependencies": {}, + "devDependencies": { + "jasmine-core": "^2.5.2", + "karma": "^1.5.0", + "karma-jasmine": "^1.1.0", + "karma-ng-scenario": "^1.0.0", + "karma-phantomjs-launcher": "^1.0.4" + }, + "scripts": {}, + "license": "Apache-2.0" +} diff --git a/pom.xml b/pom.xml index a41a4b13..692a1aad 100755 --- a/pom.xml +++ b/pom.xml @@ -91,7 +91,7 @@ com.github.joelittlejohn.embedmongo embedmongo-maven-plugin - 0.1.12 + 0.3.4 com.kelveden diff --git a/seyren-integration-tests/pom.xml b/seyren-integration-tests/pom.xml index 1edd1040..37c6b2ac 100755 --- a/seyren-integration-tests/pom.xml +++ b/seyren-integration-tests/pom.xml @@ -194,6 +194,7 @@ ${basedir}/src/test/resources/karma-e2e.conf.js + ${basedir}/../node_modules/.bin/karma diff --git a/seyren-integration-tests/src/test/e2e/scenarios.js b/seyren-integration-tests/src/test/e2e/scenarios.js index 0d1f66f3..5f92657d 100644 --- a/seyren-integration-tests/src/test/e2e/scenarios.js +++ b/seyren-integration-tests/src/test/e2e/scenarios.js @@ -18,15 +18,15 @@ describe('home page', function () { expect(element('table:eq(0) thead tr').count()).toBe(1); expect(element('table:eq(0) thead tr th:eq(0)').text()).toBe('Name'); expect(element('table:eq(0) thead tr th:eq(1)').text()).toBe('State'); - expect(element('table:eq(0) thead tr th:eq(2)').text()).toBe('Warn'); - expect(element('table:eq(0) thead tr th:eq(3)').text()).toBe('Error'); + expect(element('table:eq(0) thead tr th:eq(2)').text()).toBe('Warn level'); + expect(element('table:eq(0) thead tr th:eq(3)').text()).toBe('Error level'); expect(element('table:eq(0) thead tr th:eq(4)').text()).toBe('Enabled'); expect(element('table:eq(0) tbody tr').count()).toBe(1); expect(element('table:eq(0) tbody tr td:eq(0) a').text()).toBe('load longterm usage'); expect(element('table:eq(0) tbody tr td:eq(1) span:visible').text()).toBe('WARN'); expect(element('table:eq(0) tbody tr td:eq(2)').text()).toBe('0.5'); - expect(element('table:eq(0) tbody tr td:eq(3)').text()).toBe('2.0'); + expect(element('table:eq(0) tbody tr td:eq(3)').text()).toBe('1,000'); expect(element('table:eq(0) tbody tr td:eq(4) input:checked').val()).toBe('on'); }); @@ -40,8 +40,8 @@ describe('home page', function () { expect(element('table:eq(1) thead tr th:eq(1)').text()).toBe('Time ago'); expect(element('table:eq(1) thead tr th:eq(2)').text()).toBe('Name'); expect(element('table:eq(1) thead tr th:eq(3)').text()).toBe('Value'); - expect(element('table:eq(1) thead tr th:eq(4)').text()).toBe('Warn'); - expect(element('table:eq(1) thead tr th:eq(5)').text()).toBe('Error'); + expect(element('table:eq(1) thead tr th:eq(4)').text()).toBe('Warn level'); + expect(element('table:eq(1) thead tr th:eq(5)').text()).toBe('Error level'); expect(element('table:eq(1) thead tr th:eq(6)').text()).toBe('From'); expect(element('table:eq(1) thead tr th:eq(7)').text()).toBe('To'); @@ -51,7 +51,7 @@ describe('home page', function () { expect(element('table:eq(1) tbody tr td:eq(2) a').text()).toBe('load longterm usage'); expect(element('table:eq(1) tbody tr td:eq(3)').text()).toBe('0.8'); expect(element('table:eq(1) tbody tr td:eq(4)').text()).toBe('0.5'); - expect(element('table:eq(1) tbody tr td:eq(5)').text()).toBe('2'); + expect(element('table:eq(1) tbody tr td:eq(5)').text()).toBe('1,000'); expect(element('table:eq(1) tbody tr td:eq(6) span:visible').text()).toBe('WARN'); expect(element('table:eq(1) tbody tr td:eq(7) span:visible').text()).toBe('OK'); }); @@ -94,15 +94,15 @@ describe('checks page', function () { expect(element('table thead tr').count()).toBe(1); expect(element('table thead tr th:eq(0)').text()).toBe('Name'); expect(element('table thead tr th:eq(1)').text()).toBe('State'); - expect(element('table thead tr th:eq(2)').text()).toBe('Warn'); - expect(element('table thead tr th:eq(3)').text()).toBe('Error'); + expect(element('table thead tr th:eq(2)').text()).toBe('Warn level'); + expect(element('table thead tr th:eq(3)').text()).toBe('Error level'); expect(element('table thead tr th:eq(4)').text()).toBe('Enabled'); expect(element('table tbody tr').count()).toBe(1); expect(element('table tbody tr td:eq(0) a').text()).toBe('load longterm usage'); expect(element('table tbody tr td:eq(1) span:visible').text()).toBe('WARN'); expect(element('table tbody tr td:eq(2)').text()).toBe('0.5'); - expect(element('table tbody tr td:eq(3)').text()).toBe('2.0'); + expect(element('table tbody tr td:eq(3)').text()).toBe('1,000'); expect(element('table tbody tr td:eq(4) input:checked').val()).toBe('on'); }); @@ -125,7 +125,7 @@ describe('check page', function () { it('should have a \'Subscriptions\' section', function () { expect(element('h2:eq(1)').count()).toBe(1); expect(element('h2:eq(1)').text()).toBe('Subscriptions'); - expect(element('div.col-lg-12:eq(2) div').text()).toBe('This check has no subscriptions'); + expect(element('#subscriptionsContent div').text()).toBe('This check has no subscriptions'); }); it('should have a \'Details\' informations', function () { @@ -149,11 +149,11 @@ describe('check page', function () { expect(element('div.col-lg-6 div.detail-form:eq(5) label').text()).toBe('Until:'); expect(element('div.col-lg-6 div.detail-form:eq(5) p').text()).toBe(''); - expect(element('div.col-lg-6 div.detail-form:eq(6) label').text()).toBe('Warn:'); + expect(element('div.col-lg-6 div.detail-form:eq(6) label').text()).toBe('Warn level:'); expect(element('div.col-lg-6 div.detail-form:eq(6) p').text()).toBe('0.5'); - expect(element('div.col-lg-6 div.detail-form:eq(7) label').text()).toBe('Error:'); - expect(element('div.col-lg-6 div.detail-form:eq(7) p').text()).toBe('2.0'); + expect(element('div.col-lg-6 div.detail-form:eq(7) label').text()).toBe('Error level:'); + expect(element('div.col-lg-6 div.detail-form:eq(7) p').text()).toBe('1,000'); expect(element('div.col-lg-6 div.detail-form:eq(8) label').text()).toBe('Enabled:'); expect(element('div.col-lg-6 div.detail-form:eq(8) p input:checked').val()).toBe('on'); @@ -207,7 +207,7 @@ describe('check page', function () { expect(element('table:eq(1) tbody tr td:eq(2)').text()).toBe('prod.host1.load.longterm'); expect(element('table:eq(1) tbody tr td:eq(3)').text()).toBe('0.8'); expect(element('table:eq(1) tbody tr td:eq(4)').text()).toBe('0.5'); - expect(element('table:eq(1) tbody tr td:eq(5)').text()).toBe('2'); + expect(element('table:eq(1) tbody tr td:eq(5)').text()).toBe('1,000'); expect(element('table:eq(1) tbody tr td:eq(6) span:visible').text()).toBe('WARN'); expect(element('table:eq(1) tbody tr td:eq(7) span:visible').text()).toBe('OK'); }); @@ -376,10 +376,10 @@ describe('create new check', function () { input('check\\.description').enter('Description of karma.metric'); input('check\\.target').enter('karma.metric'); input('check\\.warn').enter('2.0'); - input('check\\.error').enter('4.0'); + input('check\\.error').enter('1000'); input('check\\.enabled').check(); - expect(element('div#editCheckModal img').attr('src')).toBe('./api/chart/karma.metric/?&width=365&height=70&from=-1day&warn=2.0&error=4.0&hideLegend=true'); + expect(element('div#editCheckModal img').attr('src')).toBe('./api/chart/karma.metric/?&width=365&height=70&from=-1day&warn=2.0&error=1000&hideLegend=true'); expect(element('button#createCheckButton:visible:enabled').count()).toEqual(1); expect(element('button#cancelCheckButton:visible:enabled').count()).toEqual(1); diff --git a/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/alerts/alerts_1.json b/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/alerts/alerts_1.json index d23d969f..a583e010 100644 --- a/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/alerts/alerts_1.json +++ b/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/alerts/alerts_1.json @@ -3,7 +3,7 @@ "timestamp": { "$date": "2013-07-12T14:15:59.497Z"}, "warn" : "0.5", "checkId" : "5205121fccf2a07eacba64da", - "error" : "2.0", + "error" : "1000", "value" : "0.8", "target" : "prod.host1.load.longterm", "toType" : "OK", diff --git a/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/checks/checks_1.json b/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/checks/checks_1.json index 31d4ac79..71173319 100644 --- a/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/checks/checks_1.json +++ b/seyren-integration-tests/src/test/resources/com/seyren/integrationtests/mongo/checks/checks_1.json @@ -4,7 +4,7 @@ description: "Load longterm of host host1", target: "prod.host1.load.longterm", warn: "0.5", - error: "2.0", + error: "1000", enabled: true, state: "WARN", "lastCheck": { "$date": "2013-07-12T10:10:00.000Z"}, diff --git a/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java b/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java index 34adbdae..e615e0b0 100644 --- a/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java +++ b/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java @@ -104,8 +104,9 @@ private void removeOldIndices() { try { getAlertsCollection().dropIndex(new BasicDBObject("checkId", 1).append("target", 1)); } catch (MongoCommandException e) { - if (e.getCode() != 27) { - // 27 is the code which appears when the index doesn't exist (which we're happy with, anything else is bad news) + if (e.getCode() != 27 && e.getCode() != -1) { + // -1 is the code which appears when the index doesn't exist (which we're happy with, anything else is + // bad news) throw e; } } diff --git a/seyren-web/src/main/webapp/css/override-bootstrap.css b/seyren-web/src/main/webapp/css/override-bootstrap.css index 8399ea3f..d829ae0d 100644 --- a/seyren-web/src/main/webapp/css/override-bootstrap.css +++ b/seyren-web/src/main/webapp/css/override-bootstrap.css @@ -53,3 +53,7 @@ body { padding-right: 10px; padding-left: 10px; } + +.glyphicon-blue { + color: #0000bf; +} \ No newline at end of file diff --git a/seyren-web/src/main/webapp/html/check.html b/seyren-web/src/main/webapp/html/check.html index bc1f9232..38d61814 100644 --- a/seyren-web/src/main/webapp/html/check.html +++ b/seyren-web/src/main/webapp/html/check.html @@ -1,4 +1,10 @@ -
+
+
+

Not Found

+

Check was not found. Maybe it has been deleted?

+
+
+
@@ -10,10 +16,10 @@
{{ graph.description }}
-
+
-
+

diff --git a/seyren-web/src/main/webapp/html/home.html b/seyren-web/src/main/webapp/html/home.html index 31858ee3..217f14bd 100755 --- a/seyren-web/src/main/webapp/html/home.html +++ b/seyren-web/src/main/webapp/html/home.html @@ -6,8 +6,8 @@

Checks in an unhealthy state

Name State - Warn - Error + Warn level + Error level Enabled @@ -23,8 +23,8 @@

Checks in an unhealthy state

{{ check.state }} {{ check.state }} - {{ check.warn }} - {{ check.error }} + {{ check.warn | number }} + {{ check.error | number }} @@ -41,22 +41,22 @@

Recent alerts

Time ago Name Value - Warn - Error + Warn level + Error level From To - {{ alert.timestamp | date: 'yyyy-MM-dd HH:mm:ss' }} + {{ alert.timestamp | date: 'yyyy-MM-dd HH:mm:ssZ' }} {{ checkNames[alert.checkId] }} - {{ alert.value }} - {{ alert.warn }} - {{ alert.error }} + {{ alert.value | number }} + {{ alert.warn | number }} + {{ alert.error | number }} {{ alert.fromType }} {{ alert.fromType }} diff --git a/seyren-web/src/main/webapp/html/modal-partial-check.html b/seyren-web/src/main/webapp/html/modal-partial-check.html index 55366a8d..bdb5e461 100644 --- a/seyren-web/src/main/webapp/html/modal-partial-check.html +++ b/seyren-web/src/main/webapp/html/modal-partial-check.html @@ -10,46 +10,50 @@
-
+
-
- +
+
- -
-
- -
+ +
-
+
- -
+ +
-
- -
-
+
@@ -78,7 +82,7 @@
-
+
Metrics found: {{ check.totalMetric }}
diff --git a/seyren-web/src/main/webapp/index.html b/seyren-web/src/main/webapp/index.html index 263160d5..9aa9c4af 100755 --- a/seyren-web/src/main/webapp/index.html +++ b/seyren-web/src/main/webapp/index.html @@ -36,7 +36,7 @@
-
+

Seyren, an alerting dashboard for Graphite

diff --git a/seyren-web/src/main/webapp/js/check-controller.js b/seyren-web/src/main/webapp/js/check-controller.js index 79e7deef..bfc05f6d 100755 --- a/seyren-web/src/main/webapp/js/check-controller.js +++ b/seyren-web/src/main/webapp/js/check-controller.js @@ -10,6 +10,9 @@ $scope.alertStartIndex = 0; $scope.alertItemsPerPage = 10; + // Assume that it exists, for now. + $scope.checkExists = true; + configResults.$promise.then(function(data) { $scope.config = data; @@ -46,8 +49,12 @@ $scope.check = data; $scope.check.descriptionHtml = $sce.trustAsHtml(linkify.normal($scope.check.description)); $scope.check.lastLoadTime = new Date().getTime(); - }, function (err) { - console.log('Loading check failed'); + $scope.checkExists = true; + }, function (httpResponse) { + console.log('Loading check failed. Status: ' + httpResponse.status); + if (httpResponse.status === 404) { + $scope.checkExists = false; + } }); }; diff --git a/seyren-web/src/main/webapp/js/check-edit-controller.js b/seyren-web/src/main/webapp/js/check-edit-controller.js index 9b64224b..fa6091cf 100644 --- a/seyren-web/src/main/webapp/js/check-edit-controller.js +++ b/seyren-web/src/main/webapp/js/check-edit-controller.js @@ -18,10 +18,23 @@ $('#editCheckModal').on('shown.bs.modal', function () { $('#check\\.name').focus(); + $('#check\\.target\\.hint').tooltip({ + placement: 'right', + title: 'The target parameter specifies a path identifying one or several metrics, optionally with functions acting on those metrics.' + }); $('#check\\.warn\\.hint').tooltip({ placement: 'right', title: 'Setting your warn level higher than your error level will result in Seyren generating alerts when the target value goes below the threshold.' }); + $('#check\\.from\\.hint').tooltip({ + placement: 'right', + html: true, + title: '"From" and "To" are optional parameters that specify the relative or absolute time period to' + + ' retrieve from the server. See ' + + 'the Graphite' + + ' documentation. Only the most recent value of each series returned will be used.', + trigger: 'click' + }); }); $scope.create = function () { diff --git a/seyren-web/src/main/webapp/js/services.js b/seyren-web/src/main/webapp/js/services.js index f541c467..1ff7c186 100644 --- a/seyren-web/src/main/webapp/js/services.js +++ b/seyren-web/src/main/webapp/js/services.js @@ -2,10 +2,15 @@ (function () { 'use strict'; + var outstandingRequests = 0; + angular.module('seyrenApp.services', ['ngResource']). config(function ($httpProvider) { + outstandingRequests = 0; + $httpProvider.responseInterceptors.push('spinnerHttpInterceptor'); var spinnerFunction = function (data, headersGetter) { + outstandingRequests += 1; $('#spinnerG').show(); $('#banner').hide(); return data; @@ -15,12 +20,18 @@ factory('spinnerHttpInterceptor', function ($q, $window) { return function (promise) { return promise.then(function (response) { - $('#spinnerG').hide(); + outstandingRequests -= 1; + if (outstandingRequests <= 0) { + $('#spinnerG').hide(); + } $('#banner').hide(); return response; }, function (response) { - $('#spinnerG').hide(); + outstandingRequests -= 1; + if (outstandingRequests <= 0) { + $('#spinnerG').hide(); + } $('#banner').show(); return $q.reject(response); });