Browse Source

Merge branch 'achrafbenyounes-refactor' into refactor

Gaël Métais 10 years ago
parent
commit
9fe1808e1c

+ 18 - 11
Gruntfile.js

@@ -12,26 +12,29 @@ module.exports = function(grunt) {
         
         font: {
             icons: {
-                src: ['app/public/fonts/svg-icons/*.svg'],
-                destCss: 'app/public/styles/less/icons.less',
-                destFonts: 'app/public/fonts/icons.woff',
+                src: ['front/src/fonts/svg-icons/*.svg'],
+                destCss: 'front/src/less/icons.less',
+                destFonts: 'front/src/fonts/icons.woff',
 
                 // Optional: Custom routing of font filepaths for CSS
                 cssRouter: function (fontpath) {
                     var pathArray = fontpath.split('/');
                     var fileName = pathArray[pathArray.length - 1];
-                    return '/public/fonts/' + fileName;
+                    return '/front/fonts/' + fileName;
                 }
             }
         },
         less: {
             all: {
-                files: {
-                    'app/public/styles/main.css': [ 'app/public/styles/less/main.less' ],
-                    'app/public/styles/index.css': [ 'app/public/styles/less/index.less' ],
-                    'app/public/styles/launchTest.css': [ 'app/public/styles/less/launchTest.less' ],
-                    'app/public/styles/results.css': [ 'app/public/styles/less/results.less' ]
-                }
+                files: [
+                    {
+                        expand: true,
+                        cwd: 'front/src/less/',
+                        src: ['**/*.less'],
+                        dest: 'front/src/css/',
+                        ext: '.css'
+                    }
+                ]
             }
         },
         jshint: {
@@ -51,6 +54,9 @@ module.exports = function(grunt) {
             icons: {
                 src: ['tmp']
             },
+            dev: {
+                src: ['front/src/css']
+            },
             coverage: {
                 src: ['coverage/']
             }
@@ -166,7 +172,8 @@ module.exports = function(grunt) {
     ]);
 
     grunt.registerTask('dev', [
-        'express:dev'
+        'clean:dev',
+        'less'
     ]);
 
     grunt.registerTask('test', [

+ 1 - 0
app/public/scripts/app.js

@@ -1,5 +1,6 @@
 angular.module('YellowLabTools', [
   'Results',
+  'Runs',
   'ngModal',
   'ShowOffendersDirective'
 ]);

+ 479 - 0
front/src/css/dashboard.css

@@ -0,0 +1,479 @@
+/* Timeline colors, related to Window Performances */
+.resultsMenu {
+  margin-top: 2em;
+}
+.resultsMenu .menuItem {
+  display: inline-block;
+  margin: 1em;
+  width: 8em;
+  height: 7em;
+  color: #fff;
+  border: 3px solid #fff;
+  border-radius: 0.5em;
+  cursor: pointer;
+  text-decoration: none;
+}
+.resultsMenu .back {
+  color: #5e2846;
+  border-color: #5e2846;
+}
+.resultsMenu .menuItem div {
+  padding-top: 0.5em;
+  font-size: 3em;
+}
+.resultsMenu .active,
+.resultsMenu .menuItem.active:hover {
+  color: #ffa319;
+  border-color: #ffa319;
+}
+.resultsMenu .menuItem:hover {
+  color: #ffa319;
+}
+.resultsMenu span {
+  position: relative;
+  top: 0.5em;
+}
+.testedUrl {
+  color: inherit;
+}
+h4 {
+  margin-bottom: 0.5em;
+}
+.summary,
+.metrics,
+.execution {
+  margin-top: 2em;
+  padding: 1em;
+  background: #fff;
+  color: #000;
+  border-radius: 0.5em;
+  text-align: left;
+}
+.notations {
+  display: table;
+  width: 90%;
+  margin: 0 10%;
+  border-spacing: 1em;
+}
+.notations > div {
+  display: table-row;
+}
+.notations > div > div {
+  display: table-cell;
+  height: 2.5em;
+  vertical-align: middle;
+}
+.notations .notation {
+  font-weight: bold;
+  text-align: center;
+}
+.notations .criteria {
+  font-weight: normal;
+}
+.notations .A,
+.notations .B,
+.notations .C,
+.notations .D,
+.notations .E,
+.notations .F,
+.notations .NA {
+  width: 2.5em;
+  font-size: 2em;
+  text-align: center;
+  border-radius: 0.5em;
+  font-weight: bold;
+}
+.notations .grade .A,
+.notations .grade .B,
+.notations .grade .C,
+.notations .grade .D,
+.notations .grade .E,
+.notations .grade .F,
+.notations .grade .NA {
+  width: 1em;
+  height: 1em;
+  font-size: 1em;
+  color: transparent;
+  position: relative;
+  top: 0.15em;
+}
+.notations .A {
+  /* green */
+  background: #00DB61;
+}
+.notations .B {
+  /* green */
+  background: #CAD600;
+}
+.notations .C {
+  /* yellow */
+  background: #FFD119;
+}
+.notations .D {
+  /* orange */
+  background: #FFA319;
+}
+.notations .E {
+  /* red */
+  background: #FF6600;
+}
+.notations .F {
+  /* red */
+  background: #FF1919;
+}
+.notations .NA {
+  /* Non applicable */
+  background: #CCC;
+}
+.notations .icon-eye {
+  color: #9c4274;
+  cursor: pointer;
+}
+.notations .criteria .table {
+  width: 75%;
+}
+.notations .criteria .label {
+  width: 70%;
+}
+.notations .criteria .result {
+  width: 20%;
+  font-weight: bold;
+  white-space: nowrap;
+  text-align: center;
+}
+.notations .warning .label,
+.notations .warning .result {
+  color: #FF1919;
+}
+.notations .criteria .info {
+  width: 10%;
+  text-align: center;
+}
+.notations .criteria .icon-question {
+  color: #f1c40f;
+  cursor: pointer;
+}
+.timeline {
+  margin: 2em 0 5em;
+}
+.timeline .chart {
+  position: relative;
+  width: 100%;
+  border-bottom: 1px solid #000;
+}
+.timeline .startTime,
+.timeline .endTime {
+  position: absolute;
+  bottom: 0.5em;
+  font-size: 0.8em;
+}
+.timeline .startTime {
+  left: 0em;
+}
+.timeline .endTime {
+  right: 0em;
+}
+.timeline .chartPoints {
+  display: table;
+  height: 100px;
+  width: 99%;
+  margin: 0 auto;
+}
+.timeline .interval {
+  display: table-cell;
+  position: relative;
+  height: 100px;
+  width: 0.5%;
+}
+.timeline .interval .color {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+}
+.timeline div.interval:hover {
+  background: #9C4274;
+}
+.timeline .interval:hover .color {
+  background: #F04DA7;
+}
+.timeline .domComplete.interval {
+  background: #ede3ff;
+}
+.timeline .domComplete .color {
+  background: #c2a3ff;
+}
+.timeline .domContentLoadedEnd.interval {
+  background: #d8f0f0;
+}
+.timeline .domContentLoadedEnd .color {
+  background: #7ecccc;
+}
+.timeline .domContentLoaded.interval {
+  background: #e0ffd1;
+}
+.timeline .domContentLoaded .color {
+  background: #a7e846;
+}
+.timeline .domInteractive.interval {
+  background: #fffccc;
+}
+.timeline .domInteractive .color {
+  background: #ffe433;
+}
+.timeline .domCreation.interval {
+  background: #ffe0cc;
+}
+.timeline .domCreation .color {
+  background: #ff6600;
+}
+.timeline .tooltip.detailsOverlay {
+  position: absolute;
+  display: none;
+  width: auto;
+  padding: 0.5em 1em;
+  top: -1.5em;
+  right: 1em;
+}
+.timeline .interval:hover .tooltip {
+  display: block;
+}
+.timeline .legend {
+  display: table;
+  width: 100%;
+  margin-top: 1em;
+}
+.timeline .legend > div {
+  display: table-row;
+}
+.timeline .legend > div > div {
+  position: relative;
+  display: table-cell;
+  margin-top: 1em;
+}
+.timeline .titles {
+  font-weight: bold;
+}
+.timeline .titles > div {
+  padding: 0 1em 0 2em;
+}
+.timeline .tips {
+  font-size: 0.7em;
+}
+.timeline .tips > div {
+  padding: 1em 1em 0 0;
+}
+.timeline .legend .color {
+  display: block;
+  position: absolute;
+  left: 0;
+  height: 1.5em;
+  width: 1.5em;
+  border-radius: 0.2em;
+}
+.metrics h4 {
+  padding-left: 2em;
+}
+.metrics .module {
+  padding-left: 4em;
+  padding-top: 0.5em;
+}
+.metrics .legend {
+  font-style: italic;
+  color: #aaa;
+}
+.metrics .offenders {
+  padding-left: 0em;
+  font-size: 0.8em;
+}
+.metrics .offenders div {
+  cursor: pointer;
+}
+.metrics .offenders ul {
+  margin-top: 0.5em;
+}
+.filters {
+  margin: 1em 0;
+  padding: 0.5em;
+  border: 1px dotted #aaa;
+}
+.slowRequestsLimit {
+  width: 3em;
+  font-size: 1em;
+  text-align: right;
+  border: 1px solid #aaa;
+}
+input.textFilter {
+  box-shadow: none;
+  font-size: 1em;
+  padding: 0 0.2em;
+  border: 1px solid #aaa;
+  border-radius: none;
+  width: 15em;
+}
+.table {
+  display: table;
+  width: 100%;
+  border-spacing: 0.25em;
+}
+.table > div {
+  display: table-row;
+}
+.table > .headers > div {
+  font-weight: bold;
+  padding: 0.5em 1em;
+}
+.table > div > div {
+  padding: 0.1em 1em;
+  background: #f2f2f2;
+  display: table-cell;
+  text-align: left;
+}
+.table > div.jsError > .type,
+.table > div.jsError > .value {
+  color: #e74c3c;
+  font-weight: bold;
+}
+.table > div.windowPerformance > div,
+.table > div.windowPerformance > div.startTime {
+  background: #EBD8E2;
+}
+.table > div.showingDetails > div {
+  background: #f1c40f;
+}
+.table > div > .index {
+  color: #bbb;
+}
+.table > div > .type {
+  white-space: nowrap;
+}
+.table > div > .value {
+  width: 70%;
+  word-break: break-all;
+}
+.table > div > .details {
+  position: relative;
+}
+.table .details .icon-question {
+  color: #f1c40f;
+  cursor: pointer;
+}
+.table .details .icon-warning {
+  cursor: pointer;
+}
+.detailsOverlay {
+  position: absolute;
+  right: 3em;
+  top: -3em;
+  width: 45em;
+  min-height: 1em;
+  padding: 0 1em 1em;
+  background: #fff;
+  border: 2px solid #f1c40f;
+  border-radius: 0.5em;
+  z-index: 1;
+}
+@media screen and (max-width: 1024px) {
+  .detailsOverlay {
+    width: 25em;
+  }
+}
+.detailsOverlay .closeBtn {
+  position: absolute;
+  top: 0.5em;
+  right: 0.5em;
+  color: #f1c40f;
+  cursor: pointer;
+}
+.detailsOverlay .advice {
+  color: #e74c3c;
+  font-weight: bold;
+}
+.detailsOverlay .trace {
+  word-break: break-all;
+}
+.table > div > .duration,
+.table > div > .startTime {
+  text-align: center;
+  white-space: nowrap;
+}
+.table > div > .startTime.domComplete {
+  background: #ede3ff;
+}
+.table > div > .startTime.domContentLoadedEnd {
+  background: #d8f0f0;
+}
+.table > div > .startTime.domContentLoaded {
+  background: #e0ffd1;
+}
+.table > div > .startTime.domInteractive {
+  background: #fffccc;
+}
+.table > div > .startTime.domCreation {
+  background: #ffe0cc;
+}
+.table .icon-warning {
+  color: #e74c3c;
+}
+/**** NgModal popin (have a look inside bower_components) ****/
+.ng-modal {
+  position: fixed;
+  z-index: 9999;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  text-align: left;
+}
+.ng-modal-overlay {
+  position: absolute;
+  z-index: 9999;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  opacity: 0.5;
+}
+.ng-modal-dialog {
+  z-index: 10000;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 50%;
+  transform: translate(-50%, -50%);
+  -webkit-transform: translate(-50%, -50%);
+  background-color: #fff;
+  padding: 10px;
+  border: 3px solid #f1c40f;
+  border-radius: 0.5em;
+  color: #000;
+}
+.ng-modal-dialog-content {
+  overflow-x: hidden;
+  overflow-y: scroll;
+  word-wrap: break-word;
+  max-height: 20em;
+  font-weight: normal;
+  white-space: normal;
+}
+.ng-modal-close {
+  position: absolute;
+  top: 3px;
+  right: 5px;
+  cursor: pointer;
+  font-size: 120%;
+  padding: 5px;
+  display: inline-block;
+}
+.ng-modal-close-x {
+  font-weight: bold;
+  font-family: Arial, sans-serif;
+}
+.ng-modal-title {
+  font-weight: bold;
+  font-size: 1.5em;
+  display: block;
+  margin-bottom: 10px;
+  padding-bottom: 7px;
+  border-bottom: solid 1px #999;
+}

+ 6 - 0
front/src/css/icons.css

@@ -0,0 +1,6 @@
+@font-face {
+  font-family: "fontsmith-icons";
+  src: url("/front/fonts/icons.woff") format("woff");
+  font-weight: normal;
+  font-style: normal;
+}

+ 36 - 0
front/src/css/index.css

@@ -0,0 +1,36 @@
+.promess {
+  padding: 0em 2em 3em;
+  font-weight: normal;
+  font-size: 1.2em;
+}
+.url {
+  width: 50%;
+}
+.launchBtn {
+  background: #e74c3c;
+  color: #fff;
+}
+.launchBtn.disabled {
+  background: #deaca6;
+}
+input[type=submit],
+input.url {
+  padding: 0 0.5em;
+  margin: 0.5em;
+  font-size: 1.2em;
+  height: 2em;
+  border: 0 solid;
+  border-radius: 0.5em;
+  box-shadow: 0.1em 0.2em 0 0 #5e2846;
+  outline: none;
+}
+input[type=submit]:hover {
+  color: #ddd;
+}
+input[type=submit].clicked {
+  color: #ddd;
+  position: relative;
+  left: 0.1em;
+  top: 0.2em;
+  box-shadow: none;
+}

+ 139 - 0
front/src/css/main.css

@@ -0,0 +1,139 @@
+@font-face {
+  font-family: "fontsmith-icons";
+  src: url("/front/fonts/icons.woff") format("woff");
+  font-weight: normal;
+  font-style: normal;
+}
+html {
+  margin: 100px 50px;
+}
+body {
+  margin: 0 auto;
+  max-width: 1280px;
+  background: #9c4274;
+  color: #fff;
+  font-size: 16px;
+  text-align: center;
+}
+body,
+input[type=submit],
+input[type=text],
+input[type=url],
+input[type=number],
+button {
+  font-family: 'Century Gothic', helvetica, arial, sans-serif;
+}
+input[type=submit] {
+  cursor: pointer;
+}
+h1 span {
+  display: inline-block;
+  height: 1em;
+  width: 1em;
+  color: #ffa319;
+}
+.footer {
+  padding: 3em;
+  color: #4e1836;
+}
+.footer a {
+  color: inherit;
+}
+.footer .star {
+  font-weight: bold;
+}
+.footer .star span {
+  font-size: 1.2em;
+}
+/* Icons */
+.icon-lab {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-lab:before {
+  content: "\e003";
+}
+.icon-question {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-question:before {
+  content: "\e001";
+}
+.icon-warning {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-warning:before {
+  content: "\e000";
+}
+.icon-back {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-back:before {
+  content: "\e006";
+}
+.icon-summary {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-summary:before {
+  content: "\e002";
+}
+.icon-spaghetti {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-spaghetti:before {
+  content: "\e005";
+}
+.icon-eye {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-eye:before {
+  content: "\e004";
+}

+ 25 - 0
front/src/css/queue.css

@@ -0,0 +1,25 @@
+.status {
+  margin-top: 2em;
+  font-size: 2.5em;
+}
+@-webkit-keyframes rotating {
+  from {
+    -webkit-transform: rotate(0deg);
+  }
+  to {
+    -webkit-transform: rotate(360deg);
+  }
+}
+@keyframes rotating {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+.waiting .icon-lab {
+  -webkit-animation: rotating 3s linear 0s infinite;
+  -webkit-transform: translateZ(0);
+  animation: rotating 3s linear 0s infinite;
+}

BIN
front/src/fonts/icons.woff


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/arrow-left3.svg


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/bars.svg


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/eye.svg


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/lab.svg


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/list.svg


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/question.svg


File diff suppressed because it is too large
+ 0 - 0
front/src/fonts/svg-icons/warning.svg


+ 10 - 2
front/src/js/app.js

@@ -2,7 +2,11 @@ var yltApp = angular.module('YellowLabTools', [
     'ngRoute',
     'indexCtrl',
     'aboutCtrl',
-    'dashboardCtrl'
+    'dashboardCtrl',
+    'queueCtrl',
+    'runsFactory',
+    'resultsFactory',
+    'gradeDirective'
 ]);
 
 yltApp.config(['$routeProvider', '$locationProvider',
@@ -12,11 +16,15 @@ yltApp.config(['$routeProvider', '$locationProvider',
                 templateUrl: 'front/views/index.html',
                 controller: 'IndexCtrl'
             }).
+            when('/queue/:runId', {
+                templateUrl: 'front/views/queue.html',
+                controller: 'QueueCtrl'
+            }).
             when('/about', {
                 templateUrl: 'front/views/about.html',
                 controller: 'AboutCtrl'
             }).
-            when('/results/:runId', {
+            when('/result/:runId', {
                 templateUrl: 'front/views/dashboard.html',
                 controller: 'DashboardCtrl'
             }).

+ 13 - 2
front/src/js/controllers/indexCtrl.js

@@ -1,5 +1,16 @@
 var indexCtrl = angular.module('indexCtrl', []);
 
-indexCtrl.controller('IndexCtrl', ['$scope', function($scope) {
-    $scope.toto = "Achraf";
+indexCtrl.controller('IndexCtrl', ['$scope', '$location', 'Runs', function($scope, $location, Runs) {
+    $scope.launchTest = function() {
+        if ($scope.url) {
+            Runs.save({
+                url: $scope.url,
+                waitForResponse: false
+            }, function(data) {
+                console.log(data);
+                $location.path('/queue/' + data.runId);
+            });
+            
+        }
+    };
 }]);

+ 23 - 0
front/src/js/controllers/queueCtrl.js

@@ -0,0 +1,23 @@
+var queueCtrl = angular.module('queueCtrl', ['runsFactory']);
+
+queueCtrl.controller('QueueCtrl', ['$scope', '$routeParams', '$location', 'Runs', function($scope, $routeParams, $location, Runs) {
+    $scope.runId = $routeParams.runId;
+    
+    function getRunStatus () {
+        Runs.get({runId: $scope.runId}, function(data) {
+            $scope.status = data.status;
+            if (data.status.statusCode === 'running' || data.status.statusCode === 'awaiting') {
+                // Retrying in 2 seconds
+                setTimeout(getRunStatus, 2000);
+            } else if (data.status.statusCode === 'complete') {
+                $location.path('/result/' + $scope.runId);
+            } else {
+                // Handled by the view
+            }
+        });
+    }
+    
+    getRunStatus();
+}]);
+
+    

+ 34 - 0
front/src/js/directives/gradeDirective.js

@@ -0,0 +1,34 @@
+var gradeDirective = angular.module('gradeDirective', []);
+
+gradeDirective.directive('grade', function() {
+ 
+    return {
+        restrict: 'E',
+        scope: {
+            score: '=score'
+        },
+        template: '<div ng-class="getGrade(score)">{{getGrade(score)}}</div>',
+        replace: true,
+        controller : function($scope) {
+            $scope.getGrade = function(score) {
+                console.log(score);
+                if (score >= 85) {
+                    return 'A';
+                }
+                if (score >= 65) {
+                    return 'B';
+                }
+                if (score >= 45) {
+                    return 'C';
+                }
+                if (score >= 30) {
+                    return 'D';
+                }
+                if (score >= 15) {
+                    return 'E';
+                }
+                return 'F';
+            };
+        }
+    };
+});

+ 7 - 0
front/src/js/models/runsFactory.js

@@ -0,0 +1,7 @@
+var runsFactory = angular.module('runsFactory', ['ngResource']);
+
+runsFactory.factory('Runs', ['$resource', function($resource) {
+    return $resource('/api/runs/:runId', {
+    
+    });
+}]);

+ 507 - 0
front/src/less/dashboard.less

@@ -0,0 +1,507 @@
+
+/* Timeline colors, related to Window Performances */
+@domCreationColor: #FF6600;
+@domCreationBg: #FFE0CC;
+@domContentLoadedColor: #A7E846;
+@domContentLoadedBg: #E0FFD1;
+@domContentLoadedEndColor: #7ECCCC;
+@domContentLoadedEndBg: #D8F0F0;
+@domCompleteColor: #C2A3FF;
+@domCompleteBg: #EDE3FF;
+@domInteractiveColor: #FFE433;
+@domInteractiveBg: #FFFCCC;
+
+
+.resultsMenu {
+    margin-top: 2em;
+}
+
+.resultsMenu .menuItem {
+    display: inline-block;
+    margin: 1em;
+    width: 8em;
+    height: 7em;
+    color: #fff;
+    border: 3px solid #fff;
+    border-radius: 0.5em;
+    cursor: pointer;
+    text-decoration: none;
+}
+.resultsMenu .back {
+    color: #5e2846;
+    border-color: #5e2846;
+}
+.resultsMenu .menuItem div {
+    padding-top: 0.5em;
+    font-size: 3em;
+}
+.resultsMenu .active, .resultsMenu .menuItem.active:hover {
+    color: #ffa319;
+    border-color: #ffa319;
+}
+.resultsMenu .menuItem:hover {
+    color: #ffa319;
+}
+
+.resultsMenu span {
+    position: relative;
+    top: 0.5em;
+}
+
+.testedUrl {
+    color: inherit;
+}
+
+h4 {
+    margin-bottom: 0.5em;
+}
+
+.summary, .metrics, .execution {
+    margin-top: 2em;
+    padding: 1em;
+    background: #fff;
+    color: #000;
+    border-radius: 0.5em;
+    text-align: left;
+}
+
+.notations {
+    display: table;
+    width: 90%;
+    margin: 0 10%;
+    border-spacing: 1em;
+}
+.notations > div {
+    display: table-row;
+}
+.notations > div > div {
+    display: table-cell;
+    height: 2.5em;
+    vertical-align: middle;
+}
+.notations .notation {
+    font-weight: bold;
+    text-align: center;
+}
+.notations .criteria {
+    font-weight: normal;
+}
+.A, .B, .C, .D, .E, .F, .NA {
+    .notations & {
+        width: 2.5em;
+        font-size: 2em;
+        text-align: center;
+        border-radius: 0.5em;
+        font-weight: bold;
+    }
+    .notations .grade & {
+        width: 1em;
+        height: 1em;
+        font-size: 1em;
+        color: transparent;
+        position: relative;
+        top: 0.15em;
+    }
+}
+.notations .A {
+    /* green */
+    background: #00DB61;
+}
+.notations .B {
+    /* green */
+    background: #CAD600;
+}
+.notations .C {
+    /* yellow */
+    background: #FFD119;
+}
+.notations .D {
+    /* orange */
+    background: #FFA319;
+}
+.notations .E {
+    /* red */
+    background: #FF6600;
+}
+.notations .F {
+    /* red */
+    background: #FF1919;
+}
+.notations .NA {
+    /* Non applicable */
+    background: #CCC;
+}
+.notations .icon-eye {
+    color: #9c4274;
+    cursor: pointer;
+}
+
+.notations .criteria .table {
+    width: 75%;
+}
+.notations .criteria .label {
+    width: 70%;
+}
+.notations .criteria .result {
+    width: 20%;
+    font-weight: bold;
+    white-space: nowrap;
+    text-align: center;
+}
+.notations .warning .label, .notations .warning .result {
+    color: #FF1919;
+}
+.notations .criteria .info {
+    width: 10%;
+    text-align: center;
+}
+.notations .criteria .icon-question {
+    color: #f1c40f;
+    cursor: pointer;
+}
+
+
+.timeline {
+    margin: 2em 0 5em;
+}
+.timeline .chart {
+    position: relative;
+    width: 100%;
+    border-bottom: 1px solid #000;
+}
+.timeline .startTime, .timeline .endTime {
+    position: absolute;
+    bottom: 0.5em;
+    font-size: 0.8em;
+}
+.timeline .startTime {
+    left: 0em;
+}
+.timeline .endTime {
+    right: 0em;
+}
+.timeline .chartPoints {
+    display: table;
+    height: 100px;
+    width: 99%;
+    margin: 0 auto;
+}
+.timeline .interval {
+    display: table-cell;
+    position: relative;
+    height: 100px;
+    width: 0.5%;
+}
+.timeline .interval .color {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+}
+.timeline div.interval:hover {
+    background: #9C4274;
+}
+.timeline .interval:hover .color {
+    background: #F04DA7;
+}
+.timeline .domComplete.interval {
+    background: @domCompleteBg;
+}
+.timeline .domComplete .color {
+    background: @domCompleteColor;
+}
+.timeline .domContentLoadedEnd.interval {
+    background: @domContentLoadedEndBg;
+}
+.timeline .domContentLoadedEnd .color {
+    background: @domContentLoadedEndColor;
+}
+.timeline .domContentLoaded.interval {
+    background: @domContentLoadedBg;
+}
+.timeline .domContentLoaded .color {
+    background: @domContentLoadedColor;
+}
+.timeline .domInteractive.interval {
+    background: @domInteractiveBg;
+}
+.timeline .domInteractive .color {
+    background: @domInteractiveColor;
+}
+.timeline .domCreation.interval {
+    background: @domCreationBg;
+}
+.timeline .domCreation .color {
+    background: @domCreationColor;
+}
+.timeline .tooltip.detailsOverlay {
+    position: absolute;
+    display: none;
+    width: auto;
+    padding: 0.5em 1em;
+    top: -1.5em;
+    right: 1em;
+}
+.timeline .interval:hover .tooltip {
+    display: block;
+}
+
+.timeline .legend {
+    display: table;
+    width: 100%;
+    margin-top: 1em;
+}
+.timeline .legend > div {
+    display: table-row;
+}
+.timeline .legend > div > div {
+    position: relative;
+    display: table-cell;
+    margin-top: 1em;
+}
+.timeline .titles {
+    font-weight: bold;
+}
+.timeline .titles > div {
+    padding: 0 1em 0 2em;
+}
+.timeline .tips {
+    font-size: 0.7em;
+}
+.timeline .tips > div {
+    padding: 1em 1em 0 0;
+}
+.timeline .legend .color {
+    display: block;
+    position: absolute;
+    left: 0;
+    height: 1.5em;
+    width: 1.5em;
+    border-radius: 0.2em;
+}
+
+
+.metrics h4 {
+    padding-left: 2em;
+}
+
+.metrics .module {
+    padding-left: 4em;
+    padding-top: 0.5em;
+}
+
+.metrics .legend {
+    font-style: italic;
+    color: #aaa;
+}
+
+.metrics .offenders {
+    padding-left: 0em;
+    font-size: 0.8em;
+}
+
+.metrics .offenders div {
+    cursor: pointer;
+}
+
+.metrics .offenders ul {
+    margin-top: 0.5em;
+}
+
+.filters {
+    margin: 1em 0;
+    padding: 0.5em;
+    border: 1px dotted #aaa;
+}
+
+.slowRequestsLimit {
+    width: 3em;
+    font-size: 1em;
+    text-align: right;
+    border: 1px solid #aaa;
+}
+
+input.textFilter {
+    box-shadow: none;
+    font-size: 1em;
+    padding: 0 0.2em;
+    border: 1px solid #aaa;
+    border-radius: none;
+    width: 15em;
+}
+
+.table {
+    display: table;
+    width: 100%;
+    border-spacing: 0.25em;
+}
+
+.table > div {
+    display: table-row;
+}
+
+.table > .headers > div {
+    font-weight: bold;
+    padding: 0.5em 1em;
+}
+
+.table > div > div {
+    padding: 0.1em 1em;
+    background: #f2f2f2;
+    display: table-cell;
+    text-align: left;
+}
+.table > div.jsError > .type, .table > div.jsError > .value {
+    color: #e74c3c;
+    font-weight: bold;
+}
+.table > div.windowPerformance > div, .table > div.windowPerformance > div.startTime {
+    background: #EBD8E2;
+}
+.table > div.showingDetails > div {
+    background: #f1c40f;
+}
+
+.table > div > .index {
+    color: #bbb;
+}
+
+.table > div > .type {
+    white-space:nowrap;
+}
+
+.table > div > .value {
+    width: 70%;
+    word-break: break-all;
+}
+
+.table > div > .details {
+    position: relative;
+}
+.table .details .icon-question {
+    color: #f1c40f;
+    cursor: pointer;
+}
+.table .details .icon-warning {
+    cursor: pointer;
+}
+
+.detailsOverlay {
+    position: absolute;
+    right: 3em;
+    top: -3em;
+    width: 45em;
+    min-height: 1em;
+    padding: 0 1em 1em;
+    background: #fff;
+    border: 2px solid #f1c40f;
+    border-radius: 0.5em;
+    z-index: 1;
+}
+@media screen and (max-width: 1024px) {
+    .detailsOverlay {
+        width: 25em;
+    }
+}
+.detailsOverlay .closeBtn {
+    position: absolute;
+    top: 0.5em;
+    right: 0.5em;
+    color: #f1c40f;
+    cursor: pointer;
+}
+.detailsOverlay .advice {
+    color: #e74c3c;
+    font-weight: bold;
+}
+.detailsOverlay .trace {
+    word-break: break-all;
+}
+
+.table > div > .duration, .table > div > .startTime {
+    text-align: center;
+    white-space:nowrap;
+}
+.table > div > .startTime.domComplete {
+    background: @domCompleteBg;
+}
+.table > div > .startTime.domContentLoadedEnd {
+    background: @domContentLoadedEndBg;
+}
+.table > div > .startTime.domContentLoaded {
+    background: @domContentLoadedBg;
+}
+.table > div > .startTime.domInteractive {
+    background: @domInteractiveBg;
+}
+.table > div > .startTime.domCreation {
+    background: @domCreationBg;
+}
+
+.table .icon-warning {
+    color: #e74c3c;
+}
+
+
+/**** NgModal popin (have a look inside bower_components) ****/
+.ng-modal {
+    position: fixed;
+    z-index: 9999;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    text-align: left;
+}
+.ng-modal-overlay {
+    position: absolute;
+    z-index: 9999;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: #000;
+    opacity: 0.5;
+}
+.ng-modal-dialog {
+    z-index: 10000;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 50%;
+    transform: translate(-50%, -50%);
+    -webkit-transform: translate(-50%, -50%);
+    background-color: #fff;
+    padding: 10px;
+    border: 3px solid #f1c40f;
+    border-radius: 0.5em;
+    color: #000;
+}
+.ng-modal-dialog-content {
+    overflow-x: hidden;
+    overflow-y: scroll;
+    word-wrap: break-word;
+    max-height: 20em;
+    font-weight: normal;
+    white-space: normal;
+}
+.ng-modal-close {
+    position: absolute;
+    top: 3px;
+    right: 5px;
+    cursor: pointer;
+    font-size: 120%;
+    padding: 5px;
+    display: inline-block;
+}
+.ng-modal-close-x {
+    font-weight: bold;
+    font-family: Arial, sans-serif;
+}
+.ng-modal-title {
+    font-weight: bold;
+    font-size: 1.5em;
+    display: block;
+    margin-bottom: 10px;
+    padding-bottom: 7px;
+    border-bottom: solid 1px #999;
+}

+ 56 - 0
front/src/less/icons.less

@@ -0,0 +1,56 @@
+@arrow-left3-font-family: "fontsmith-icons";
+@arrow-left3-value: "\e006";
+@arrow-left3: '"fontsmith-icons"' '"\\e006"';
+@lab-font-family: "fontsmith-icons";
+@lab-value: "\e003";
+@lab: '"fontsmith-icons"' '"\\e003"';
+@warning-font-family: "fontsmith-icons";
+@warning-value: "\e000";
+@warning: '"fontsmith-icons"' '"\\e000"';
+@list-font-family: "fontsmith-icons";
+@list-value: "\e002";
+@list: '"fontsmith-icons"' '"\\e002"';
+@eye-font-family: "fontsmith-icons";
+@eye-value: "\e004";
+@eye: '"fontsmith-icons"' '"\\e004"';
+@bars-font-family: "fontsmith-icons";
+@bars-value: "\e005";
+@bars: '"fontsmith-icons"' '"\\e005"';
+@question-font-family: "fontsmith-icons";
+@question-value: "\e001";
+@question: '"fontsmith-icons"' '"\\e001"';
+
+.icon-font-family(@char) {
+  font-family: ~`@{char}[0]`;
+}
+
+.icon-font() {
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+
+.icon-content(@char) {
+  content: ~`@{char}[1]`;
+}
+
+.icon(@char) {
+  .icon-font-family(@char);
+  .icon-font();
+
+  &:before {
+    .icon-content(@char);
+  }
+}
+
+@font-face {
+  font-family: "fontsmith-icons";
+  src:url("/front/fonts/icons.woff") format("woff"),
+    ;
+  font-weight: normal;
+  font-style: normal;
+}

+ 38 - 0
front/src/less/index.less

@@ -0,0 +1,38 @@
+.promess {
+    padding: 0em 2em 3em;
+    font-weight: normal;
+    font-size: 1.2em;
+}
+
+.url {
+    width: 50%;
+}
+
+.launchBtn {
+    background: #e74c3c;
+    color: #fff;
+    &.disabled {
+        background: #deaca6;
+    }
+}
+
+input[type=submit], input.url {
+    padding: 0 0.5em;
+    margin: 0.5em;
+    font-size: 1.2em;
+    height: 2em;
+    border: 0 solid;
+    border-radius: 0.5em;
+    box-shadow: 0.1em 0.2em 0 0 #5e2846;
+    outline: none;
+}
+input[type=submit]:hover {
+    color: #ddd;
+}
+input[type=submit].clicked {
+    color: #ddd;
+    position: relative;
+    left: 0.1em;
+    top: 0.2em;
+    box-shadow: none;
+}

+ 66 - 0
front/src/less/main.less

@@ -0,0 +1,66 @@
+@import "icons.less";
+
+html {
+    margin: 100px 50px;
+}
+
+body {
+    margin: 0 auto;
+    max-width: 1280px;
+    background: #9c4274;
+    color: #fff;
+    font-size: 16px;
+    text-align: center;
+}
+
+body, input[type=submit], input[type=text], input[type=url], input[type=number], button {
+    font-family: 'Century Gothic', helvetica, arial, sans-serif;
+}
+
+input[type=submit] {
+    cursor: pointer;
+}
+
+h1 span {
+    display: inline-block;
+    height: 1em;
+    width: 1em;
+    color: #ffa319;
+}
+
+.footer {
+    padding: 3em;
+    color: #4e1836;
+    a {
+        color: inherit;
+    }
+    .star {
+        font-weight: bold;
+        span {
+            font-size: 1.2em;
+        }
+    }
+}
+
+/* Icons */
+.icon-lab {
+    .icon(@lab);
+}
+.icon-question {
+    .icon(@question);
+}
+.icon-warning {
+    .icon(@warning);
+}
+.icon-back {
+    .icon(@arrow-left3);
+}
+.icon-summary {
+    .icon(@list);
+}
+.icon-spaghetti {
+    .icon(@bars);
+}
+.icon-eye {
+    .icon(@eye);
+}

+ 19 - 0
front/src/less/queue.less

@@ -0,0 +1,19 @@
+.status {
+    margin-top: 2em;
+    font-size: 2.5em;
+}
+
+@-webkit-keyframes rotating {
+    from { -webkit-transform: rotate(0deg); }
+    to { -webkit-transform: rotate(360deg); }
+}
+@keyframes rotating {
+    from { transform: rotate(0deg); }
+    to { transform: rotate(360deg); }
+}
+
+.waiting .icon-lab {
+    -webkit-animation: rotating 3s linear 0s infinite;
+    -webkit-transform: translateZ(0);
+    animation: rotating 3s linear 0s infinite;
+}

+ 21 - 5
front/src/main.html

@@ -1,7 +1,12 @@
 <html>
 <head>
-	<title>index template</title>
+	<meta charset="utf-8">
+    <title>Yellow Lab Tools</title>
     <base href="/">
+    <link rel="stylesheet" type="text/css" href="/front/css/main.css"> 
+    <link rel="stylesheet" type="text/css" href="/front/css/index.css"> 
+    <link rel="stylesheet" type="text/css" href="/front/css/dashboard.css"> 
+    <link rel="stylesheet" type="text/css" href="/front/css/queue.css"> 
     <script src="/bower_components/angular/angular.min.js"></script>
     <script src="/bower_components/angular-route/angular-route.min.js"></script>
     <script src="/bower_components/angular-resource/angular-resource.min.js"></script>
@@ -9,11 +14,22 @@
     <script src="/front/js/controllers/indexCtrl.js"></script>
     <script src="/front/js/controllers/aboutCtrl.js"></script>
     <script src="/front/js/controllers/dashboardCtrl.js"></script>
+    <script src="/front/js/controllers/queueCtrl.js"></script>
     <script src="/front/js/models/resultsFactory.js"></script>
+    <script src="/front/js/models/runsFactory.js"></script>
+    <script src="/front/js/directives/gradeDirective.js"></script>
+  
 <head>
+
 <body ng-app="YellowLabTools">
-  <div id="header">hello yellowlab tools, header</div>
-  <div id="body" ng-view>hello yellowlab, body</div>
-  <div id="footer">hello yellowlab, footer</div>
+
+    <div id="header"><h1>Yellow Lab <span class="icon-lab"></span> Tools</h1></div>
+    <div id="body" ng-view></div>
+    <div class="footer">
+        <p><b>Yellow Lab Tools</b> is an open source project by <a href="http://www.gaelmetais.com" target="_blank">Gaël Métais</a>, based on <a href="https://github.com/macbre/phantomas" target="_blank">Phantomas</a>.<br>If you like it, <a href="https://github.com/gmetais/YellowLabTools" target="_blank" class="star">give it a <span>&#9733;</span> on GitHub</a>!</p>
+    </div>
 </body>
-</html>
+</html>
+
+<html>
+

+ 8 - 1
front/src/views/about.html

@@ -1 +1,8 @@
-{{about}}
+<html>
+<head></head>
+<body>
+    <div class="footer">
+        <p><b>Yellow Lab Tools</b> is an open source project by <a href="http://www.gaelmetais.com" target="_blank">Gaël Métais</a>, based on <a href="https://github.com/macbre/phantomas" target="_blank">Phantomas</a>.<br>If you like it, <a href="https://github.com/gmetais/YellowLabTools" target="_blank" class="star">give it a <span>&#9733;</span> on GitHub</a>!</p>
+    </div>
+</body>
+</html>

+ 19 - 2
front/src/views/dashboard.html

@@ -1,2 +1,19 @@
-<div>{{dashboard}}</div>
-<div>{{runId}}</div>
+<div class="summary">
+    <h2>Grades</h2>
+
+    <div class="notations">
+        <div ng-repeat="category in result.scoreProfiles.generic.categories">
+            <grade score="category.categoryScore"></grade>
+            <div class="notation">{{category.label}}</div>
+            <div class="criteria">
+                <div class="table">
+                    <div ng-repeat="ruleName in category.rules" ng-init="rule = result.rules[ruleName]" ng-class="{'warning': 1 > 5000}">
+                        <div class="grade"><grade score="rule.score"></grade></div>
+                        <div class="label">{{rule.policy.label}}</div>
+                        <div class="result">{{rule.value}}</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 4 - 1
front/src/views/index.html

@@ -1 +1,4 @@
-this is a html index by {{toto}}
+<h2 class="promess">Free online test to help speeding up <b>heavy</b> web pages</h2>
+<input type="text"ng-model="url" placeholder="http://www.mysite.com" class="url" />
+<input type="submit" value="Launch test" class="launchBtn" ng-click="launchTest()" ng-class="{disabled: !url}" />
+<p>(This is a BETA, your feedback is more than welcome)</p>

+ 8 - 0
front/src/views/queue.html

@@ -0,0 +1,8 @@
+<div class="status" ng-if="status.statusCode == 'failed'">Test failed</div>
+<div class="status" ng-if="status.statusCode == 'awaiting'">
+    <ng-pluralize count="status.position" when="{
+                     'one': 'Waiting behind 1 other test',
+                     'other': 'Waiting behind {} other tests'}">
+    </ng-pluralize></div>
+<div class="status" ng-if="status.statusCode == 'running'">Test is running...</div>
+<div class="status" ng-if="status.statusCode == 'complete'">Test complete</div>

+ 1 - 1
lib/metadata/policies.json

@@ -313,7 +313,7 @@
     },
     "otherCount": {
         "tool": "phantomas",
-        "label": "Other type of requests",
+        "label": "Other types of requests",
         "message": "<p>They can be Flash, XML, music or any unknown format.</p>",
         "isOkThreshold": 5,
         "isBadThreshold": 20,

+ 1 - 1
lib/metadata/scoreProfileGeneric.json

@@ -69,7 +69,7 @@
         "requests": {
             "label": "Requests number",
             "policies": {
-                "request": 5,
+                "requests": 5,
                 "htmlCount": 0,
                 "jsCount": 1,
                 "cssCount": 1,

+ 5 - 9
lib/server/controllers/frontController.js

@@ -3,17 +3,13 @@ var express = require('express');
 
 var FrontController = function(app) {
     'use strict';
- 
-    app.get('/', function(req, res) {
-        res.sendFile(path.join(__dirname, '../../../front/src/main.html')); 
-    });
     
-    app.get('/about', function(req, res) {
-        res.sendFile(path.join(__dirname, '../../../front/src/main.html')); 
-    });
+    var routes = ['/', '/about', '/result/:runId', '/queue/:runId'];
     
-    app.get('/results/:runId', function(req, res) {
-        res.sendFile(path.join(__dirname, '../../../front/src/main.html')); 
+    routes.forEach(function(route) {
+        app.get(route, function(req, res) {
+            res.sendFile(path.join(__dirname, '../../../front/src/main.html')); 
+        });
     });
     
     app.use('/front', express.static(path.join(__dirname, '../../../front/src')));

+ 672 - 0
src

@@ -0,0 +1,672 @@
+/* Timeline colors, related to Window Performances */
+.resultsMenu {
+  margin-top: 2em;
+}
+.resultsMenu .menuItem {
+  display: inline-block;
+  margin: 1em;
+  width: 8em;
+  height: 7em;
+  color: #fff;
+  border: 3px solid #fff;
+  border-radius: 0.5em;
+  cursor: pointer;
+  text-decoration: none;
+}
+.resultsMenu .back {
+  color: #5e2846;
+  border-color: #5e2846;
+}
+.resultsMenu .menuItem div {
+  padding-top: 0.5em;
+  font-size: 3em;
+}
+.resultsMenu .active,
+.resultsMenu .menuItem.active:hover {
+  color: #ffa319;
+  border-color: #ffa319;
+}
+.resultsMenu .menuItem:hover {
+  color: #ffa319;
+}
+.resultsMenu span {
+  position: relative;
+  top: 0.5em;
+}
+.testedUrl {
+  color: inherit;
+}
+h4 {
+  margin-bottom: 0.5em;
+}
+.summary,
+.metrics,
+.execution {
+  margin-top: 2em;
+  padding: 1em;
+  background: #fff;
+  color: #000;
+  border-radius: 0.5em;
+  text-align: left;
+}
+.notations {
+  display: table;
+  width: 90%;
+  margin: 0 10%;
+  border-spacing: 1em;
+}
+.notations > div {
+  display: table-row;
+}
+.notations > div > div {
+  display: table-cell;
+  height: 2.5em;
+  vertical-align: middle;
+}
+.notations .notation {
+  font-weight: bold;
+  text-align: center;
+}
+.notations .criteria {
+  font-weight: normal;
+}
+.notations .A,
+.notations .B,
+.notations .C,
+.notations .D,
+.notations .E,
+.notations .F,
+.notations .NA {
+  width: 2.5em;
+  font-size: 2em;
+  text-align: center;
+  border-radius: 0.5em;
+  font-weight: bold;
+}
+.notations .A {
+  /* green */
+  background: #00DB61;
+}
+.notations .B {
+  /* green */
+  background: #CAD63D;
+}
+.notations .C {
+  /* yellow */
+  background: #FFD119;
+}
+.notations .D {
+  /* orange */
+  background: #FFA319;
+}
+.notations .E {
+  /* red */
+  background: #FF6600;
+}
+.notations .F {
+  /* red */
+  background: #FF1919;
+}
+.notations .NA {
+  /* Non applicable */
+  background: #CCC;
+}
+.notations .icon-eye {
+  color: #9c4274;
+  cursor: pointer;
+}
+.notations .criteria .table {
+  width: 75%;
+}
+.notations .criteria .label {
+  width: 70%;
+}
+.notations .criteria .result {
+  width: 20%;
+  font-weight: bold;
+  white-space: nowrap;
+  text-align: center;
+}
+.notations .warning .label,
+.notations .warning .result {
+  color: #FF1919;
+}
+.notations .criteria .info {
+  width: 10%;
+  text-align: center;
+}
+.notations .criteria .icon-question {
+  color: #f1c40f;
+  cursor: pointer;
+}
+.timeline {
+  margin: 2em 0 5em;
+}
+.timeline .chart {
+  position: relative;
+  width: 100%;
+  border-bottom: 1px solid #000;
+}
+.timeline .startTime,
+.timeline .endTime {
+  position: absolute;
+  bottom: 0.5em;
+  font-size: 0.8em;
+}
+.timeline .startTime {
+  left: 0em;
+}
+.timeline .endTime {
+  right: 0em;
+}
+.timeline .chartPoints {
+  display: table;
+  height: 100px;
+  width: 99%;
+  margin: 0 auto;
+}
+.timeline .interval {
+  display: table-cell;
+  position: relative;
+  height: 100px;
+  width: 0.5%;
+}
+.timeline .interval .color {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+}
+.timeline div.interval:hover {
+  background: #9C4274;
+}
+.timeline .interval:hover .color {
+  background: #F04DA7;
+}
+.timeline .domComplete.interval {
+  background: #ede3ff;
+}
+.timeline .domComplete .color {
+  background: #c2a3ff;
+}
+.timeline .domContentLoadedEnd.interval {
+  background: #d8f0f0;
+}
+.timeline .domContentLoadedEnd .color {
+  background: #7ecccc;
+}
+.timeline .domContentLoaded.interval {
+  background: #e0ffd1;
+}
+.timeline .domContentLoaded .color {
+  background: #a7e846;
+}
+.timeline .domInteractive.interval {
+  background: #fffccc;
+}
+.timeline .domInteractive .color {
+  background: #ffe433;
+}
+.timeline .domCreation.interval {
+  background: #ffe0cc;
+}
+.timeline .domCreation .color {
+  background: #ff6600;
+}
+.timeline .tooltip.detailsOverlay {
+  position: absolute;
+  display: none;
+  width: auto;
+  padding: 0.5em 1em;
+  top: -1.5em;
+  right: 1em;
+}
+.timeline .interval:hover .tooltip {
+  display: block;
+}
+.timeline .legend {
+  display: table;
+  width: 100%;
+  margin-top: 1em;
+}
+.timeline .legend > div {
+  display: table-row;
+}
+.timeline .legend > div > div {
+  position: relative;
+  display: table-cell;
+  margin-top: 1em;
+}
+.timeline .titles {
+  font-weight: bold;
+}
+.timeline .titles > div {
+  padding: 0 1em 0 2em;
+}
+.timeline .tips {
+  font-size: 0.7em;
+}
+.timeline .tips > div {
+  padding: 1em 1em 0 0;
+}
+.timeline .legend .color {
+  display: block;
+  position: absolute;
+  left: 0;
+  height: 1.5em;
+  width: 1.5em;
+  border-radius: 0.2em;
+}
+.metrics h4 {
+  padding-left: 2em;
+}
+.metrics .module {
+  padding-left: 4em;
+  padding-top: 0.5em;
+}
+.metrics .legend {
+  font-style: italic;
+  color: #aaa;
+}
+.metrics .offenders {
+  padding-left: 0em;
+  font-size: 0.8em;
+}
+.metrics .offenders div {
+  cursor: pointer;
+}
+.metrics .offenders ul {
+  margin-top: 0.5em;
+}
+.filters {
+  margin: 1em 0;
+  padding: 0.5em;
+  border: 1px dotted #aaa;
+}
+.slowRequestsLimit {
+  width: 3em;
+  font-size: 1em;
+  text-align: right;
+  border: 1px solid #aaa;
+}
+input.textFilter {
+  box-shadow: none;
+  font-size: 1em;
+  padding: 0 0.2em;
+  border: 1px solid #aaa;
+  border-radius: none;
+  width: 15em;
+}
+.table {
+  display: table;
+  width: 100%;
+  border-spacing: 0.25em;
+}
+.table > div {
+  display: table-row;
+}
+.table > .headers > div {
+  font-weight: bold;
+  padding: 0.5em 1em;
+}
+.table > div > div {
+  padding: 0.1em 1em;
+  background: #f2f2f2;
+  display: table-cell;
+  text-align: left;
+}
+.table > div.jsError > .type,
+.table > div.jsError > .value {
+  color: #e74c3c;
+  font-weight: bold;
+}
+.table > div.windowPerformance > div,
+.table > div.windowPerformance > div.startTime {
+  background: #EBD8E2;
+}
+.table > div.showingDetails > div {
+  background: #f1c40f;
+}
+.table > div > .index {
+  color: #bbb;
+}
+.table > div > .type {
+  white-space: nowrap;
+}
+.table > div > .value {
+  width: 70%;
+  word-break: break-all;
+}
+.table > div > .details {
+  position: relative;
+}
+.table .details .icon-question {
+  color: #f1c40f;
+  cursor: pointer;
+}
+.table .details .icon-warning {
+  cursor: pointer;
+}
+.detailsOverlay {
+  position: absolute;
+  right: 3em;
+  top: -3em;
+  width: 45em;
+  min-height: 1em;
+  padding: 0 1em 1em;
+  background: #fff;
+  border: 2px solid #f1c40f;
+  border-radius: 0.5em;
+  z-index: 1;
+}
+@media screen and (max-width: 1024px) {
+  .detailsOverlay {
+    width: 25em;
+  }
+}
+.detailsOverlay .closeBtn {
+  position: absolute;
+  top: 0.5em;
+  right: 0.5em;
+  color: #f1c40f;
+  cursor: pointer;
+}
+.detailsOverlay .advice {
+  color: #e74c3c;
+  font-weight: bold;
+}
+.detailsOverlay .trace {
+  word-break: break-all;
+}
+.table > div > .duration,
+.table > div > .startTime {
+  text-align: center;
+  white-space: nowrap;
+}
+.table > div > .startTime.domComplete {
+  background: #ede3ff;
+}
+.table > div > .startTime.domContentLoadedEnd {
+  background: #d8f0f0;
+}
+.table > div > .startTime.domContentLoaded {
+  background: #e0ffd1;
+}
+.table > div > .startTime.domInteractive {
+  background: #fffccc;
+}
+.table > div > .startTime.domCreation {
+  background: #ffe0cc;
+}
+.table .icon-warning {
+  color: #e74c3c;
+}
+/**** NgModal popin (have a look inside bower_components) ****/
+.ng-modal {
+  position: fixed;
+  z-index: 9999;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  text-align: left;
+}
+.ng-modal-overlay {
+  position: absolute;
+  z-index: 9999;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  opacity: 0.5;
+}
+.ng-modal-dialog {
+  z-index: 10000;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 50%;
+  transform: translate(-50%, -50%);
+  -webkit-transform: translate(-50%, -50%);
+  background-color: #fff;
+  padding: 10px;
+  border: 3px solid #f1c40f;
+  border-radius: 0.5em;
+  color: #000;
+}
+.ng-modal-dialog-content {
+  overflow-x: hidden;
+  overflow-y: scroll;
+  word-wrap: break-word;
+  max-height: 20em;
+  font-weight: normal;
+  white-space: normal;
+}
+.ng-modal-close {
+  position: absolute;
+  top: 3px;
+  right: 5px;
+  cursor: pointer;
+  font-size: 120%;
+  padding: 5px;
+  display: inline-block;
+}
+.ng-modal-close-x {
+  font-weight: bold;
+  font-family: Arial, sans-serif;
+}
+.ng-modal-title {
+  font-weight: bold;
+  font-size: 1.5em;
+  display: block;
+  margin-bottom: 10px;
+  padding-bottom: 7px;
+  border-bottom: solid 1px #999;
+}
+
+@font-face {
+  font-family: "fontsmith-icons";
+  src: url("/public/fonts/icons.woff") format("woff");
+  font-weight: normal;
+  font-style: normal;
+}
+
+.promess {
+  padding: 0em 2em 3em;
+  font-weight: normal;
+  font-size: 1.2em;
+}
+.url {
+  width: 50%;
+}
+.launchBtn {
+  background: #e74c3c;
+  color: #fff;
+}
+input[type=submit],
+input.url {
+  padding: 0 0.5em;
+  margin: 0.5em;
+  font-size: 1.2em;
+  height: 2em;
+  border: 0 solid;
+  border-radius: 0.5em;
+  box-shadow: 0.1em 0.2em 0 0 #5e2846;
+  outline: none;
+}
+input[type=submit]:hover {
+  color: #ddd;
+}
+input[type=submit].clicked {
+  color: #ddd;
+  position: relative;
+  left: 0.1em;
+  top: 0.2em;
+  box-shadow: none;
+}
+
+#status {
+  margin-top: 2em;
+  font-size: 2.5em;
+}
+@-webkit-keyframes rotating {
+  from {
+    -webkit-transform: rotate(0deg);
+  }
+  to {
+    -webkit-transform: rotate(360deg);
+  }
+}
+@keyframes rotating {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+.waiting .icon-lab {
+  -webkit-animation: rotating 3s linear 0s infinite;
+  -webkit-transform: translateZ(0);
+  animation: rotating 3s linear 0s infinite;
+}
+
+@font-face {
+  font-family: "fontsmith-icons";
+  src: url("/public/fonts/icons.woff") format("woff");
+  font-weight: normal;
+  font-style: normal;
+}
+html {
+  margin: 100px 50px;
+}
+body {
+  margin: 0 auto;
+  max-width: 1280px;
+  background: #9c4274;
+  color: #fff;
+  font-size: 16px;
+  text-align: center;
+}
+body,
+input[type=submit],
+input[type=text],
+input[type=url],
+input[type=number],
+button {
+  font-family: 'Century Gothic', helvetica, arial, sans-serif;
+}
+input[type=submit] {
+  cursor: pointer;
+}
+h1 span {
+  display: inline-block;
+  height: 1em;
+  width: 1em;
+  color: #ffa319;
+}
+.footer {
+  padding: 3em;
+  color: #4e1836;
+}
+.footer a {
+  color: inherit;
+}
+.footer .star {
+  font-weight: bold;
+}
+.footer .star span {
+  font-size: 1.2em;
+}
+/* Icons */
+.icon-lab {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-lab:before {
+  content: "\e003";
+}
+.icon-question {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-question:before {
+  content: "\e001";
+}
+.icon-warning {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-warning:before {
+  content: "\e000";
+}
+.icon-back {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-back:before {
+  content: "\e006";
+}
+.icon-summary {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-summary:before {
+  content: "\e002";
+}
+.icon-spaghetti {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-spaghetti:before {
+  content: "\e005";
+}
+.icon-eye {
+  font-family: "fontsmith-icons";
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+}
+.icon-eye:before {
+  content: "\e004";
+}

Some files were not shown because too many files changed in this diff