From 9aef5ba80e239aaab185618a817f407c07f79fa3 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Fri, 16 Jan 2015 10:24:56 -0600 Subject: [PATCH] Moving files from old repo --- .bowerrc | 3 + .eslintignore | 2 + .eslintrc | 11 ++ .gitignore | 5 + bower.json | 22 +++ client/static/404.html | 44 ++++++ client/static/assets/css/app.css | 143 ++++++++++++++++++ client/static/assets/css/opensans.css | 55 +++++++ client/static/assets/run-detail.json | 40 +++++ client/static/assets/scripts/app.js | 3 + client/static/assets/scripts/configs.js | 28 ++++ client/static/assets/scripts/constants.js | 17 +++ client/static/assets/scripts/controllers.js | 62 ++++++++ client/static/assets/scripts/directives.js | 21 +++ client/static/assets/scripts/filters.js | 12 ++ client/static/assets/scripts/services.js | 81 ++++++++++ .../assets/views/_partials/comment-form.html | 33 ++++ .../assets/views/_partials/comment-list.html | 19 +++ .../assets/views/_partials/footer-nav.html | 17 +++ .../assets/views/_partials/header-nav.html | 42 +++++ .../views/_partials/runs-active-table.html | 12 ++ .../assets/views/_partials/runs-body.html | 13 ++ .../views/_partials/runs-finished-table.html | 31 ++++ .../assets/views/_partials/runs-head.html | 4 + client/static/assets/views/agents-check.html | 13 ++ client/static/assets/views/agents-status.html | 13 ++ client/static/assets/views/cluster.html | 11 ++ client/static/assets/views/home.html | 11 ++ client/static/assets/views/reference.html | 36 +++++ client/static/assets/views/run-detail.html | 141 +++++++++++++++++ client/static/assets/views/runs-active.html | 16 ++ client/static/assets/views/runs-finished.html | 12 ++ client/static/assets/views/runs.html | 16 ++ client/static/assets/views/tests.html | 12 ++ client/static/index.html | 46 ++++++ package.json | 32 ++++ server.js | 3 + server/db.js | 29 ++++ server/index.js | 28 ++++ server/routes/404.js | 14 ++ server/routes/GET-api.js | 30 ++++ server/routes/GET-bower-components.js | 19 +++ server/routes/GET-comments.js | 56 +++++++ server/routes/GET-index.js | 14 ++ server/routes/GET-static.js | 19 +++ server/routes/POST-comments.js | 34 +++++ 46 files changed, 1325 insertions(+) create mode 100644 .bowerrc create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 bower.json create mode 100644 client/static/404.html create mode 100644 client/static/assets/css/app.css create mode 100644 client/static/assets/css/opensans.css create mode 100644 client/static/assets/run-detail.json create mode 100644 client/static/assets/scripts/app.js create mode 100644 client/static/assets/scripts/configs.js create mode 100644 client/static/assets/scripts/constants.js create mode 100644 client/static/assets/scripts/controllers.js create mode 100644 client/static/assets/scripts/directives.js create mode 100644 client/static/assets/scripts/filters.js create mode 100644 client/static/assets/scripts/services.js create mode 100644 client/static/assets/views/_partials/comment-form.html create mode 100644 client/static/assets/views/_partials/comment-list.html create mode 100644 client/static/assets/views/_partials/footer-nav.html create mode 100644 client/static/assets/views/_partials/header-nav.html create mode 100644 client/static/assets/views/_partials/runs-active-table.html create mode 100644 client/static/assets/views/_partials/runs-body.html create mode 100644 client/static/assets/views/_partials/runs-finished-table.html create mode 100644 client/static/assets/views/_partials/runs-head.html create mode 100644 client/static/assets/views/agents-check.html create mode 100644 client/static/assets/views/agents-status.html create mode 100644 client/static/assets/views/cluster.html create mode 100644 client/static/assets/views/home.html create mode 100644 client/static/assets/views/reference.html create mode 100644 client/static/assets/views/run-detail.html create mode 100644 client/static/assets/views/runs-active.html create mode 100644 client/static/assets/views/runs-finished.html create mode 100644 client/static/assets/views/runs.html create mode 100644 client/static/assets/views/tests.html create mode 100644 client/static/index.html create mode 100644 package.json create mode 100644 server.js create mode 100644 server/db.js create mode 100644 server/index.js create mode 100644 server/routes/404.js create mode 100644 server/routes/GET-api.js create mode 100644 server/routes/GET-bower-components.js create mode 100644 server/routes/GET-comments.js create mode 100644 server/routes/GET-index.js create mode 100644 server/routes/GET-static.js create mode 100644 server/routes/POST-comments.js diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..5942cf2 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "client/bower_components" +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..204111f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +client/bower_components +node_modules diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..4d903aa --- /dev/null +++ b/.eslintrc @@ -0,0 +1,11 @@ +env: + browser: true + node: true + +globals: + angular: false + +rules: + global-strict: 0 + no-use-before-define: [2, 'nofunc'] + quotes: [1, single] diff --git a/.gitignore b/.gitignore index 248afde..1b21118 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ nosetests.xml .mr.developer.cfg .project .pydevproject + +# Generated files not desired +.DS_Store +client/bower_components +node_modules \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..94a0515 --- /dev/null +++ b/bower.json @@ -0,0 +1,22 @@ +{ + "name": "angular-routes", + "version": "1.0.0", + "authors": [ + "Peter deHaan " + ], + "license": "WTFPL", + "private": true, + "ignore": [ + "**/.*", + "bower_components", + "node_modules" + ], + "dependencies": { + "angular": "~1.3.0", + "angular-route": "~1.3.0", + "angular-sanitize": "~1.3.0", + "bootstrap": "~3.2.0", + "foundation": "~5.4.6", + "foundation-icons": "*" + } +} diff --git a/client/static/404.html b/client/static/404.html new file mode 100644 index 0000000..010c032 --- /dev/null +++ b/client/static/404.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + 404 » Loads App + + + + + +
+
+

404 File not found!

+
+
+ + + + + + + + + + + + + + + + + + diff --git a/client/static/assets/css/app.css b/client/static/assets/css/app.css new file mode 100644 index 0000000..54c8b58 --- /dev/null +++ b/client/static/assets/css/app.css @@ -0,0 +1,143 @@ +/* Structure */ +body { + background: #e9eaed; +} + +body > header { + background: #c13832; + -webkit-box-shadow: 0 2px 2px -2px rgba(0, 0, 0, .52); + box-shadow: 0 2px 2px -2px rgba(0, 0, 0, .52); +} + +.top-bar, .top-bar-section li:not(.has-form) a:not(.button), .top-bar-section ul li { + background: none; +} + +.top-bar .name h1 a { + font-weight: bold; +} + +.top-bar-section > ul > .divider, .top-bar-section > ul > [role="separator"] { + border-right-color: #ad0901; +} + +nav.breadcrumbs { + background: none; + margin: 10px 0; + padding: 0; + border: 0; + float: right; +} + +h2 .label { + float: right; + font-size: 1.3rem; + background: transparent; + color: #999; +} + +.runs-column { + width: calc(50% - 20px); + width: -webkit-calc(50% - 20px); + float: left; +} + +@media (max-width: 768px) { + + .runs-column { + width: 100%; + float: none; + } +} + + +/* Tags */ +body, h1, h2, h3, h4, h5, h6 { + color: #4d4e53; + font-family: 'Open Sans', sans-serif; +} + +h1 { + color: #222; + margin-top: 20px; +} + +h2 { + font-size: 1.5em; +} + +.runs-column table caption { + color: inherit; + text-align: start; +} + +.runs-column table h2 { + margin-bottom: 0; +} + +.runs-column table caption small { + display: block; + color: #999; + margin-bottom: 10px; + font-weight: normal; + font-size: 70%; +} + +.run-table { + width: 100%; +} +.run-table tbody td { + border-bottom: 1px solid #eee; +} + +.run-result { + position: relative; + padding-right: 40px; +} + +.run-result div { + font-size: 85%; + display: inline-block; + margin-right: 8px; +} + +.run-result-icon { + position: absolute; + top: 35%; + right: 20px; +} + + + + +a.run-link { + font-weight: bold; + display: block; +} + +.success { + background-color: rgba(0, 255, 0, 0.05) !important; +} + +.danger { + background-color: rgba(255, 0, 0, 0.05) !important; +} + +.success-text { + color: green; +} +.danger-text { + color: red; +} + +table.details-table { + width: 100%; +} + +table.details-table th { + text-align: start; +} + +table.details-table tbody th[scope="row"] { + width: 10em; +} diff --git a/client/static/assets/css/opensans.css b/client/static/assets/css/opensans.css new file mode 100644 index 0000000..fdd94c6 --- /dev/null +++ b/client/static/assets/css/opensans.css @@ -0,0 +1,55 @@ +@font-face { + font-family: 'Open Sans Light'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Light-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Light-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Light-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Light-webfont.svg#OpenSansLight') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Open Sans Light'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Open Sans Light'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-LightItalic-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-LightItalic-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-LightItalic-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-LightItalic-webfont.svg#OpenSansRegular') format('svg'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Open Sans'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Open Sans'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Semibold-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Semibold-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Semibold-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Open Sans'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Italic-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Italic-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Italic-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Open Sans Extra Bold'; + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-ExtraBold-webfont.eot'); + src: url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-ExtraBold-webfont.eot?#iefix') format('embedded-opentype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-ExtraBold-webfont.woff') format('woff'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-ExtraBold-webfont.ttf') format('truetype'), url('//mozorg.cdn.mozilla.net/media/fonts/OpenSans-ExtraBold-webfont.svg#OpenSansSemibold') format('svg'); + font-weight: bold; + font-style: normal; +} diff --git a/client/static/assets/run-detail.json b/client/static/assets/run-detail.json new file mode 100644 index 0000000..7e6375a --- /dev/null +++ b/client/static/assets/run-detail.json @@ -0,0 +1,40 @@ +{ + "desc": "Testing loadtest.TestLoop.test_all", + "status": { + "author": "jbonacci", + "runId": "dc481968-3610-4083-a5e0-3b6c0bb40f8a", + "startTime": "2014-10-20T17:43:43-08:00", + "endTime": "2014-10-20T18:14:06-08:00", + "duration": "30 min and 23 sec", + "state": "Ended" + }, + "configuration": { + "users": [ + 20 + ], + "hits": 0, + "agents": 5, + "duration": 1800, + "serverUrl": "https://loop.stage.mozaws.net" + }, + "results": { + "testsOver": 70204, + "successes": 70204, + "failures": 0, + "errors": 2, + "tcpHits": 281264, + "openedWebSockets": 140488, + "totalWebSockets": 140488, + "bytesPerWebSocket": 30294148, + "requestsPerSecond": 154 + }, + "customMetrics": { + "healthCheck": 20 + }, + "errors": [ + { + "count": "2", + "message": "Invalid response\n File \"/usr/lib/python2.7/unittest/case.py\", line 332, in run\n testMethod()\n File \"loadtest.py\", line 47, in test_all\n self._test_websockets(*params)\n File \"loadtest.py\", line 270, in _test_websockets\n callee_ws = self.create_ws(progress_url, callback=_handle_callee)\n File \"loadtest.py\", line 34, in create_ws\n ws = TestCase.create_ws(self, *args, **kw)\n File \"/home/ubuntu/loads/loads/case.py\", line 65, in create_ws\n test_case=self)\n File \"/home/ubuntu/loads/loads/websockets.py\", line 56, in create_ws\n extensions, klass, test_case)\n File \"/home/ubuntu/loads/loads/websockets.py\", line 82, in _create_ws\n socket.connect()\n File \"/home/ubuntu/loads/local/lib/python2.7/site-packages/ws4py/client/__init__.py\", line 126, in connect\n raise HandshakeError(\"Invalid response\")" + } + ] +} diff --git a/client/static/assets/scripts/app.js b/client/static/assets/scripts/app.js new file mode 100644 index 0000000..78c4107 --- /dev/null +++ b/client/static/assets/scripts/app.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('LoadsApp', ['ngRoute', 'ngSanitize']); diff --git a/client/static/assets/scripts/configs.js b/client/static/assets/scripts/configs.js new file mode 100644 index 0000000..59f9a07 --- /dev/null +++ b/client/static/assets/scripts/configs.js @@ -0,0 +1,28 @@ +'use strict'; + +angular.module('LoadsApp') + .config(function ($routeProvider, $locationProvider) { + $locationProvider.html5Mode(false); + + $routeProvider + .when('/', route('RunsController', 'runs')) + .when('/cluster', route('ClusterManagementController', 'cluster')) + .when('/agents/status', route('AgentsStatusController', 'agents-status')) + .when('/agents/check', route('LaunchAgentHealthCheckController', 'agents-check')) + .when('/tests', route('TestsController', 'tests')) + .when('/runs', route('RunsController', 'runs')) + .when('/runs/active', route('ActiveRunsController', 'runs-active')) + .when('/runs/finished', route('FinishedRunsController', 'runs-finished')) + .when('/run/:id?', route('RunDetailController', 'run-detail')) + .when('/reference', route('ReferenceController', 'reference')) + .otherwise({ + redirectTo: '/' + }); + }); + +function route(controller, templateUrl) { + return { + controller: controller, + templateUrl: 'assets/views/' + templateUrl + '.html' + }; +} diff --git a/client/static/assets/scripts/constants.js b/client/static/assets/scripts/constants.js new file mode 100644 index 0000000..d338128 --- /dev/null +++ b/client/static/assets/scripts/constants.js @@ -0,0 +1,17 @@ +'use strict'; + +angular.module('LoadsApp') + .constant('ROUTES', { + HOME: '/#/', + CLUSTER: '/#/cluster', + AGENTS_STATUS: '/#/agents/status', + AGENTS_CHECK: '/#/agents/check', + TESTS: '/#/tests', + RUNS: '/#/runs', + RUNS_ACTIVE: '/#/runs/active', + RUNS_FINISHED: '/#/runs/finished', + RUN_DETAIL: '/#/run', + REFERENCE: '/#/reference' + }) + .constant('WEBSOCKET_URL', 'wss://loads.services.mozilla.com/status/websocket') + .constant('YEAR', new Date().getFullYear()); diff --git a/client/static/assets/scripts/controllers.js b/client/static/assets/scripts/controllers.js new file mode 100644 index 0000000..9268a98 --- /dev/null +++ b/client/static/assets/scripts/controllers.js @@ -0,0 +1,62 @@ +'use strict'; + +angular.module('LoadsApp') + .controller('AppController', ['$scope', 'ROUTES', 'YEAR', function ($scope, ROUTES, YEAR) { + $scope.ROUTES = ROUTES; + $scope.YEAR = YEAR; + // Unused + }]).controller('HomeController', function ($scope, $rootScope) { + $rootScope.title = 'Home'; + }).controller('ClusterManagementController', function ($scope, $rootScope) { + $rootScope.title = 'Cluster Management'; + }).controller('AgentsStatusController', function ($scope, $rootScope) { + $rootScope.title = 'Agents Status'; + }).controller('LaunchAgentHealthCheckController', function ($scope, $rootScope) { + $rootScope.title = 'Launch Agent Health Check'; + }).controller('TestsController', function ($scope, $rootScope) { + $rootScope.title = 'Containers'; + }).controller('RunsController', function ($scope, $rootScope, MockRunsService, RunsService) { + $rootScope.title = 'Runs'; + }).controller('ActiveRunsController', function ($scope, $rootScope, MockRunsService, RunsService) { + $rootScope.title = 'Active Runs'; + }).controller('FinishedRunsController', function ($scope, $rootScope, MockRunsService, RunsService) { + $rootScope.title = 'Finished Runs'; + }).controller('RunDetailController', function ($scope, $rootScope, $routeParams, $http) { + var id = $routeParams.id; + $rootScope.title = 'Run'; + $scope.params = $routeParams; + + // For use for ng-model binding. + $scope.runComment = { + runId: id + }; + + $scope.resetCommentForm = function () { + delete $scope.runComment.comment; + }; + + $scope.submitCommentForm = function () { + var data = angular.copy($scope.runComment); + data.runId = id; + $http.post('/api/comments/', data).success(function (data) { + $scope.resetCommentForm(); + }).error(function (err) { + console.log(err); + }); + }; + + $http.get('/api/comments/runId/' + id).success(function (data) { + $scope.comments = data; + }); + + $http.get('/assets/run-detail.json').success(function (data) { + data.status.runId = id; + // data.status.startTime = new Date(data.status.startTime); + // data.status.endTime = new Date(data.status.endTime); + $scope.details = data; + }).error(function (err) { + console.error(err); + }); + }).controller('ReferenceController', function ($scope, $rootScope) { + $rootScope.title = 'Reference'; + }); diff --git a/client/static/assets/scripts/directives.js b/client/static/assets/scripts/directives.js new file mode 100644 index 0000000..1c6e14c --- /dev/null +++ b/client/static/assets/scripts/directives.js @@ -0,0 +1,21 @@ +'use strict'; + +angular.module('LoadsApp') + .directive('headerNav', partial(true, 'E', 'header-nav')) + .directive('footerNav', partial(true, 'E', 'footer-nav')) + .directive('activeRuns', partial(false, 'E', 'runs-active-table')) + .directive('finishedRuns', partial(false, 'E', 'runs-finished-table')) + .directive('runsHead', partial(false, 'A', 'runs-head')) + .directive('runsBody', partial(false, 'A', 'runs-body')) + .directive('commentList', partial(true, 'E', 'comment-list')) + .directive('commentForm', partial(true, 'E', 'comment-form')); + +function partial(replace, restrict, templateUrl) { + return function () { + return { + replace: replace, + restrict: restrict, + templateUrl: 'assets/views/_partials/' + templateUrl + '.html' + }; + }; +} diff --git a/client/static/assets/scripts/filters.js b/client/static/assets/scripts/filters.js new file mode 100644 index 0000000..cfe9e78 --- /dev/null +++ b/client/static/assets/scripts/filters.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('LoadsApp') + .filter('checkmark', function () { + return function (bool) { + return bool ? '\u2713' : '\u2718'; + }; + }).filter('successClass', function () { + return function (bool) { + return bool ? 'success' : 'danger'; + }; + }); diff --git a/client/static/assets/scripts/services.js b/client/static/assets/scripts/services.js new file mode 100644 index 0000000..88ef021 --- /dev/null +++ b/client/static/assets/scripts/services.js @@ -0,0 +1,81 @@ +'use strict'; + +angular.module('LoadsApp') + .factory('RunsService', function ($rootScope, WEBSOCKET_URL) { + var cleanData = function (arr) { + return arr.map(function (result) { + var data = { + date: result[0], + name: result[1], + runId: result[2], + details: result[3] + }; + data.success = (data.details.metadata.style === 'green'); + data.cssClass = (data.success) ? 'success' : 'danger'; + data.iconClass = (data.success) ? 'checkmark' : 'remove'; + return data; + }); + }; + + var ws = new WebSocket(WEBSOCKET_URL); + ws.onopen = function () { + console.log('Socket has been opened to %s', WEBSOCKET_URL); + }; + ws.onmessage = function (message) { + $rootScope.$apply(function () { + try { + var data = angular.fromJson(message.data); + $rootScope.runs = { + active: cleanData(data.active), + inactive: cleanData(data.inactive), + hasActive: (data.active.length !== 0), + hasInactive: (data.inactive.length !== 0), + lastSync: new Date() + }; + } catch (err) { + console.error(err); + console.log(message); + } + }); + }; + + return ws; + }) + .factory('MockRunsService', ['$http', function ($http) { + var getRuns = function (active, count) { + active = active ? 'active' : 'finished'; + count = Math.min(parseInt(count, 10), 30); + + // Example: "/mock/api/finished/5" + var API_URL = ['/mock', 'api', active, count].join('/'); + + return $http.get(API_URL).error(function (err) { + console.error(err); + }); + }; + + return { + getActiveRuns: function () { + var cnt = Math.floor(Math.random() * 5); + return getRuns(true, cnt).success(function (runs) { + runs.map(function (run) { + run.success = true; + return run; + }); + + if (runs.length > 3) { + return []; + } + return runs; + }); + }, + getFinishedRuns: function (count) { + return getRuns(false, count).success(function (runs) { + // console.table(runs); + return runs.sort(function (runA, runB) { + return new Date(runB.endDate) - new Date(runA.endDate); + }); + }); + } + }; + }]); diff --git a/client/static/assets/views/_partials/comment-form.html b/client/static/assets/views/_partials/comment-form.html new file mode 100644 index 0000000..cec5bf0 --- /dev/null +++ b/client/static/assets/views/_partials/comment-form.html @@ -0,0 +1,33 @@ +
+
+

Add Comment

+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/client/static/assets/views/_partials/comment-list.html b/client/static/assets/views/_partials/comment-list.html new file mode 100644 index 0000000..6acac97 --- /dev/null +++ b/client/static/assets/views/_partials/comment-list.html @@ -0,0 +1,19 @@ +
+

Comments {{ comments.length }}

+ +
+
+
+ commented + +
+
+
+
+
+
+
+
diff --git a/client/static/assets/views/_partials/footer-nav.html b/client/static/assets/views/_partials/footer-nav.html new file mode 100644 index 0000000..6e20ab8 --- /dev/null +++ b/client/static/assets/views/_partials/footer-nav.html @@ -0,0 +1,17 @@ + diff --git a/client/static/assets/views/_partials/header-nav.html b/client/static/assets/views/_partials/header-nav.html new file mode 100644 index 0000000..c7961f5 --- /dev/null +++ b/client/static/assets/views/_partials/header-nav.html @@ -0,0 +1,42 @@ +
+ +
diff --git a/client/static/assets/views/_partials/runs-active-table.html b/client/static/assets/views/_partials/runs-active-table.html new file mode 100644 index 0000000..2aad4bd --- /dev/null +++ b/client/static/assets/views/_partials/runs-active-table.html @@ -0,0 +1,12 @@ + + + + + + + + +
+

Active {{ runs.active.length }}

+ Last updated: +
diff --git a/client/static/assets/views/_partials/runs-body.html b/client/static/assets/views/_partials/runs-body.html new file mode 100644 index 0000000..72f95ab --- /dev/null +++ b/client/static/assets/views/_partials/runs-body.html @@ -0,0 +1,13 @@ + +
+ + {{ run.name }} +
+
ago
+
+
\ No newline at end of file diff --git a/client/static/assets/views/_partials/runs-finished-table.html b/client/static/assets/views/_partials/runs-finished-table.html new file mode 100644 index 0000000..cf46329 --- /dev/null +++ b/client/static/assets/views/_partials/runs-finished-table.html @@ -0,0 +1,31 @@ + + + + + + + + + +
+

Finished {{ runs.inactive.length }}

+ Last updated: +
diff --git a/client/static/assets/views/_partials/runs-head.html b/client/static/assets/views/_partials/runs-head.html new file mode 100644 index 0000000..2fc6102 --- /dev/null +++ b/client/static/assets/views/_partials/runs-head.html @@ -0,0 +1,4 @@ +Run \ No newline at end of file diff --git a/client/static/assets/views/agents-check.html b/client/static/assets/views/agents-check.html new file mode 100644 index 0000000..76cf6c1 --- /dev/null +++ b/client/static/assets/views/agents-check.html @@ -0,0 +1,13 @@ + diff --git a/client/static/assets/views/agents-status.html b/client/static/assets/views/agents-status.html new file mode 100644 index 0000000..5e85c05 --- /dev/null +++ b/client/static/assets/views/agents-status.html @@ -0,0 +1,13 @@ + diff --git a/client/static/assets/views/cluster.html b/client/static/assets/views/cluster.html new file mode 100644 index 0000000..490d45c --- /dev/null +++ b/client/static/assets/views/cluster.html @@ -0,0 +1,11 @@ + diff --git a/client/static/assets/views/home.html b/client/static/assets/views/home.html new file mode 100644 index 0000000..0d3e09c --- /dev/null +++ b/client/static/assets/views/home.html @@ -0,0 +1,11 @@ +
+
+ +

{{ title }}

+
+ +

This is my home page. There are many like it, but this one is mine.

+

Here is some more content.

+
diff --git a/client/static/assets/views/reference.html b/client/static/assets/views/reference.html new file mode 100644 index 0000000..93610e2 --- /dev/null +++ b/client/static/assets/views/reference.html @@ -0,0 +1,36 @@ +
+
+ +

{{ title }}

+
+ + + +

Usage/Getting Started

+ + +

Loads Tool v1

+ + +

Loads Tool v2

+ + +

Mozilla Services - products under test

+ +
diff --git a/client/static/assets/views/run-detail.html b/client/static/assets/views/run-detail.html new file mode 100644 index 0000000..54a0b34 --- /dev/null +++ b/client/static/assets/views/run-detail.html @@ -0,0 +1,141 @@ +
+
+ +

{{ title }}

+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Status

Run Id:
Duration:
Started:
Ended:
State:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Configuration

Users:
Hits:
Agents:
Duration:
Server URL:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Results

Tests over:
Successes:
Failures:
Errors:
TCP Hits:
+ + + + + + + + + + + + + + +

Custom Metrics

Health check:
+ + + + + + + + + + + + + +

Errors

+

{{ error.count }} occurrences

+

+      
+ + + + +
diff --git a/client/static/assets/views/runs-active.html b/client/static/assets/views/runs-active.html new file mode 100644 index 0000000..b2e8fcf --- /dev/null +++ b/client/static/assets/views/runs-active.html @@ -0,0 +1,16 @@ +
+
+ +

{{ title }}

+
+ +

Some Active Runs content

+

Mo content, mo problems!

+ + + +
diff --git a/client/static/assets/views/runs-finished.html b/client/static/assets/views/runs-finished.html new file mode 100644 index 0000000..8ce4844 --- /dev/null +++ b/client/static/assets/views/runs-finished.html @@ -0,0 +1,12 @@ + diff --git a/client/static/assets/views/runs.html b/client/static/assets/views/runs.html new file mode 100644 index 0000000..725f1f2 --- /dev/null +++ b/client/static/assets/views/runs.html @@ -0,0 +1,16 @@ + diff --git a/client/static/assets/views/tests.html b/client/static/assets/views/tests.html new file mode 100644 index 0000000..7e40975 --- /dev/null +++ b/client/static/assets/views/tests.html @@ -0,0 +1,12 @@ +
+
+ +

{{ title }}

+
+ +

Project/Strategy/Container generation menu

+

Some more content

+
diff --git a/client/static/index.html b/client/static/index.html new file mode 100644 index 0000000..cba1de6 --- /dev/null +++ b/client/static/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + {{ title }} » Loads App + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..abb1870 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "loads-template-v3", + "description": "Testing Hapi w/ Angular routing", + "version": "1.0.0", + "author": "Peter deHaan (http://nodeexamples.com/)", + "bugs": "https://github.com/pdehaan/loads-template-v3/issues", + "dependencies": { + "hapi": "7.1.1", + "joi": "4.7.0", + "mockloadsresults": "pdehaan/MockLoadsResults", + "mysql": "2.5.3", + "remarkable": "1.4.1", + "sequelize": "2.0.0-rc2" + }, + "devDependencies": { + "eslint": "0.9.1" + }, + "homepage": "https://github.com/pdehaan/loads-template-v3", + "keywords": [ + "hapi", + "angular" + ], + "license": "WTFPL", + "main": "server.js", + "repository": "pdehaan/loads-template-v3", + "scripts": { + "lint": "eslint .", + "postinstall": "bower update", + "start": "PORT=5000 node server", + "test": "npm run lint" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..9a4b112 --- /dev/null +++ b/server.js @@ -0,0 +1,3 @@ +'use strict'; + +require('./server/index'); diff --git a/server/db.js b/server/db.js new file mode 100644 index 0000000..2288841 --- /dev/null +++ b/server/db.js @@ -0,0 +1,29 @@ +'use strict'; + +var Sequelize = require('sequelize'); + +var CONNECTION_STRING = 'mysql://bf7b93b4271c8b:b86e5248@us-cdbr-iron-east-01.cleardb.net/heroku_9f656c64b3de37f?reconnect=true'; + +var sequelize = new Sequelize(CONNECTION_STRING, { + dialect: 'mysql', + logging: false, + pool: { + maxConnections: 10, + maxIdleTime: 30 + } +}); + +exports.Comment = sequelize.define('Comment', { + runId: Sequelize.STRING, + name: Sequelize.STRING, + comment: Sequelize.TEXT +}); + +// Sync the models to the server and recreate tables. +sequelize.sync({ + force: false +}).complete(function (err) { + if (err) { + return console.error(err); + } +}); diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..c04603d --- /dev/null +++ b/server/index.js @@ -0,0 +1,28 @@ +'use strict'; + +var Hapi = require('hapi'); + +var PORT = process.env.PORT || 5000; + +var server = new Hapi.Server('0.0.0.0', PORT, {}); +server.route([ + require('./routes/GET-static'), + require('./routes/GET-bower-components'), + require('./routes/GET-index'), + require('./routes/GET-api'), + require('./routes/GET-comments'), + require('./routes/POST-comments'), + require('./routes/404') +]); + +server.ext('onPreResponse', function (request, reply) { + console.log('[%s] %d %s', request.method.toUpperCase(), request.response.statusCode, request.url.path); + var response = request.response; + if (!response.isBoom) { + return reply(); + } +}); + +server.start(function () { + console.log('Hapi %s server started at %s', Hapi.version, server.info.uri); +}); diff --git a/server/routes/404.js b/server/routes/404.js new file mode 100644 index 0000000..2599d3d --- /dev/null +++ b/server/routes/404.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + method: '*', + path: '/{param*}', + handler: function (request, reply) { + reply.file('client/static/404.html').code(404); + }, + config: { + description: 'Static route for the /static/ directory.', + notes: 'This directory contains all the CSS, images, and scripts which are used by the app.', + tags: ['static', 'assets'] + } +}; diff --git a/server/routes/GET-api.js b/server/routes/GET-api.js new file mode 100644 index 0000000..5771d71 --- /dev/null +++ b/server/routes/GET-api.js @@ -0,0 +1,30 @@ +'use strict'; + +var Joi = require('joi'); + +var makeResults = require('mockloadsresults').makeResults; + +module.exports = { + method: 'GET', + path: '/mock/api/{active}/{count?}', + handler: function (req, reply) { + var active = (req.params.active === 'active'); + var count = req.params.count; + var results = makeResults(active, count); + if (results.length < 2) { + return reply([]); + } + reply(results); + }, + config: { + validate: { + params: { + active: Joi.string().required().valid('active', 'finished'), + count: Joi.number().min(0).max(30) + } + }, + description: 'API endpoint to get an array of active or finished results.', + notes: 'This is used for the /runs/, /runs/active/, and /runs/finished/ routes.', + tags: ['api', 'results', 'runs'] + } +}; diff --git a/server/routes/GET-bower-components.js b/server/routes/GET-bower-components.js new file mode 100644 index 0000000..b527ffa --- /dev/null +++ b/server/routes/GET-bower-components.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = { + method: 'GET', + path: '/bower_components/{param*}', + handler: { + directory: { + path: 'client/bower_components', + index: false, + listing: false, + lookupCompressed: true + } + }, + config: { + description: 'Static route for the /bower_components/ directory.', + notes: 'This directory contains all the bower dependencies.', + tags: ['static', 'assets'] + } +}; diff --git a/server/routes/GET-comments.js b/server/routes/GET-comments.js new file mode 100644 index 0000000..3b34b26 --- /dev/null +++ b/server/routes/GET-comments.js @@ -0,0 +1,56 @@ +'use strict'; + +var Joi = require('joi'); +var Remarkable = require('remarkable'); + +var Comment = require('../db').Comment; + +var md = new Remarkable('full', { + breaks: true, + html: true, + linkify: true, + typographer: true, + xhtmlOut: true +}); + +var mdKey = function (key) { + return function (result) { + result.comment = md.render(result.comment); + return result; + }; +}; + +module.exports = { + method: 'GET', + path: '/api/comments/runId/{runId}', + handler: function (req, reply) { + var runId = req.params.runId; + + var args = { + where: { + runId: runId + } + }; + + Comment.findAll(args).on('success', function (comments) { + comments = comments.map(function (item) { + var values = item.dataValues; + // Convert our comment from Markdown to HTML. + values.comment = md.render(values.comment); + return values; + }); + + reply(comments); + }); + }, + config: { + validate: { + params: { + runId: Joi.string().required() + } + }, + description: 'API endpoint to get all comments for a specific `runId`.', + notes: 'This is used on the runs details page.', + tags: ['api', 'comments'] + } +}; diff --git a/server/routes/GET-index.js b/server/routes/GET-index.js new file mode 100644 index 0000000..2a38ea6 --- /dev/null +++ b/server/routes/GET-index.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + method: 'GET', + path: '/', + handler: function (request, reply) { + reply.file('client/static/index.html'); + }, + config: { + description: 'Static route for the /static/ directory.', + notes: 'This directory contains all the CSS, images, and scripts which are used by the app.', + tags: ['static', 'assets'] + } +}; diff --git a/server/routes/GET-static.js b/server/routes/GET-static.js new file mode 100644 index 0000000..1df1910 --- /dev/null +++ b/server/routes/GET-static.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = { + method: 'GET', + path: '/assets/{param*}', + handler: { + directory: { + path: 'client/static/assets', + listing: false, + index: false, + lookupCompressed: true + } + }, + config: { + description: 'Static route for the /static/ directory.', + notes: 'This directory contains all the CSS, images, and scripts which are used by the app.', + tags: ['static', 'assets'] + } +}; diff --git a/server/routes/POST-comments.js b/server/routes/POST-comments.js new file mode 100644 index 0000000..341045e --- /dev/null +++ b/server/routes/POST-comments.js @@ -0,0 +1,34 @@ +'use strict'; + +var Joi = require('joi'); + +var Comment = require('../db').Comment; + +module.exports = { + method: 'POST', + path: '/api/comments/', + handler: function (req, reply) { + + Comment.create(req.payload).complete(function (err, comment) { + if (err) { + return reply(err); + } + reply(comment); + }); + + // console.log('a:', JSON.stringify(req.payload, null, 2)); + // reply({success: true}); + // }, + // config: { + // validate: { + // params: { + // runId: Joi.string().required(), + // name: Joi.string(), + // comment: Joi.string() + // } + // }, + // description: 'API endpoint to add a comment to the database for a specific `runId`.', + // notes: 'Requires the following keys `runId`, `name`, `comment`.', + // tags: ['api', 'comments'] + } +};