diff --git a/.gitignore b/.gitignore
index 6d1ae36..db68fac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,6 @@ node_modules
package-lock.json
.tmp
tmp
-.vagrant
-results/*
coverage
-front/build
package-lock.json
har.json
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a9a1f81..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-language: node_js
-sudo: false
-node_js:
- - "12.18"
- - "14.7"
-env:
- - CXX=g++-4.8
-addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- packages:
- - g++-4.8
-before_install:
- - "npm install -g npm"
- - "npm install -g grunt-cli"
-install:
- - "npm install"
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 392c36f..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,252 +0,0 @@
-module.exports = function(grunt) {
-
- // Load all grunt modules
- require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
-
- // Tell our Express server that Grunt launched it
- process.env.GRUNTED = true;
-
- // Project configuration.
- grunt.initConfig({
- pkg: grunt.file.readJSON('package.json'),
- settings: grunt.file.readJSON('./server_config/settings.json'),
-
- less: {
- all: {
- files: [
- {
- expand: true,
- cwd: 'front/src/less/',
- src: ['**/*.less'],
- dest: 'front/src/css/',
- ext: '.css'
- }
- ]
- }
- },
- jshint: {
- all: [
- '*.js',
- 'app/lib/*.js',
- 'bin/*.js',
- 'lib/**/*.js',
- 'app/nodeControllers/*.js',
- 'app/public/scripts/*.js',
- 'phantomas_custom/**/*.js',
- 'test/api/*.js',
- 'test/core/*.js',
- 'test/fixtures/*.js',
- 'front/src/js/**/*.js'
- ],
- options: {
- esversion: 6
- }
- },
- clean: {
- tmp: {
- src: ['.tmp']
- },
- dev: {
- src: ['front/src/css']
- },
- build: {
- src: ['front/build']
- }
- },
- copy: {
- build: {
- files: [
- {src: ['./front/src/main.html'], dest: './front/build/main.html'},
- {src: ['./front/src/img/favicon.png'], dest: './front/build/img/favicon.png'},
- {src: ['./front/src/img/logo-large.png'], dest: './front/build/img/logo-large.png'},
- ]
- },
- favicons: {
- files: [
- {src: ['./front/src/img/favicon.png'], dest: './front/build/img/favicon.png'},
- {src: ['./front/src/img/favicon-fail.png'], dest: './front/build/img/favicon-fail.png'},
- {src: ['./front/src/img/favicon-success.png'], dest: './front/build/img/favicon-success.png'},
- ]
- }
- },
- mochaTest: {
- test: {
- options: {
- reporter: 'spec',
- },
- src: ['test/core/*.js', 'test/api/*.js']
- },
- 'test-current-work': {
- options: {
- reporter: 'spec',
- },
- src: ['test/core/mediaQueriesCheckerTest.js']
- }
- },
- env: {
- dev: {
- NODE_ENV: 'development'
- },
- built: {
- NODE_ENV: 'production'
- }
- },
- express: {
- dev: {
- options: {
- port: 8383,
- server: './bin/server.js',
- serverreload: true,
- showStack: true
- }
- },
- built: {
- options: {
- port: 8383,
- server: './bin/server.js',
- serverreload: true,
- showStack: true
- }
- },
- test: {
- options: {
- port: 8387,
- server: './bin/server.js',
- showStack: true
- }
- },
- 'test-current-work': {
- options: {
- port: 8387,
- server: './bin/server.js',
- showStack: true
- }
- },
- testSuite: {
- options: {
- port: 8388,
- bases: 'test/www'
- }
- }
- },
- useminPrepare: {
- html: './front/src/main.html',
- options: {
- dest: './front/build',
- root: ['./', './front/src']
- }
- },
- usemin: {
- html: './front/build/main.html',
- css: './front/build/css/*.css',
- options: {
- assetsDirs: ['front/build']
- }
- },
- htmlmin: {
- options: {
- removeComments: true,
- collapseWhitespace: true,
- conservativeCollapse: true
- },
- main: {
- files: [{
- expand: true,
- cwd: './front/build/',
- src: 'main.html',
- flatten: true,
- dest: './front/build'
- }]
- },
- views: {
- files: [{
- expand: true,
- cwd: './front/src/views',
- src: '*.html',
- flatten: true,
- dest: '.tmp/views/'
- }]
- }
- },
- inline_angular_templates: {
- build: {
- options: {
- base: '.tmp',
- method: 'append',
- unescape: {
- '<': '<',
- '>': '>'
- }
- },
- files: {
- './front/build/main.html': ['.tmp/views/*.html']
- }
- }
- },
- filerev: {
- options: {
- algorithm: 'md5',
- length: 8
- },
- assets: {
- src: './front/build/*/*.*'
- }
- }
- });
-
- // Custom task that sets a variable for tests
- grunt.registerTask('test-settings', function() {
- process.env.IS_TEST = true;
- });
-
- grunt.registerTask('build', [
- 'jshint',
- 'clean:build',
- 'copy:build',
- 'less',
- 'useminPrepare',
- 'concat',
- 'uglify',
- 'cssmin',
- 'htmlmin:views',
- 'inline_angular_templates',
- 'filerev',
- 'usemin',
- 'htmlmin:main',
- 'clean:tmp',
- 'copy:favicons'
- ]);
-
- grunt.registerTask('hint', [
- 'jshint'
- ]);
-
- grunt.registerTask('dev', [
- 'env:dev',
- 'express:dev'
- ]);
-
- grunt.registerTask('built', [
- 'env:built',
- 'express:built'
- ]);
-
- grunt.registerTask('test', [
- 'test-settings',
- 'build',
- 'express:testSuite',
- 'express:test',
- 'mochaTest:test',
- 'clean:tmp'
- ]);
-
- grunt.registerTask('test-current-work', [
- 'test-settings',
- 'jshint',
- 'express:testSuite',
- 'express:test-current-work',
- 'mochaTest:test-current-work',
- 'clean:tmp'
- ]);
-
-};
\ No newline at end of file
diff --git a/README.md b/README.md
index 90bef79..626906d 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ By the way, it's free because I am a geek, not businessmen. In return, you can a
If your project is not accessible from outside or if you want to test your localhost, you might want to run your own instance of Yellow Lab Tools.
-The classical way is to clone the project's GitHub repository and run it on Linux of MacOS. The documentation is [here](https://github.com/YellowLabTools/YellowLabTools/wiki/Install-your-private-server).
+The classical way is to clone the YLT server's GitHub repository and run it on Linux or MacOS. The documentation is [here](https://github.com/YellowLabTools/YellowLabTools/wiki/Install-your-private-server).
The new recommended solution is to run Yellow Lab Tools inside a Docker virtual machine. My friend Ousama Ben Younes maintains [this ready-to-use Docker image based on Alpine](https://github.com/ousamabenyounes/docker-yellowlabtools)).
diff --git a/bin/cli.js b/bin/cli.js
old mode 100644
new mode 100755
diff --git a/bin/server.js b/bin/server.js
deleted file mode 100644
index 700f527..0000000
--- a/bin/server.js
+++ /dev/null
@@ -1,49 +0,0 @@
-var express = require('express');
-var app = express();
-var server = require('http').createServer(app);
-var bodyParser = require('body-parser');
-var compress = require('compression');
-var cors = require('cors');
-
-var authMiddleware = require('../lib/server/middlewares/authMiddleware');
-var apiLimitsMiddleware = require('../lib/server/middlewares/apiLimitsMiddleware');
-var wwwRedirectMiddleware = require('../lib/server/middlewares/wwwRedirectMiddleware');
-
-
-// Middlewares
-app.use(compress());
-app.use(bodyParser.json());
-app.use(cors());
-app.use(wwwRedirectMiddleware);
-app.use(authMiddleware);
-app.use(apiLimitsMiddleware);
-app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
-
-
-// EJS HTML engine
-app.engine('.html', require('ejs').__express);
-app.set('view engine', 'ejs');
-
-
-// Initialize the controllers
-var apiController = require('../lib/server/controllers/apiController')(app);
-var frontController = require('../lib/server/controllers/frontController')(app);
-
-
-// Let's start the server!
-if (!process.env.GRUNTED) {
- var settings = require('../server_config/settings.json');
-
- app.locals.baseUrl = settings.baseUrl;
-
- server.listen(settings.serverPort, function() {
- console.log('Listening on port %d', server.address().port);
-
- // For the tests
- if (server.startTests) {
- server.startTests();
- }
- });
-}
-
-module.exports = app;
diff --git a/front/src/css/about.css b/front/src/css/about.css
deleted file mode 100644
index 2e8b784..0000000
--- a/front/src/css/about.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.about {
- margin: 3em auto;
- width: 80%;
-}
-@media (min-width: 640px) {
- .about {
- width: 50%;
- }
-}
-.about p {
- margin: 2em;
-}
-.about a {
- color: #fff;
-}
-.sponsor {
- color: #ffa319;
-}
diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css
deleted file mode 100644
index 3e76ccb..0000000
--- a/front/src/css/dashboard.css
+++ /dev/null
@@ -1,212 +0,0 @@
-.testedUrl {
- color: inherit;
-}
-.summary {
- text-align: center;
-}
-.summary .globalScore {
- margin: 3em auto;
-}
-.summary .globalScore .globalGrade {
- margin: 0.5 auto;
- width: 2.5em;
- height: 2.5em;
- line-height: 2.5em;
- border-radius: 0.5em;
- font-size: 3em;
- vertical-align: middle;
-}
-.summary .globalScore .on100 {
- font-size: 1.2em;
- margin: 0.5em 0 1em;
-}
-.summary .globalScore .screenshotWrapper:hover {
- opacity: 0.75;
-}
-.summary .globalScore .screenshotWrapper:hover:after {
- position: absolute;
- width: 1.25em;
- height: 1.25em;
- top: 0.7em;
- left: 1.55em;
- font-size: 3em;
- color: #FFF;
- background: #000;
- border-radius: 0.2em;
- text-align: center;
- content: "+";
- opacity: 0.85;
-}
-.summary .globalScore .screenshotWrapper.phone:hover:after {
- top: 1.7em;
- left: 0.64em;
-}
-.summary .globalScore .screenshotWrapper.tablet:hover:after {
- top: 1.5em;
- left: 0.9em;
-}
-@media (min-width: 820px) {
- .summary .globalScore {
- width: 65%;
- display: table;
- }
- .summary .globalScore > div {
- display: table-cell;
- width: 50%;
- vertical-align: middle;
- }
-}
-.summary .notations {
- width: 100%;
- display: table;
- margin: 0 0 1.5em;
- border-spacing: 0 1em;
-}
-@media (min-width: 820px) {
- .summary .notations {
- width: 80%;
- margin: 0 10% 1.5em;
- border-spacing: 1em;
- }
-}
-.summary .notations > div {
- display: table-row;
-}
-.summary .notations > div > div {
- vertical-align: middle;
-}
-@media (min-width: 820px) {
- .summary .notations > div > div {
- display: table-cell;
- height: 2.5em;
- }
-}
-.summary .notations .category {
- font-size: 1.2em;
- width: 50%;
- float: left;
- text-align: left;
- margin: 0.5em 0.25em;
-}
-@media (min-width: 820px) {
- .summary .notations .category {
- width: 20%;
- text-align: center;
- float: none;
- }
-}
-.summary .notations .criteria {
- font-weight: normal;
-}
-@media (min-width: 820px) {
- .summary .notations .criteria {
- width: 75%;
- }
-}
-.summary .notations .A.categoryScore,
-.summary .notations .B.categoryScore,
-.summary .notations .C.categoryScore,
-.summary .notations .D.categoryScore,
-.summary .notations .E.categoryScore,
-.summary .notations .F.categoryScore,
-.summary .notations .NA.categoryScore {
- width: 2.5em;
- max-width: 2.5em;
- min-width: 2.5em;
- margin: 0.2em;
- font-size: 1.5em;
- text-align: center;
- border-radius: 0.5em;
- float: right;
-}
-@media (min-width: 820px) {
- .summary .notations .A.categoryScore,
- .summary .notations .B.categoryScore,
- .summary .notations .C.categoryScore,
- .summary .notations .D.categoryScore,
- .summary .notations .E.categoryScore,
- .summary .notations .F.categoryScore,
- .summary .notations .NA.categoryScore {
- float: none;
- font-size: 2em;
- }
-}
-.summary .notations .grade .A,
-.summary .notations .grade .B,
-.summary .notations .grade .C,
-.summary .notations .grade .D,
-.summary .notations .grade .E,
-.summary .notations .grade .F,
-.summary .notations .grade .NA {
- width: 1em;
- height: 1em;
- font-size: 1em;
- color: transparent;
- margin: 0 auto;
- border-radius: 0.5em;
-}
-.summary .notations .criteria .table {
- width: 100%;
-}
-.summary .notations .criteria .table > a {
- text-decoration: none;
- color: inherit;
-}
-.summary .notations .criteria .table > a:hover > div {
- background: #d8ebe0;
- cursor: pointer;
-}
-.summary .notations .criteria .table > a:hover > div.info {
- background: #FFF;
-}
-.summary .notations .criteria .table > a:hover > div.info svg {
- fill: #d8ebe0;
-}
-.summary .notations .criteria .grade {
- width: 10%;
- padding-left: 0.5em;
- padding-right: 0.5em;
- vertical-align: middle;
-}
-.summary .notations .criteria .label {
- width: 70%;
-}
-.summary .notations .criteria .result {
- width: 18%;
- white-space: nowrap;
- text-align: center;
- vertical-align: middle;
-}
-.summary .notations .warning .label,
-.summary .notations .warning .result {
- color: #FF1919;
-}
-.summary .notations .icon-warning svg {
- fill: #FF1919;
- margin: -2px 0;
-}
-.summary .notations .criteria .info {
- display: none;
-}
-@media (min-width: 820px) {
- .summary .notations .criteria .info {
- display: table-cell;
- width: 2%;
- text-align: center;
- vertical-align: middle;
- background: #FFF;
- padding-left: 0.1em;
- padding-right: 0.1em;
- }
-}
-.summary .notations .criteria .info svg {
- fill: transparent;
-}
-.summary .sponsor {
- font-size: 0.9em;
- margin-bottom: 4em;
- color: #ffa319;
-}
-.summary .sponsor a {
- color: inherit;
-}
diff --git a/front/src/css/index.css b/front/src/css/index.css
deleted file mode 100644
index fabcadf..0000000
--- a/front/src/css/index.css
+++ /dev/null
@@ -1,196 +0,0 @@
-.promess {
- padding: 0em 2em;
- margin-bottom: 0.5em;
- font-weight: normal;
- font-size: 1.2em;
-}
-.price {
- padding: 0em 2em 3em;
- margin-top: 0em;
- font-size: 0.9em;
-}
-.url {
- width: 50%;
-}
-.launchBtn {
- background: #ffa319;
- color: #fff;
-}
-.launchBtn:focus {
- background: #e74c3c;
-}
-.launchBtn.disabled {
- background: #f1bd70;
-}
-.launchBtn.disabled:focus {
- color: #ddd;
-}
-.settings {
- width: 50%;
- margin: 0 auto;
-}
-.settings input,
-.settings select {
- font-size: 1em;
-}
-.settings input[type=text],
-.settings input[type=password],
-.settings textarea {
- width: 100%;
- min-width: 4em;
-}
-.device {
- margin-top: 3em;
-}
-.device .item {
- display: inline-block;
- margin: 1em 0.75em;
- width: 5.5em;
- height: 5.5em;
- color: #FFF;
- border: 1px solid #FFF;
- padding: 1px;
- border-radius: 0.5em;
- cursor: pointer;
- text-decoration: none;
- font-size: 0.8em;
-}
-.device .item > svg {
- display: block;
- margin: 0.6em auto 0.3em;
- fill: #fff;
-}
-.device .item.active {
- color: #ffa319;
- border: 2px solid #ffa319;
- padding: 0;
-}
-.device .item.active > svg {
- fill: #ffa319;
-}
-.device .item:hover {
- color: #ffa319;
-}
-.device .item:hover > svg {
- fill: #ffa319;
-}
-.settingsTooltip {
- position: relative;
-}
-.settingsTooltip svg {
- vertical-align: text-top;
-}
-.settingsTooltip div {
- display: none;
- position: absolute;
- padding: 0.5em;
- width: 25em;
- background: #FFF;
- color: #000;
- font-size: 0.8em;
- border-radius: 1em;
- border: 2px solid #ffa319;
- white-space: normal;
- word-break: break-all;
- word-break: break-word;
- z-index: 2;
-}
-.settingsTooltip:hover div {
- display: block;
-}
-.showAdvanced {
- display: inline-block;
- margin-top: 2em;
- color: #FFF;
- text-decoration: none;
- font-size: 0.9em;
-}
-.showAdvanced:hover {
- color: #ffa319;
-}
-.currentSettings {
- font-size: 0.9em;
-}
-.currentSettings span {
- color: #ffa319;
-}
-.currentSettings span:after {
- color: #FFF;
- content: ",";
-}
-.currentSettings span:last-child:after {
- content: "";
-}
-.advanced {
- margin: 1em 0 0;
- display: table;
- width: 100%;
- text-align: left;
- border-spacing: 0.75em;
-}
-.advanced > div {
- display: table-row;
-}
-.advanced > div > div {
- display: table-cell;
- width: 75%;
-}
-.advanced > div > div.label {
- width: 25%;
- white-space: nowrap;
- vertical-align: middle;
-}
-.advanced .subTable {
- display: table;
- border-spacing: 0;
- width: 100%;
-}
-.advanced .subTable > div {
- display: table-row;
-}
-.advanced .subTable > div > div {
- display: table-cell;
- padding: 0 0 0.75em;
-}
-.features {
- display: table;
- width: 50%;
- margin: 6em auto 0;
- font-size: 0.9em;
- color: #8abfaf;
-}
-@media (min-width: 640px) {
- .features > div {
- width: 33.3%;
- display: table-cell;
- padding: 0 1.5em;
- }
-}
-.features h3 {
- font-size: 1.5em;
- font-weight: normal;
- 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;
- 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;
-}
-.homeSponsor {
- margin-top: 3em;
-}
diff --git a/front/src/css/main.css b/front/src/css/main.css
deleted file mode 100644
index f8d78f4..0000000
--- a/front/src/css/main.css
+++ /dev/null
@@ -1,280 +0,0 @@
-html {
- margin: 35px 5px;
-}
-@media (min-width: 640px) {
- html {
- margin: 100px 50px;
- }
-}
-body {
- margin: 0 auto;
- max-width: 1280px;
- background: #212240;
- 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 {
- font-weight: 200;
-}
-.resultsMenu {
- margin-top: 2em;
-}
-.resultsMenu .menuItem {
- font-size: 0.8em;
- display: inline-block;
- width: 7em;
- height: 7em;
- color: #fff;
- cursor: pointer;
- text-decoration: none;
-}
-@media (min-width: 640px) {
- .resultsMenu .menuItem {
- font-size: 1em;
- margin: 1em;
- width: 8em;
- border: 2px solid #fff;
- border-radius: 0.5em;
- }
-}
-.resultsMenu .menuItem svg {
- fill: #fff;
-}
-.resultsMenu .menuItem.back,
-.resultsMenu .menuItem.restart {
- color: #fff;
- border-color: #fff;
-}
-.resultsMenu .menuItem div {
- padding-top: 0.5em;
- font-size: 3em;
-}
-.resultsMenu svg {
- display: block;
- margin: 1.2em auto 0.2em;
-}
-.resultsMenu .active,
-.resultsMenu .menuItem.active:hover {
- color: #ffa319;
- border-color: #ffa319;
-}
-.resultsMenu .active svg,
-.resultsMenu .menuItem.active:hover svg {
- fill: #ffa319;
-}
-.resultsMenu .menuItem:hover {
- color: #ffa319;
-}
-.resultsMenu .menuItem:hover svg {
- fill: #ffa319;
-}
-.resultsMenu span {
- position: relative;
- top: 0.5em;
-}
-/* Grade colors */
-.A {
- /* green */
- background: #0C4;
-}
-.B {
- /* green */
- background: #CD0;
-}
-.C {
- /* yellow */
- background: #FD2;
-}
-.D {
- /* orange */
- background: #FA2;
-}
-.E {
- /* red */
- background: #F60;
-}
-.F {
- /* red */
- background: #F22;
-}
-.NA {
- /* Non applicable */
- background: #CCC;
-}
-.board {
- margin-top: 2em;
- padding: 1em;
- background: #fff;
- color: #000;
- border-radius: 0.5em;
- text-align: left;
-}
-.backToDashboard {
- text-align: center;
-}
-.backToDashboard a {
- font-size: 0.9em;
- display: block;
- margin-top: 4em;
- color: black;
-}
-a.linkButton {
- font-size: 1em;
- padding: 0.3em 0.5em;
- margin: 0.5em;
- line-height: 2em;
- border: 0 solid;
- border-radius: 0.5em;
- box-shadow: 0.1em 0.2em 0 0 #5e2846;
- background: #e74c3c;
- color: #fff;
- text-decoration: none;
-}
-.screenshotWrapper {
- display: inline-block;
- position: relative;
- background: #000;
-}
-.screenshotWrapper > div {
- position: relative;
- overflow: hidden;
-}
-.screenshotWrapper .screenshotImage {
- width: 100%;
-}
-.screenshotWrapper .screenshotError {
- color: #fff;
-}
-.screenshotWrapper.desktop,
-.screenshotWrapper.desktop-hd {
- border: 0.2em solid #AAA;
- padding: 0.5em;
- border-top-left-radius: 0.4em;
- border-top-right-radius: 0.4em;
-}
-.screenshotWrapper.desktop:before,
-.screenshotWrapper.desktop-hd:before {
- position: absolute;
- width: 15em;
- height: 0.6em;
- bottom: -0.75em;
- left: -1em;
- background: #CCC;
- border-bottom-left-radius: 0.2em;
- border-bottom-right-radius: 0.2em;
- content: " ";
-}
-.screenshotWrapper.desktop:after,
-.screenshotWrapper.desktop-hd:after {
- position: absolute;
- width: 0.4em;
- height: 0.2em;
- bottom: -0.55em;
- left: 12.5em;
- background: lime;
- content: " ";
-}
-.screenshotWrapper.desktop > div,
-.screenshotWrapper.desktop-hd > div {
- width: 12em;
- height: 7.5em;
-}
-.screenshotWrapper.phone {
- border: 0.07em solid #CCC;
- padding: 1em 0.3em 1.5em;
- border-radius: 0.6em;
-}
-.screenshotWrapper.phone:before {
- position: absolute;
- width: 0.8em;
- height: 0.8em;
- bottom: 0.3em;
- left: 3.3em;
- border: 0.07em solid #CCC;
- border-radius: 0.5em;
- content: " ";
-}
-.screenshotWrapper.phone:after {
- position: absolute;
- width: 1em;
- height: 0.1em;
- bottom: 13.9em;
- left: 3.2em;
- background: #555;
- border-radius: 0.05em;
- content: " ";
-}
-.screenshotWrapper.phone > div {
- width: 6.75em;
- height: 12em;
-}
-.screenshotWrapper.tablet {
- border: 0.07em solid #CCC;
- padding: 0.8em 0.5em 0.9em;
- border-radius: 0.6em;
-}
-.screenshotWrapper.tablet:before {
- position: absolute;
- width: 0.5em;
- height: 0.5em;
- bottom: 0.15em;
- left: 4.35em;
- border: 0.07em solid #CCC;
- border-radius: 0.4em;
- content: " ";
-}
-.screenshotWrapper.tablet > div {
- width: 8em;
- height: 12.8em;
-}
-.table {
- display: table;
- width: 100%;
- border-spacing: 0.25em;
-}
-.table > div,
-.table > a {
- display: table-row;
-}
-.table > .headers > div {
- font-weight: bold;
- padding: 0.5em 1em;
-}
-.table > div > div,
-.table > a > div {
- padding: 0.1em 1em;
- background: #f2f2f2;
- display: table-cell;
- text-align: left;
-}
-.footer {
- padding: 3em;
- color: #fff;
-}
-.footer a {
- color: inherit;
-}
-.footer .version {
- font-size: 0.7em;
-}
-.footer .github {
- margin: 1em 0 0 0.5em;
-}
-.footer .sponsor {
- font-size: 0.9em;
-}
-.homeSponsor {
- color: #ffa319;
-}
diff --git a/front/src/css/queue.css b/front/src/css/queue.css
deleted file mode 100644
index e6b9fa2..0000000
--- a/front/src/css/queue.css
+++ /dev/null
@@ -1,21 +0,0 @@
-.status {
- margin-top: 2em;
- font-size: 2.5em;
-}
-.statusSubMessage {
- font-size: 0.8em;
- margin-bottom: 6em;
-}
-.progressBarEmpty {
- width: 90%;
- max-width: 300px;
- margin: 1em auto;
- padding: 0.05em;
- border: 1px solid #ffa319;
-}
-.progressBarFilled {
- width: 5%;
- height: 0.5em;
- background: #ffa319;
- transition: width 3s ease-out;
-}
diff --git a/front/src/css/rule.css b/front/src/css/rule.css
deleted file mode 100644
index 24e789e..0000000
--- a/front/src/css/rule.css
+++ /dev/null
@@ -1,277 +0,0 @@
-.rule.board {
- text-align: center;
-}
-.rule .ruleTable {
- border-spacing: 1em;
- width: 90%;
- margin: 2em auto;
- background: #f2f2f2;
- border: 1px dashed #666;
- border-radius: 0.5em;
-}
-@media (min-width: 820px) {
- .rule .ruleTable {
- display: table;
- }
- .rule .ruleTable > div {
- display: table-cell;
- vertical-align: middle;
- }
- .rule .ruleTable .left {
- width: 33%;
- }
- .rule .ruleTable .right {
- width: 67%;
- }
-}
-.rule .score {
- font-size: 2.5em;
- line-height: 2em;
- height: 2em;
- width: 2em;
- border-radius: 0.5em;
- margin: 0 auto 0.25em;
-}
-.rule h3 {
- margin-bottom: 0em;
-}
-.rule .okThreshold {
- font-style: italic;
- font-size: 0.9em;
-}
-.rule .message {
- width: 80%;
- margin: 1.5em auto;
-}
-.rule .message p {
- margin: 0.5em;
-}
-.rule .message ul {
- list-style-type: none;
- padding-left: 0;
-}
-.rule .message li:before {
- content: '\25e6';
- margin-right: 0.3em;
- font-size: 1.2em;
- position: relative;
- top: 0.1em;
-}
-.rule .warning {
- width: 90%;
- margin: -1em auto 2em;
- background: #FEE;
- border: 1px dashed #e74c3c;
- color: #e74c3c;
- border-radius: 0.5em;
-}
-.rule .offendersTable {
- display: table;
- border-spacing: 0 0.25em;
- margin: 0 auto;
- min-width: 10%;
- font-size: 0.875em;
-}
-@media (min-width: 820px) {
- .rule .offendersTable {
- max-width: 90%;
- font-size: 1em;
- }
-}
-.rule .offendersTable > div {
- display: table-row;
-}
-.rule .offendersTable > div > div {
- display: table-cell;
- background: #f2f2f2;
- padding: 0 0.25em;
- word-wrap: break-word;
- word-break: break-all;
-}
-.rule .offendersTable > div > div:hover {
- background: #d8ebe0;
-}
-.rule .notFound {
- font-size: 1em;
-}
-.rule .notFound h2 {
- font-size: 3em;
- margin-bottom: 1em;
-}
-.rule .startTime {
- display: none;
-}
-.offendersTable .offenderButton,
-.value .offenderButton {
- display: inline-block;
- position: relative;
- background: #efe;
- padding: 0 0.5em;
- margin: 0.2em 0;
- border-radius: 0.4em;
- z-index: 1;
- cursor: pointer;
-}
-.offendersTable .offenderButton.opens,
-.value .offenderButton.opens {
- padding-right: 0.75em;
-}
-.offendersTable .offenderButton.opens:after,
-.value .offenderButton.opens:after {
- position: relative;
- left: 0.5em;
- content: '\25BC';
- font-size: 0.8em;
-}
-.offendersTable .offenderButton > div,
-.value .offenderButton > div {
- display: none;
- position: absolute;
- right: 0;
- min-width: 100%;
- background: inherit;
- border-bottom-left-radius: 0.4em;
- border-bottom-right-radius: 0.4em;
- border-top: 1px solid #999;
- z-index: 2;
-}
-.offendersTable .offenderButton .domTree,
-.value .offenderButton .domTree {
- text-align: left;
- white-space: nowrap;
-}
-.offendersTable .offenderButton .domTree > div,
-.value .offenderButton .domTree > div {
- margin: 0.5em;
-}
-.offendersTable .offenderButton .domTree > div div,
-.value .offenderButton .domTree > div div {
- margin-left: 1em;
-}
-.offendersTable .offenderButton .backtrace,
-.value .offenderButton .backtrace,
-.offendersTable .offenderButton .cssFileAndLine,
-.value .offenderButton .cssFileAndLine {
- white-space: nowrap;
- padding: 0.5em;
-}
-.offendersTable .offenderButton.opens:hover,
-.value .offenderButton.opens:hover {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- background: #ffe0cc;
- z-index: 2;
-}
-.offendersTable .offenderButton.opens:hover > div,
-.value .offenderButton.opens:hover > div {
- display: block;
- background: #ffe0cc;
-}
-.offendersTable .smallerOffenders,
-.value .smallerOffenders {
- font-size: 0.9em;
-}
-.offendersHtml {
- display: inline-block;
-}
-.domTree div {
- text-align: left;
- margin-left: 1em;
-}
-.domTree div span:only-child {
- font-weight: bold;
-}
-.domTree div span:only-child span {
- font-style: italic;
- font-weight: normal;
-}
-.checker {
- /* Checkerboard background */
- background-color: #ddd;
- background-image: linear-gradient(45deg, #AAA 25%, transparent 25%, transparent 75%, #AAA 75%, #AAA), linear-gradient(45deg, #AAA 25%, transparent 25%, transparent 75%, #AAA 75%, #AAA);
- background-size: 1em 1em;
- background-position: 0 0, 0.5em 0.5em;
-}
-.colorPalette {
- width: 30em;
- border: 2px solid #000;
- text-align: left;
-}
-.colorPalette > div {
- display: inline-block;
- height: 2em;
- position: relative;
-}
-.colorPalette > div div {
- display: none;
- position: absolute;
- left: 100%;
- top: 100%;
- background: #FFF;
- padding: 0.5em;
- border: 2px solid #f1c40f;
- border-radius: 0.5em;
- white-space: nowrap;
- z-index: 3;
- font-weight: bold;
-}
-.colorPalette > div:hover div {
- display: block;
-}
-.colorPalette > div:hover:after {
- content: " ";
- position: absolute;
- left: -0.2em;
- top: -0.2em;
- width: 100%;
- height: 100%;
- z-index: 2;
- border: 0.2em solid #f1c40f;
-}
-.similarColors {
- margin: 1em;
- width: 20em;
- height: 6em;
-}
-.similarColors > div {
- display: inline-block;
- width: 10em;
- height: 3.5em;
- padding-top: 2.5em;
-}
-.totalWeightPie {
- max-width: 20em;
- margin: 2em auto 4em;
-}
-.totalWeightPie canvas {
- max-width: inherit;
-}
-.offenderProblem {
- font-weight: bold;
- color: #e74c3c;
-}
-.imageOffenders {
- display: table;
- border-spacing: 3em;
- width: 90%;
-}
-.imageOffenders > div {
- display: table-row;
-}
-.imageOffenders > div > div {
- display: table-cell;
- vertical-align: middle;
-}
-.imageOffenders img {
- max-height: 10em;
- max-width: 40em;
- border: 1px solid #000;
- margin-top: 0.5em;
-}
-.smallPreview {
- display: block;
- max-height: 6em;
- max-width: 16em;
- border: 1px solid #000;
- margin: 1em auto 0.2em;
-}
diff --git a/front/src/css/screenshot.css b/front/src/css/screenshot.css
deleted file mode 100644
index 0df36d4..0000000
--- a/front/src/css/screenshot.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.screenshot.board {
- text-align: center;
-}
-.screenshot .screenshotWrapper {
- font-size: 1.2em;
- margin-bottom: 0.5em;
-}
-@media (min-width: 420px) {
- .screenshot .screenshotWrapper {
- font-size: 1.6em;
- }
-}
-@media (min-width: 640px) {
- .screenshot .screenshotWrapper {
- font-size: 2.08333333em;
- }
-}
diff --git a/front/src/img/favicon-fail.png b/front/src/img/favicon-fail.png
deleted file mode 100644
index 8192c94..0000000
Binary files a/front/src/img/favicon-fail.png and /dev/null differ
diff --git a/front/src/img/favicon-success.png b/front/src/img/favicon-success.png
deleted file mode 100644
index 09f498f..0000000
Binary files a/front/src/img/favicon-success.png and /dev/null differ
diff --git a/front/src/img/favicon.png b/front/src/img/favicon.png
deleted file mode 100644
index 23ff5bc..0000000
Binary files a/front/src/img/favicon.png and /dev/null differ
diff --git a/front/src/img/logo-large.png b/front/src/img/logo-large.png
deleted file mode 100644
index 88d1971..0000000
Binary files a/front/src/img/logo-large.png and /dev/null differ
diff --git a/front/src/js/app.js b/front/src/js/app.js
deleted file mode 100644
index 4b04f1e..0000000
--- a/front/src/js/app.js
+++ /dev/null
@@ -1,85 +0,0 @@
-var yltApp = angular.module('YellowLabTools', [
- 'ngRoute',
- 'ngSanitize',
- 'ngAnimate',
- 'indexCtrl',
- 'dashboardCtrl',
- 'queueCtrl',
- 'ruleCtrl',
- 'screenshotCtrl',
- 'runsFactory',
- 'resultsFactory',
- 'apiService',
- 'menuService',
- 'settingsService',
- 'gradeDirective',
- 'offendersDirectives',
- 'LocalStorageModule'
-]);
-
-yltApp.run(['$rootScope', '$location', function($rootScope, $location) {
- $rootScope.isTouchDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent);
- $rootScope.loadedRunId = null;
-
- var oldHash;
-
- // We don't want the hash to be kept between two pages
- $rootScope.$on('$locationChangeStart', function(param1, param2, param3, param4){
- var newHash = $location.hash();
- if (newHash === oldHash) {
- $location.hash(null);
- }
- oldHash = newHash;
- });
-
- // Google Analytics
- $rootScope.$on('$routeChangeSuccess', function(){
- if (typeof ga !== "undefined") {
- ga('send', 'pageview', {'page': $location.path()});
- }
- });
-
- // GitHub star button (asynchronously loaded iframe)
- window.addEventListener('load', function() {
- window.document.getElementById('ghbtn').src = 'https://ghbtns.com/github-btn.html?user=YellowLabTools&repo=YellowLabTools&type=star&count=true&size=large';
- });
-}]);
-
-yltApp.config(['$routeProvider', '$locationProvider',
- function($routeProvider, $locationProvider) {
- $routeProvider.
- when('/', {
- templateUrl: 'views/index.html',
- controller: 'IndexCtrl'
- }).
- when('/queue/:runId', {
- templateUrl: 'views/queue.html',
- controller: 'QueueCtrl'
- }).
- when('/about', {
- templateUrl: 'views/about.html'
- }).
- when('/result/:runId', {
- templateUrl: 'views/dashboard.html',
- controller: 'DashboardCtrl'
- }).
- when('/result/:runId/screenshot', {
- templateUrl: 'views/screenshot.html',
- controller: 'ScreenshotCtrl'
- }).
- when('/result/:runId/rule/:policy', {
- templateUrl: 'views/rule.html',
- controller: 'RuleCtrl'
- }).
- otherwise({
- redirectTo: '/'
- });
-
- $locationProvider.html5Mode(true);
- }
-]);
-
-// Disable debugging https://docs.angularjs.org/guide/production
-yltApp.config(['$compileProvider', function ($compileProvider) {
- $compileProvider.debugInfoEnabled(false);
-}]);
\ No newline at end of file
diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js
deleted file mode 100644
index cff25b3..0000000
--- a/front/src/js/controllers/dashboardCtrl.js
+++ /dev/null
@@ -1,37 +0,0 @@
-var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory', 'menuService']);
-
-dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'Results', 'API', 'Menu', function($scope, $rootScope, $routeParams, $location, Results, API, Menu) {
- $scope.runId = $routeParams.runId;
- $scope.Menu = Menu.setCurrentPage('dashboard', $scope.runId);
-
- function loadResults() {
- // Load result if needed
- if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) {
- Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) {
- $rootScope.loadedResult = result;
- $scope.result = result;
- init();
- }, function(err) {
- $scope.error = true;
- });
- } else {
- $scope.result = $rootScope.loadedResult;
- init();
- }
- }
-
- function init() {
- // By default, Angular sorts object's attributes alphabetically. Countering this problem by retrieving the keys order here.
- $scope.categoriesOrder = Object.keys($scope.result.scoreProfiles.generic.categories);
-
- $scope.globalScore = Math.max($scope.result.scoreProfiles.generic.globalScore, 0);
-
- $scope.tweetText = 'I\'ve discovered this cool open-source tool that audits the front-end quality of a web page: ';
- }
-
- $scope.testAgain = function() {
- API.relaunchTest($scope.result);
- };
-
- loadResults();
-}]);
\ No newline at end of file
diff --git a/front/src/js/controllers/indexCtrl.js b/front/src/js/controllers/indexCtrl.js
deleted file mode 100644
index 2537a93..0000000
--- a/front/src/js/controllers/indexCtrl.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var indexCtrl = angular.module('indexCtrl', []);
-
-indexCtrl.controller('IndexCtrl', ['$scope', '$routeParams', '$location', 'Settings', 'API', function($scope, $routeParams, $location, Settings, API) {
-
- $scope.settings = Settings.getMergedSettings();
-
- $scope.launchTest = function() {
- if ($scope.url) {
- $location.search('url', null);
- $location.search('run', null);
- Settings.saveSettings($scope.settings);
- API.launchTest($scope.url, $scope.settings);
- }
- };
-
- // Auto fill URL field and auto launch test when the good params are set in the URL
- if ($routeParams.url) {
- $scope.url = $routeParams.url;
- if ($routeParams.run === 'true' || $routeParams.run === 1 || $routeParams.run === '1') {
- $scope.launchTest();
- }
- }
-}]);
\ No newline at end of file
diff --git a/front/src/js/controllers/queueCtrl.js b/front/src/js/controllers/queueCtrl.js
deleted file mode 100644
index 938e6d7..0000000
--- a/front/src/js/controllers/queueCtrl.js
+++ /dev/null
@@ -1,100 +0,0 @@
-var queueCtrl = angular.module('queueCtrl', ['runsFactory']);
-
-queueCtrl.controller('QueueCtrl', ['$scope', '$routeParams', '$location', 'Runs', 'API', function($scope, $routeParams, $location, Runs, API) {
- $scope.runId = $routeParams.runId;
-
- var numberOfTries = 0;
-
- var favicon = document.querySelector('link[rel=icon]');
- var faviconUrl = 'img/favicon.png';
- var faviconSuccessUrl = 'img/favicon-success.png';
- var faviconFailUrl = 'img/favicon-fail.png';
- var faviconInterval = null;
- var faviconCounter = 0;
- var faviconCanvas = null;
- var faviconCanvasContext = null;
- var faviconImage = null;
-
- function getRunStatus () {
- Runs.get({runId: $scope.runId}, function(data) {
- $scope.url = data.params.url;
- $scope.status = data.status;
- $scope.progress = data.progress;
- $scope.notFound = false;
- $scope.connectionLost = false;
-
- if (data.status.statusCode === 'awaiting') {
- numberOfTries ++;
- rotateFavicon();
-
- // Retrying every 2 seconds (and increasing the delay a bit more each time)
- setTimeout(getRunStatus, 2000 + (numberOfTries * 100));
-
- } else if (data.status.statusCode === 'running') {
- numberOfTries ++;
- rotateFavicon();
-
- // Retrying every second or so
- setTimeout(getRunStatus, 1000 + (numberOfTries * 10));
-
- } else if (data.status.statusCode === 'complete') {
- stopFavicon(true);
-
- $location.path('/result/' + $scope.runId).replace();
- } else {
- stopFavicon(false);
-
- // The rest is handled by the view
- }
- }, function(response) {
- if (response.status === 404) {
- stopFavicon(false);
- $scope.notFound = true;
- $scope.connectionLost = false;
- } else if (response.status === 0) {
- // Connection lost, retry in 10 seconds
- setTimeout(getRunStatus, 10000);
- $scope.connectionLost = true;
- $scope.notFound = false;
- }
- });
- }
-
- function rotateFavicon() {
- if (!faviconInterval) {
- faviconImage = new Image();
- faviconImage.onload = function() {
- faviconCanvas = document.getElementById('faviconRotator');
- faviconCanvasContext = faviconCanvas.getContext('2d');
- faviconCanvasContext.fillStyle = '#212240';
-
- if (!!faviconCanvasContext) {
- faviconInterval = window.setInterval(faviconTick, 300);
- }
- };
- faviconImage.src = faviconUrl;
- }
- }
-
- function faviconTick() {
- faviconCounter ++;
- faviconCanvasContext.save();
- faviconCanvasContext.fillRect(0, 0, 32, 32);
- faviconCanvasContext.translate(16, 16);
- faviconCanvasContext.rotate(22.5 * faviconCounter * Math.PI / 180);
- faviconCanvasContext.translate(-16, -16);
- faviconCanvasContext.drawImage(faviconImage, 0, 0, 32, 32);
- faviconCanvasContext.restore();
- favicon.href = faviconCanvas.toDataURL('image/png');
- }
-
- function stopFavicon(isSuccess) {
- window.clearInterval(faviconInterval);
- faviconInterval = null;
- favicon.href = isSuccess ? faviconSuccessUrl : faviconFailUrl;
- }
-
- getRunStatus();
-}]);
-
-
\ No newline at end of file
diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js
deleted file mode 100644
index b2502c2..0000000
--- a/front/src/js/controllers/ruleCtrl.js
+++ /dev/null
@@ -1,78 +0,0 @@
-var ruleCtrl = angular.module('ruleCtrl', ['chart.js']);
-
-ruleCtrl.config(['ChartJsProvider', function (ChartJsProvider) {
- // Configure all charts
- ChartJsProvider.setOptions({
- animation: false,
- colours: ['#FF5252', '#FF8A80'],
- responsive: true
- });
-}]);
-
-ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$sce', 'Menu', 'Results', 'API', function($scope, $rootScope, $routeParams, $location, $sce, Menu, Results, API) {
- $scope.runId = $routeParams.runId;
- $scope.policyName = $routeParams.policy;
- $scope.Menu = Menu.setCurrentPage(null, $scope.runId);
- $scope.rule = null;
-
- function loadResults() {
- // Load result if needed
- if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) {
- Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) {
- $rootScope.loadedResult = result;
- $scope.result = result;
- init();
- });
- } else {
- $scope.result = $rootScope.loadedResult;
- init();
- }
- }
-
- function init() {
- $scope.rule = $scope.result.rules[$scope.policyName];
-
- // Init "Total Weight" chart
- if ($scope.policyName === 'totalWeight') {
- $scope.weightLabels = [];
- $scope.weightColours = ['#7ECCCC', '#A7E846', '#FF944D', '#FFE74A', '#C2A3FF', '#5A9AED', '#FF6452', '#C1C1C1'];
- $scope.weightData = [];
-
- var types = ['html', 'css', 'js', 'json', 'image', 'video', 'webfont', 'other'];
- types.forEach(function(type) {
- $scope.weightLabels.push(type);
- $scope.weightData.push(Math.round($scope.rule.offendersObj.list.byType[type].totalWeight / 1024));
- });
-
- $scope.weightOptions = {
- tooltips: {
- callbacks: {
- label: function(tooltipItem, data) {
- var label = data.labels[tooltipItem.index];
- var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
- return label + ': ' + value + ' KB';
- }
- }
- },
- legend: {
- display: true,
- position: 'bottom',
- labels: {
- boxWidth: 12,
- fontSize: 14
- }
- }
- };
- }
- }
-
- $scope.backToDashboard = function() {
- $location.path('/result/' + $scope.runId);
- };
-
- $scope.testAgain = function() {
- API.relaunchTest($scope.result);
- };
-
- loadResults();
-}]);
\ No newline at end of file
diff --git a/front/src/js/controllers/screenshotCtrl.js b/front/src/js/controllers/screenshotCtrl.js
deleted file mode 100644
index 6bd5fbf..0000000
--- a/front/src/js/controllers/screenshotCtrl.js
+++ /dev/null
@@ -1,30 +0,0 @@
-var screenshotCtrl = angular.module('screenshotCtrl', ['resultsFactory', 'menuService']);
-
-screenshotCtrl.controller('ScreenshotCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'Results', 'API', 'Menu', function($scope, $rootScope, $routeParams, $location, Results, API, Menu) {
- $scope.runId = $routeParams.runId;
- $scope.Menu = Menu.setCurrentPage(null, $scope.runId);
-
- function loadResults() {
- // Load result if needed
- if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) {
- Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) {
- $rootScope.loadedResult = result;
- $scope.result = result;
- }, function(err) {
- $scope.error = true;
- });
- } else {
- $scope.result = $rootScope.loadedResult;
- }
- }
-
- $scope.backToDashboard = function() {
- $location.path('/result/' + $scope.runId);
- };
-
- $scope.testAgain = function() {
- API.relaunchTest($scope.result);
- };
-
- loadResults();
-}]);
\ No newline at end of file
diff --git a/front/src/js/directives/gradeDirective.js b/front/src/js/directives/gradeDirective.js
deleted file mode 100644
index 99cad49..0000000
--- a/front/src/js/directives/gradeDirective.js
+++ /dev/null
@@ -1,33 +0,0 @@
-var gradeDirective = angular.module('gradeDirective', []);
-
-gradeDirective.directive('grade', function() {
-
- return {
- restrict: 'E',
- scope: {
- score: '=score'
- },
- template: '
{{getGrade(score)}}
',
- replace: true,
- controller : ['$scope', function($scope) {
- $scope.getGrade = function(score) {
- if (score > 80) {
- return 'A';
- }
- if (score > 60) {
- return 'B';
- }
- if (score > 40) {
- return 'C';
- }
- if (score > 20) {
- return 'D';
- }
- if (score > 0) {
- return 'E';
- }
- return 'F';
- };
- }]
- };
-});
\ No newline at end of file
diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js
deleted file mode 100644
index 956a9cb..0000000
--- a/front/src/js/directives/offendersDirectives.js
+++ /dev/null
@@ -1,307 +0,0 @@
-(function() {
- "use strict";
- var offendersDirectives = angular.module('offendersDirectives', []);
-
- function getdomTreeHTML(tree) {
- return '' + getdomTreeInnerHTML(tree) + '
';
- }
-
- function getdomTreeInnerHTML(tree) {
- return recursiveHtmlBuilder(tree);
- }
-
- function recursiveHtmlBuilder(tree) {
- var html = '';
- var keys = Object.keys(tree);
-
- keys.forEach(function(key) {
- if (isNaN(tree[key])) {
- html += '' + key + ' ' + recursiveHtmlBuilder(tree[key]) + '
';
- } else if (tree[key] > 1) {
- html += '' + key + ' (x' + tree[key] + ')
';
- } else {
- html += '' + key + '
';
- }
- });
-
- return html;
- }
-
- offendersDirectives.directive('domTree', function() {
- return {
- restrict: 'E',
- scope: {
- tree: '='
- },
- template: '
',
- replace: true,
- link: function(scope, element) {
- element.append(getdomTreeInnerHTML(scope.tree));
- }
- };
- });
-
- function getDomElementButtonHTML(obj, onASingleLine) {
- if (obj.tree && !onASingleLine) {
- return '' + getDomElementButtonInnerHTML(obj, onASingleLine) + '
';
- } else {
- return '' + getDomElementButtonInnerHTML(obj, onASingleLine) + '
';
- }
- }
-
- function getDomElementButtonInnerHTML(obj, onASingleLine) {
- if (obj.type === 'html' ||
- obj.type === 'body' ||
- obj.type === 'head' ||
- obj.type === 'window' ||
- obj.type === 'document' ||
- obj.type === 'fragment') {
- return obj.type;
- }
-
- if (obj.type === 'notAnElement') {
- return 'Incorrect element';
- }
-
- var html = '';
- if (obj.type === 'domElement') {
- html = 'DOM element ' + obj.element + ' ';
- } else if (obj.type === 'fragmentElement') {
- html = 'Fragment element ' + obj.element + ' ';
- } else if (obj.type === 'createdElement') {
- html = 'Created element ' + obj.element + ' ';
- }
-
- if (obj.tree && !onASingleLine) {
- html += getdomTreeHTML(obj.tree);
- }
-
- return html;
- }
-
- offendersDirectives.directive('domElementButton', function() {
- return {
- restrict: 'E',
- scope: {
- obj: '='
- },
- template: '
',
- replace: true,
- link: function(scope, element) {
- element.append(getDomElementButtonInnerHTML(scope.obj));
- }
- };
- });
-
- offendersDirectives.filter('lastDOMNode', function() {
- return function(str) {
- var splited = str.split(' > ');
- return splited[splited.length - 1];
- };
- });
-
- function getBacktraceHTML(backtrace) {
- var html = '';
- var parsedBacktrace = parseBacktrace(backtrace);
-
- if (!parsedBacktrace || parsedBacktrace.length === 0) {
- html += 'can\'t find any backtrace :/
';
- } else {
- for (var i = 0 ; i < parsedBacktrace.length ; i++) {
- html += '';
- html += '
' + (parsedBacktrace[i].fnName || '(anonymous function)') + '
';
- html += '
' + getUrlLink(parsedBacktrace[i].filePath, 40) + '
';
- if (parsedBacktrace[i].column) {
- html += '
' + parsedBacktrace[i].line + ':' + parsedBacktrace[i].column + '
';
- } else {
- html += '
line ' + parsedBacktrace[i].line + '
';
- }
- html += '
';
- }
- }
-
- return html;
- }
-
- function parseBacktrace(str) {
- if (!str) {
- return null;
- }
-
- var out = [];
- var splited = str.split(' / ');
-
- try {
-
- splited.forEach(function(trace) {
- var fnName = null, fileAndLine;
-
- var withFnResult = /^([^\s\(]+) \((.+:\d+)\)$/.exec(trace);
-
- if (withFnResult === null) {
- // Try the PhantomJS 2 format
- withFnResult = /^([^\s\(]+) \((.+:\d+:\d+)\)$/.exec(trace);
- }
-
- if (withFnResult === null) {
- // Yet another PhantomJS 2 format?
- withFnResult = /^([^\s\(]+|global code)@(.+:\d+:\d+)$/.exec(trace);
- }
-
- if (withFnResult === null) {
- // Try the PhantomJS 2 ERROR format
- withFnResult = /^([^\s\(]+) (http.+:\d+)$/.exec(trace);
- }
-
- if (withFnResult === null) {
- fileAndLine = trace;
- } else {
- fnName = withFnResult[1];
- fileAndLine = withFnResult[2];
- }
-
- // And now the second part
- var fileAndLineSplit = /^(.*):(\d+):(\d+)$/.exec(fileAndLine);
-
- if (fileAndLineSplit === null) {
- fileAndLineSplit = /^(.*):(\d+)$/.exec(fileAndLine);
- }
-
- var filePath = fileAndLineSplit[1];
- var line = fileAndLineSplit[2];
- var column = fileAndLineSplit[3];
-
- // Filter phantomas code
- if (filePath.indexOf('phantomjs://') === -1) {
- out.push({
- fnName: fnName,
- filePath: filePath,
- line: line,
- column: column
- });
- }
- });
-
- } catch(e) {
- return null;
- }
-
- return out;
- }
-
- function shortenUrl(url, maxLength) {
- if (!maxLength) {
- maxLength = 110;
- }
-
- // Why dividing by 2.1? Because it adds a 5% margin.
- var leftLength = Math.floor((maxLength - 5) / 2.1);
- var rightLength = Math.ceil((maxLength - 5) / 2.1);
-
- return (url.length > maxLength) ? url.substr(0, leftLength) + ' ... ' + url.substr(-rightLength) : url;
- }
-
- offendersDirectives.filter('shortenUrl', function() {
- return shortenUrl;
- });
-
- function getUrlLink(url, maxLength) {
- return '' + shortenUrl(url, maxLength) + ' ';
- }
-
- offendersDirectives.directive('urlLink', function() {
- return {
- restrict: 'E',
- scope: {
- url: '=',
- maxLength: '='
- },
- template: '{{url | shortenUrl:maxLength}} ',
- replace: true
- };
- });
-
- offendersDirectives.filter('encodeURIComponent', function() {
- return window.encodeURIComponent;
- });
-
- offendersDirectives.directive('fileAndLine', function() {
- return {
- restrict: 'E',
- scope: {
- file: '=',
- line: '=',
- column: '='
- },
- template: '<inline CSS> @ {{line}}:{{column}} ',
- replace: true
- };
- });
-
- offendersDirectives.directive('fileAndLineButton', function() {
- return {
- restrict: 'E',
- scope: {
- file: '=',
- line: '=',
- column: '='
- },
- template: '',
- replace: true
- };
- });
-
- offendersDirectives.filter('bytes', function() {
- return function(bytes) {
- if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) {
- return '-';
- }
-
- var kilo = bytes / 1024;
-
- if (kilo < 1) {
- return bytes + ' bytes';
- }
-
- if (kilo < 100) {
- return kilo.toFixed(1) + ' KB';
- }
-
- if (kilo < 1024) {
- return kilo.toFixed(0) + ' KB';
- }
-
- var mega = kilo / 1024;
-
- if (mega < 10) {
- return mega.toFixed(2) + ' MB';
- }
-
- return mega.toFixed(1) + ' MB';
- };
- });
-
- offendersDirectives.filter('addSpaces', function() {
- return function(str) {
- return str.split('').join(' ');
- };
- });
-
- // Proxify an HTTP image to HTTPS if hosted on HTTPS
- // Uses a great free open-source external service: https://images.weserv.nl
- offendersDirectives.filter('https', function() {
- return function(url) {
- if (url && url.indexOf('http://') === 0 && window.location.protocol === 'https:') {
- return 'https://images.weserv.nl/?url=' + encodeURIComponent(url.substr(7));
- }
- return url;
- };
- });
-
- offendersDirectives.filter('roundNbr', function() {
- return function(nbr) {
- return Math.round(nbr);
- };
- });
-
-})();
diff --git a/front/src/js/models/resultsFactory.js b/front/src/js/models/resultsFactory.js
deleted file mode 100644
index dfe5a3c..0000000
--- a/front/src/js/models/resultsFactory.js
+++ /dev/null
@@ -1,7 +0,0 @@
-var resultsFactory = angular.module('resultsFactory', ['ngResource']);
-
-resultsFactory.factory('Results', ['$resource', function($resource) {
- return $resource('api/results/:runId', {
-
- });
-}]);
\ No newline at end of file
diff --git a/front/src/js/models/runsFactory.js b/front/src/js/models/runsFactory.js
deleted file mode 100644
index da54bb0..0000000
--- a/front/src/js/models/runsFactory.js
+++ /dev/null
@@ -1,7 +0,0 @@
-var runsFactory = angular.module('runsFactory', ['ngResource']);
-
-runsFactory.factory('Runs', ['$resource', function($resource) {
- return $resource('api/runs/:runId', {
-
- });
-}]);
\ No newline at end of file
diff --git a/front/src/js/services/apiService.js b/front/src/js/services/apiService.js
deleted file mode 100644
index 01c6409..0000000
--- a/front/src/js/services/apiService.js
+++ /dev/null
@@ -1,64 +0,0 @@
-var apiService = angular.module('apiService', []);
-
-apiService.factory('API', ['$location', 'Runs', 'Results', function($location, Runs, Results) {
-
- return {
-
- launchTest: function(url, settings) {
- var runObject = {
- url: url,
- waitForResponse: false,
- screenshot: true,
- device: settings.device,
- waitForSelector: settings.waitForSelector,
- proxy: settings.proxy,
- cookie: settings.cookie,
- authUser: settings.authUser,
- authPass: settings.authPass,
- blockDomain: settings.blockDomain,
- allowedDomains: settings.allowedDomains,
- noExternals: settings.noExternals
- };
-
-
- if (settings.domainsBlockOrAllow === 'block') {
- runObject.blockDomain = this.parseDomains(settings.domains);
- } else if (settings.domainsBlockOrAllow === 'allow') {
- var allowedDomains = this.parseDomains(settings.domains);
- if (allowedDomains.length > 0) {
- runObject.allowDomain = allowedDomains;
- } else {
- runObject.noExternals = true;
- }
- }
-
- Runs.save(runObject, function(data) {
- $location.path('/queue/' + data.runId);
- }, function(response) {
- if (response.status === 429) {
- alert('Too many requests, you reached the max number of requests allowed in 24h');
- } else if (response.status === 403) {
- alert('This particular query was blocked due to spamming. If you think it\'s an error, please open an issue on GitHub.');
- } else {
- alert('An error occured...');
- }
- });
- },
-
- relaunchTest: function(result) {
- this.launchTest(result.params.url, result.params.options);
- },
-
- parseDomains: function(textareaContent) {
- var lines = textareaContent.split('\n');
-
- function removeEmptyLines (line) {
- return line.trim() !== '';
- }
-
- // Remove empty lines
- return lines.filter(removeEmptyLines).join(',');
- }
- };
-
-}]);
\ No newline at end of file
diff --git a/front/src/js/services/menuService.js b/front/src/js/services/menuService.js
deleted file mode 100644
index 9ef3c37..0000000
--- a/front/src/js/services/menuService.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var menuService = angular.module('menuService', []);
-
-menuService.factory('Menu', ['$location', function($location) {
-
- var currentPage, currentRunId;
-
- return {
- getCurrentPage: function() {
- return currentPage;
- },
- setCurrentPage: function(page, runId) {
- currentPage = page;
- currentRunId = runId;
-
- return this;
- },
- changePage: function(page) {
- switch (page) {
- case 'index':
- $location.path('/');
- break;
- case 'dashboard':
- $location.path('/result/' + currentRunId);
- break;
- default:
- console.err('Undefined Menu.changePage() destination');
- }
- }
- };
-
-}]);
\ No newline at end of file
diff --git a/front/src/js/services/settingsService.js b/front/src/js/services/settingsService.js
deleted file mode 100644
index 741280c..0000000
--- a/front/src/js/services/settingsService.js
+++ /dev/null
@@ -1,24 +0,0 @@
-var settingsService = angular.module('settingsService', []);
-
-settingsService.factory('Settings', ['localStorageService', function(localStorageService) {
-
- return {
-
- getMergedSettings: function() {
- var defaultSettings = {
- device: 'phone',
- showAdvanced: false
- };
-
- var savedValues = localStorageService.get('settings');
-
- return angular.extend(defaultSettings, savedValues);
- },
-
- saveSettings: function(settings) {
- localStorageService.set('settings', settings);
- }
-
- };
-
-}]);
\ No newline at end of file
diff --git a/front/src/less/about.less b/front/src/less/about.less
deleted file mode 100644
index 0f101e9..0000000
--- a/front/src/less/about.less
+++ /dev/null
@@ -1,20 +0,0 @@
-.about {
- margin: 3em auto;
- width: 80%;
-
- @media (min-width: 640px) {
- width: 50%;
- }
-}
-
-.about p {
- margin: 2em;
-}
-
-.about a {
- color: #fff;
-}
-
-.sponsor {
- color: #ffa319;
-}
\ No newline at end of file
diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less
deleted file mode 100644
index d344964..0000000
--- a/front/src/less/dashboard.less
+++ /dev/null
@@ -1,199 +0,0 @@
-.testedUrl {
- color: inherit;
-}
-
-.summary {
- text-align: center;
-}
-
-.summary .globalScore {
- margin: 3em auto;
-
- .globalGrade {
- margin: 0.5 auto;
- width: 2.5em;
- height: 2.5em;
- line-height: 2.5em;
- border-radius: 0.5em;
- font-size: 3em;
- vertical-align: middle;
- }
- .on100 {
- font-size: 1.2em;
- margin: 0.5em 0 1em;
- }
-
- .screenshotWrapper:hover {
- opacity: 0.75;
-
- &:after {
- position: absolute;
- width: 1.25em;
- height: 1.25em;
- top: 0.7em;
- left: 1.55em;
- font-size: 3em;
- color: #FFF;
- background: #000;
- border-radius: 0.2em;
- text-align: center;
- content: "+";
- opacity: 0.85;
- }
- }
-
- .screenshotWrapper.phone:hover:after {
- top: 1.7em;
- left: 0.64em;
- }
-
- .screenshotWrapper.tablet:hover:after {
- top: 1.5em;
- left: 0.9em;
- }
-
- @media (min-width: 820px) {
- width: 65%;
- display: table;
-
- > div {
- display: table-cell;
- width: 50%;
- vertical-align: middle;
- }
- }
-}
-
-.summary .notations {
- width: 100%;
- display: table;
- margin: 0 0 1.5em;
- border-spacing: 0 1em;
-
- @media (min-width: 820px) {
- width: 80%;
- margin: 0 10% 1.5em;
- border-spacing: 1em;
- }
-}
-.summary .notations > div {
- display: table-row;
-}
-.summary .notations > div > div {
- vertical-align: middle;
-
- @media (min-width: 820px) {
- display: table-cell;
- height: 2.5em;
- }
-}
-.summary .notations .category {
- font-size: 1.2em;
- width: 50%;
- float: left;
- text-align: left;
- margin: 0.5em 0.25em;
-
- @media (min-width: 820px) {
- width: 20%;
- text-align: center;
- float: none;
- }
-}
-.summary .notations .criteria {
- font-weight: normal;
-
- @media (min-width: 820px) {
- width: 75%;
- }
-}
-.A, .B, .C, .D, .E, .F, .NA {
- .summary .notations &.categoryScore {
- width: 2.5em;
- max-width: 2.5em;
- min-width: 2.5em;
- margin: 0.2em;
- font-size: 1.5em;
- text-align: center;
- border-radius: 0.5em;
- float: right;
-
- @media (min-width: 820px) {
- float: none;
- font-size: 2em;
- }
- }
- .summary .notations .grade & {
- width: 1em;
- height: 1em;
- font-size: 1em;
- color: transparent;
- margin: 0 auto;
- border-radius: 0.5em;
- }
-}
-
-.summary .notations .criteria .table {
- width: 100%;
- > a {
- text-decoration: none;
- color: inherit;
- }
- > a:hover > div {
- background: #d8ebe0;
- cursor: pointer;
- &.info {
- background: #FFF;
- svg {
- fill: #d8ebe0;
- }
- }
- }
-}
-.summary .notations .criteria .grade {
- width: 10%;
- padding-left: 0.5em;
- padding-right: 0.5em;
- vertical-align: middle;
-}
-.summary .notations .criteria .label {
- width: 70%;
-}
-.summary .notations .criteria .result {
- width: 18%;
- white-space: nowrap;
- text-align: center;
- vertical-align: middle;
-}
-.summary .notations .warning .label, .summary .notations .warning .result {
- color: #FF1919;
-}
-.summary .notations .icon-warning svg {
- fill: #FF1919;
- margin: -2px 0;
-}
-.summary .notations .criteria .info {
- display: none;
-
- @media (min-width: 820px) {
- display: table-cell;
- width: 2%;
- text-align: center;
- vertical-align: middle;
- background: #FFF;
- padding-left: 0.1em;
- padding-right: 0.1em;
- }
-}
-.summary .notations .criteria .info svg {
- fill: transparent;
-}
-
-.summary .sponsor {
- font-size: 0.9em;
- margin-bottom: 4em;
- color: #ffa319;
- a {
- color: inherit;
- }
-}
\ No newline at end of file
diff --git a/front/src/less/index.less b/front/src/less/index.less
deleted file mode 100644
index 1edb887..0000000
--- a/front/src/less/index.less
+++ /dev/null
@@ -1,223 +0,0 @@
-.promess {
- padding: 0em 2em;
- margin-bottom: 0.5em;
- font-weight: normal;
- font-size: 1.2em;
-}
-
-.price {
- padding: 0em 2em 3em;
- margin-top: 0em;
- font-size: 0.9em;
-}
-
-.url {
- width: 50%;
-}
-
-.launchBtn {
- background: #ffa319;
- color: #fff;
- &:focus {
- background: #e74c3c;
- }
- &.disabled {
- background: #f1bd70;
- &:focus {
- color: #ddd;
- }
- }
-
-}
-
-.settings {
- width: 50%;
- margin: 0 auto;
-
- input, select {
- font-size: 1em;
- }
-
- input[type=text], input[type=password], textarea {
- width: 100%;
- min-width: 4em;
- }
-}
-
-.device {
- margin-top: 3em;
- .item {
- display: inline-block;
- margin: 1em 0.75em;
- width: 5.5em;
- height: 5.5em;
- color: #FFF;
- border: 1px solid #FFF;
- padding: 1px;
- border-radius: 0.5em;
- cursor: pointer;
- text-decoration: none;
- font-size: 0.8em;
-
- > svg {
- display: block;
- margin: 0.6em auto 0.3em;
- fill: #fff;
- }
-
- &.active {
- color: #ffa319;
- border: 2px solid #ffa319;
- padding: 0;
-
- > svg {
- fill: #ffa319;
- }
- }
-
- &:hover {
- color: #ffa319;
-
- > svg {
- fill: #ffa319;
- }
- }
- }
-}
-
-.settingsTooltip {
- position: relative;
- svg {
- vertical-align: text-top;
- }
-
- div {
- display: none;
- position: absolute;
- padding: 0.5em;
- width: 25em;
- background: #FFF;
- color: #000;
- font-size: 0.8em;
- border-radius: 1em;
- border: 2px solid #ffa319;
- white-space: normal;
- word-break: break-all;
- word-break: break-word;
- z-index: 2;
- }
-
- &:hover div {
- display: block;
- }
-}
-
-.showAdvanced {
- display: inline-block;
- margin-top: 2em;
- color: #FFF;
- text-decoration: none;
- font-size: 0.9em;
-
- &:hover {
- color: #ffa319;
- }
-}
-
-.currentSettings {
- font-size: 0.9em;
-
- span {
- color: #ffa319;
- &:after {
- color: #FFF;
- content: ",";
- }
-
- &:last-child:after {
- content: "";
- }
- }
-}
-
-.advanced {
- margin: 1em 0 0;
- display: table;
- width: 100%;
- text-align: left;
- border-spacing: 0.75em;
-
- > div {
- display: table-row;
-
- > div {
- display: table-cell;
- width: 75%;
-
- &.label {
- width: 25%;
- white-space: nowrap;
- vertical-align: middle;
- }
- }
- }
-
- .subTable {
- display: table;
- border-spacing: 0;
- width: 100%;
- > div {
- display: table-row;
- > div {
- display: table-cell;
- padding: 0 0 0.75em;
- }
- }
- }
-}
-
-.features {
- display: table;
- width: 50%;
- margin: 6em auto 0;
- font-size: 0.9em;
- color: #8abfaf;
-
- > div {
- @media (min-width: 640px) {
- width: 33.3%;
- display: table-cell;
- padding: 0 1.5em;
- }
- }
-
- h3 {
- font-size: 1.5em;
- font-weight: normal;
- 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;
- 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;
-}
-
-.homeSponsor {
- margin-top: 3em;
-}
\ No newline at end of file
diff --git a/front/src/less/main.less b/front/src/less/main.less
deleted file mode 100644
index 2b4c0ff..0000000
--- a/front/src/less/main.less
+++ /dev/null
@@ -1,298 +0,0 @@
-html {
- margin: 35px 5px;
- @media (min-width: 640px) {
- margin: 100px 50px;
- }
-}
-
-body {
- margin: 0 auto;
- max-width: 1280px;
- background: #212240;
- 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 {
- font-weight: 200;
-}
-
-.resultsMenu {
- margin-top: 2em;
-}
-.resultsMenu .menuItem {
- font-size: 0.8em;
- display: inline-block;
- width: 7em;
- height: 7em;
- color: #fff;
- cursor: pointer;
- text-decoration: none;
-
- @media (min-width: 640px) {
- font-size: 1em;
- margin: 1em;
- width: 8em;
- border: 2px solid #fff;
- border-radius: 0.5em;
- }
-
- svg {
- fill: #fff;
- }
-
- &.back, &.restart {
- color: #fff;
- border-color: #fff;
- }
-}
-.resultsMenu .menuItem div {
- padding-top: 0.5em;
- font-size: 3em;
-}
-.resultsMenu svg {
- display: block;
- margin: 1.2em auto 0.2em;
-}
-.resultsMenu .active, .resultsMenu .menuItem.active:hover {
- color: #ffa319;
- border-color: #ffa319;
-
- svg {
- fill: #ffa319;
- }
-}
-.resultsMenu .menuItem:hover {
- color: #ffa319;
-
- svg {
- fill: #ffa319;
- }
-}
-.resultsMenu span {
- position: relative;
- top: 0.5em;
-}
-
-/* Grade colors */
-.A {
- /* green */
- background: #0C4;
-}
-.B {
- /* green */
- background: #CD0;
-}
-.C {
- /* yellow */
- background: #FD2;
-}
-.D {
- /* orange */
- background: #FA2;
-}
-.E {
- /* red */
- background: #F60;
-}
-.F {
- /* red */
- background: #F22;
-}
-.NA {
- /* Non applicable */
- background: #CCC;
-}
-
-.board {
- margin-top: 2em;
- padding: 1em;
- background: #fff;
- color: #000;
- border-radius: 0.5em;
- text-align: left;
-}
-
-.backToDashboard {
- text-align: center;
-
- a {
- font-size: 0.9em;
- display: block;
- margin-top: 4em;
- color: black;
- }
-}
-
-a.linkButton {
- font-size: 1em;
- padding: 0.3em 0.5em;
- margin: 0.5em;
- line-height: 2em;
- border: 0 solid;
- border-radius: 0.5em;
- box-shadow: 0.1em 0.2em 0 0 #5e2846;
- background: #e74c3c;
- color: #fff;
- text-decoration: none;
-}
-
-
-.screenshotWrapper {
- display: inline-block;
- position: relative;
- background: #000;
-
- > div {
- position: relative;
- overflow: hidden;
- }
-
- .screenshotImage {
- width: 100%;
- }
-
- .screenshotError {
- color: #fff;
- }
-}
-
-.screenshotWrapper.desktop, .screenshotWrapper.desktop-hd {
- border: 0.2em solid #AAA;
- padding: 0.5em;
- border-top-left-radius: 0.4em;
- border-top-right-radius: 0.4em;
-
- &:before {
- position: absolute;
- width: 15em;
- height: 0.6em;
- bottom: -0.75em;
- left: -1em;
- background: #CCC;
- border-bottom-left-radius: 0.2em;
- border-bottom-right-radius: 0.2em;
- content: " ";
- }
-
- &:after {
- position: absolute;
- width: 0.4em;
- height: 0.2em;
- bottom: -0.55em;
- left: 12.5em;
- background: lime;
- content: " ";
- }
-
- > div {
- width: 12em;
- height: 7.5em;
- }
-}
-
-.screenshotWrapper.phone {
- border: 0.07em solid #CCC;
- padding: 1em 0.3em 1.5em;
- border-radius: 0.6em;
-
- &:before {
- position: absolute;
- width: 0.8em;
- height: 0.8em;
- bottom: 0.3em;
- left: 3.3em;
- border: 0.07em solid #CCC;
- border-radius: 0.5em;
- content: " ";
- }
-
- &:after {
- position: absolute;
- width: 1em;
- height: 0.1em;
- bottom: 13.9em;
- left: 3.2em;
- background: #555;
- border-radius: 0.05em;
- content: " ";
- }
-
- > div {
- width: 6.75em;
- height: 12em;
- }
-}
-
-.screenshotWrapper.tablet {
- border: 0.07em solid #CCC;
- padding: 0.8em 0.5em 0.9em;
- border-radius: 0.6em;
-
- &:before {
- position: absolute;
- width: 0.5em;
- height: 0.5em;
- bottom: 0.15em;
- left: 4.35em;
- border: 0.07em solid #CCC;
- border-radius: 0.4em;
- content: " ";
- }
-
- > div {
- width: 8em;
- height: 12.8em;
- }
-}
-
-.table {
- display: table;
- width: 100%;
- border-spacing: 0.25em;
-}
-.table > div,
-.table > a {
- display: table-row;
-}
-.table > .headers > div {
- font-weight: bold;
- padding: 0.5em 1em;
-}
-.table > div > div,
-.table > a > div {
- padding: 0.1em 1em;
- background: #f2f2f2;
- display: table-cell;
- text-align: left;
-}
-
-.footer {
- padding: 3em;
- color: #fff;
- a {
- color: inherit;
- }
- .version {
- font-size: 0.7em;
- }
- .github {
- margin: 1em 0 0 0.5em;
- }
- .sponsor {
- font-size: 0.9em;
- }
-}
-
-.homeSponsor {
- color: #ffa319;
-}
\ No newline at end of file
diff --git a/front/src/less/queue.less b/front/src/less/queue.less
deleted file mode 100644
index 266fbee..0000000
--- a/front/src/less/queue.less
+++ /dev/null
@@ -1,24 +0,0 @@
-.status {
- margin-top: 2em;
- font-size: 2.5em;
-}
-
-.statusSubMessage {
- font-size: 0.8em;
- margin-bottom: 6em;
-}
-
-.progressBarEmpty {
- width: 90%;
- max-width: 300px;
- margin: 1em auto;
- padding: 0.05em;
- border: 1px solid #ffa319;
-}
-
-.progressBarFilled {
- width: 5%;
- height: 0.5em;
- background: #ffa319;
- transition: width 3s ease-out;
-}
\ No newline at end of file
diff --git a/front/src/less/rule.less b/front/src/less/rule.less
deleted file mode 100644
index 43cae38..0000000
--- a/front/src/less/rule.less
+++ /dev/null
@@ -1,307 +0,0 @@
-.rule.board {
- text-align: center;
-}
-
-.rule .ruleTable {
- border-spacing: 1em;
- width: 90%;
- margin: 2em auto;
- background: #f2f2f2;
- border: 1px dashed #666;
- border-radius: 0.5em;
-
- @media (min-width: 820px) {
- display: table;
-
- > div {
- display: table-cell;
- vertical-align: middle;
- }
-
- .left {
- width: 33%;
- }
- .right {
- width: 67%;
- }
- }
-}
-
-.rule .score {
- font-size: 2.5em;
- line-height: 2em;
- height: 2em;
- width: 2em;
- border-radius: 0.5em;
- margin: 0 auto 0.25em;
-}
-
-.rule h3 {
- margin-bottom: 0em;
-}
-
-.rule .okThreshold {
- font-style: italic;
- font-size: 0.9em;
-}
-
-.rule .message {
- width: 80%;
- margin: 1.5em auto;
- p {
- margin: 0.5em;
- }
-}
-
-.rule .message ul {
- list-style-type: none;
- padding-left: 0;
-}
-.rule .message li:before {
- content:'\25e6';
- margin-right: 0.3em;
- font-size: 1.2em;
- position: relative;
- top: 0.1em;
-}
-
-.rule .warning {
- width: 90%;
- margin: -1em auto 2em;
- background: #FEE;
- border: 1px dashed #e74c3c;
- color: #e74c3c;
- border-radius: 0.5em;
-}
-
-.rule .offendersTable {
- display: table;
- border-spacing: 0 0.25em;
- margin: 0 auto;
- min-width: 10%;
- font-size: 0.875em;
-
- @media (min-width: 820px) {
- max-width: 90%;
- font-size: 1em;
- }
-
- > div {
- display: table-row;
- > div {
- display: table-cell;
- background: #f2f2f2;
- padding: 0 0.25em;
- word-wrap: break-word;
- word-break: break-all;
- &:hover {
- background: #d8ebe0;
- }
- }
- }
-}
-
-.rule .notFound {
- font-size: 1em;
- h2 {
- font-size: 3em;
- margin-bottom: 1em;
- }
-}
-
-.rule .startTime {
- display: none;
-}
-
-.offendersTable, .value {
- .offenderButton {
- display: inline-block;
- position: relative;
- background: #efe;
- padding: 0 0.5em;
- margin: 0.2em 0;
- border-radius: 0.4em;
- z-index: 1;
- cursor: pointer;
-
- &.opens {
- padding-right: 0.75em;
-
- &:after {
- position: relative;
- left: 0.5em;
- content: '\25BC';
- font-size: 0.8em;
- }
- }
-
- > div {
- display: none;
- position: absolute;
- right: 0;
- min-width: 100%;
- background: inherit;
- border-bottom-left-radius: 0.4em;
- border-bottom-right-radius: 0.4em;
- border-top: 1px solid #999;
- z-index: 2;
- }
-
- .domTree {
- text-align: left;
- white-space: nowrap;
-
- > div {
- margin: 0.5em;
-
- div {
- margin-left: 1em;
- }
- }
- }
-
- .backtrace, .cssFileAndLine {
- white-space: nowrap;
- padding: 0.5em;
- }
-
- &.opens:hover {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- background: #ffe0cc;
- z-index: 2;
-
- > div {
- display: block;
- background: #ffe0cc;
- }
- }
- }
-
- .smallerOffenders {
- font-size: 0.9em;
- }
-}
-
-.offendersHtml {
- display: inline-block;
-}
-
-.domTree div {
- text-align: left;
- margin-left: 1em;
-
- span:only-child {
- font-weight: bold;
- span {
- font-style: italic;
- font-weight: normal;
- }
- }
-}
-
-.checker {
- /* Checkerboard background */
- background-color: #ddd;
- background-image: linear-gradient(45deg, #AAA 25%, transparent 25%, transparent 75%, #AAA 75%, #AAA), linear-gradient(45deg, #AAA 25%, transparent 25%, transparent 75%, #AAA 75%, #AAA);
- background-size:1em 1em;
- background-position:0 0, 0.5em 0.5em;
-}
-
-.colorPalette {
- width: 30em;
- border: 2px solid #000;
- text-align: left;
-
- > div {
- display: inline-block;
- height: 2em;
- position: relative;
-
- div {
- display: none;
- position: absolute;
- left: 100%;
- top: 100%;
- background: #FFF;
- padding: 0.5em;
- border: 2px solid #f1c40f;
- border-radius: 0.5em;
- white-space: nowrap;
- z-index: 3;
- font-weight: bold;
- }
-
- &:hover div {
- display: block;
- }
-
- &:hover:after {
- content: " ";
- position: absolute;
- left: -0.2em;
- top: -0.2em;
- width: 100%;
- height: 100%;
- z-index: 2;
- border: 0.2em solid #f1c40f;
- }
- }
-}
-
-.similarColors {
- margin: 1em;
- width: 20em;
- height: 6em;
-
- > div {
- display: inline-block;
- width: 10em;
- height: 3.5em;
- padding-top: 2.5em;
- }
-}
-
-.totalWeightPie {
- max-width: 20em;
- margin: 2em auto 4em;
-
- canvas {
- max-width: inherit;
- }
-}
-
-.offenderProblem {
- font-weight: bold;
- color: #e74c3c;
-}
-
-.imageOffenders {
- display: table;
- border-spacing: 3em;
- width: 90%;
-
- > div {
- display: table-row;
-
- > div {
- display: table-cell;
- vertical-align: middle;
- }
- }
-
- img {
- max-height: 10em;
- max-width: 40em;
- border: 1px solid #000;
- margin-top: 0.5em;
- }
-}
-
-.smallPreview {
- display: block;
- max-height: 6em;
- max-width: 16em;
- border: 1px solid #000;
- margin: 1em auto 0.2em;
-}
\ No newline at end of file
diff --git a/front/src/less/screenshot.less b/front/src/less/screenshot.less
deleted file mode 100644
index 5bc58e3..0000000
--- a/front/src/less/screenshot.less
+++ /dev/null
@@ -1,16 +0,0 @@
-.screenshot.board {
- text-align: center;
-}
-
-.screenshot .screenshotWrapper {
- font-size: 1.2em;
- margin-bottom: 0.5em;
-
- @media (min-width: 420px) {
- font-size: 1.6em;
- }
-
- @media (min-width: 640px) {
- font-size: 2.08333333333333em;
- }
-}
\ No newline at end of file
diff --git a/front/src/main.html b/front/src/main.html
deleted file mode 100644
index 2648d8e..0000000
--- a/front/src/main.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
- Yellow Lab Tools - Page Speed audit
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/front/src/views/about.html b/front/src/views/about.html
deleted file mode 100644
index 91dcfe5..0000000
--- a/front/src/views/about.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
Yellow Lab Tools is an open source project by Gaël Métais . It allows you to test a webpage (via an URL) and detects performance and front-end code quality issues.
-
-
It is based on Phantomas , a tool that instruments Chrome Headless to collect dozens of metrics. These metrics are then categorized and transformed into scores. It also provides in-depth details so that developers can fix the detected issues.
-
-
By the way, it's entirely free . In return, you can add a ★ on GitHub or buy me a coffee . It will boost my motivation to add more awesome features!
-
- <%if (sponsoring.about) { %>
-
- <% } %>
-
-
Back to index
-
\ No newline at end of file
diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html
deleted file mode 100644
index cf58243..0000000
--- a/front/src/views/dashboard.html
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
-
-
-
-
Global score
-
-
-
{{globalScore}}/100
-
-
-
-
-
-
Score details
-
-
-
-
{{category.label}}
-
-
-
-
- <%if (sponsoring.wordpress) { %>
-
- <% } %>
-
- <%if (sponsoring.dashboard) { %>
-
- <% } %>
-
-
-
-
-
-
Run failed / Run not found
-
-
\ No newline at end of file
diff --git a/front/src/views/index.html b/front/src/views/index.html
deleted file mode 100644
index ffb5b40..0000000
--- a/front/src/views/index.html
+++ /dev/null
@@ -1,117 +0,0 @@
-Online test to help speeding up heavy web pages
-Free and open source!
-
-
-
-
-
-
-
Page speed audit
-
Checks if performance good practices are respected
-
-
-
-
Front-end analyzis
-
Detects problems on HTML, CSS, JS, images, fonts and more
-
-
-
-
In-depth details
-
Provides precise information to fix the detected performance issues
-
-
-
-<%if (sponsoring.home) { %>
-
-<% } %>
\ No newline at end of file
diff --git a/front/src/views/queue.html b/front/src/views/queue.html
deleted file mode 100644
index 835f6e4..0000000
--- a/front/src/views/queue.html
+++ /dev/null
@@ -1,46 +0,0 @@
-Tested url: {{url}}
-
-
-
-
-
-
-
-
-
(auto-refresh activated)
-
-
-
Test is running...
-
-
(Phantomas launched)
-
(DOM Ready fired)
-
(page loaded, waiting for late requests)
-
(now simulating compression, optimization and minification)
-
(calculating score and retrieving screenshot)
-
-
-
Test complete
-
Opening results...
-
-
-
-
Error 404 (test not found)
-
The server probably just rebooted. We are very sorry about that, please try to launch the test again.
-
-
New test
-
-
-
Connection lost with server
-
Check your wifi cable, or maybe YellowLab.tools is rebooting.
-
-
\ No newline at end of file
diff --git a/front/src/views/resultSubHeader.html b/front/src/views/resultSubHeader.html
deleted file mode 100644
index b5b66a9..0000000
--- a/front/src/views/resultSubHeader.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/front/src/views/rule.html b/front/src/views/rule.html
deleted file mode 100644
index 2c8a2a0..0000000
--- a/front/src/views/rule.html
+++ /dev/null
@@ -1,514 +0,0 @@
-
-
-
-
-
-
-
{{rule.policy.label}}
-
-
{{rule.score}}/100
-
-
-
- Value:
- {{rule.value | bytes}}
- {{rule.value}} {{rule.policy.unit}}
-
-
- Have
- {{rule.policy.isOkThreshold | bytes}}
- {{rule.policy.isOkThreshold}} {{rule.policy.unit}}
- or less
- or more
- to get the 100/100 score on this issue.
-
-
- Your new global score would increase by {{rule.globalScoreIfFixed - result.scoreProfiles.generic.globalScore}} points ({{rule.globalScoreIfFixed}}/100).
-
-
- Your new global score would increase by {{rule.globalScoreIfFixed}} points ({{rule.globalScoreIfFixed}}/100).
-
-
- Your new global score would increase, but still not enough to reach 0/100. That's embarassing...
-
-
- Your new global score would slightly increase, but not enough to gain a single point.
-
-
-
-
-
-
Warning
-
This rule reached the abnormality threshold, which means there is a real problem you should care about.
-
-
-
-
-
-
-
- {{offender.parseError}}
-
-
-
-
- {{offender.url}}
- an iframe without URL
-
-
-
- {{offender.id}} : {{offender.count}} occurrences
-
-
-
- {{offender.query}} (in ) using {{offender.fn}}: {{offender.count}} queries
-
-
-
-
Scroll event bound on window
-
Scroll event bound on document
-
window.onscroll function declared
-
no backtrace
-
-
-
-
-
{{offender.error}}
-
no backtrace
-
-
-
-
- function {{offender.functionName}} used {{offender.count}} times
-
-
-
- {{offender.writeFn}}
-
- called from
- {{offender.from.functionName}}()
-
- line {{offender.from.line}}
-
-
- called from (no backtrace available)
-
-
-
-
- inline CSS
-
- :
-
-
-
-
{{offender.color1}}
{{offender.color2}}
-
-
-
-
-
- {{offender.css}}
-
-
-
-
-
{{offender.property}} {{offender.message}}
-
- show
- hide
-
-
-
-
- {{cssRule.rule}} {{'{' + offender.property}}: {{cssRule.value + '}' }}
-
-
-
-
-
-
-
-
(offset: {{offender.offset | roundNbr}}px)
-
-
-
-
-
-
-
-
-
-
{{offender.width}}x{{offender.height}}
-
-
-
-
-
-
-
-
-
- cached for {{offender.ttlWithUnit}} {{offender.unit}}
-
-
-
- {{offender.domain}}
- ( )
-
-
-
- {{offender}}
-
-
-
- {{offender.version}}
-
-
-
- {{offender.url}}
-
-
-
-
- ({{offender.size | bytes}})
-
-
-
- {{offender.domain}} sends over {{offender.httpVersion}}
-
-
-
- {{offender.domain}} uses {{offender.tlsVersion}} and seems to be on the critical path
-
-
-
-
-
-
-
-
- in
-
- inline CSS
-
-
-
-
-
-
- {{offender.css}}
- @ {{offender.line}}:{{offender.column}}
-
-
-
- {{offender.query}} for
- @ {{offender.line}}:{{offender.column}}
-
-
-
- {{offender.rule}} (x{{offender.occurrences}} )
-
-
-
- Property {{offender.property}} duplicated in {{offender.rule}} { }
- @ {{offender.line}}:{{offender.column}}
-
-
-
- {{offender.css}} { }
- @ {{offender.line}}:{{offender.column}}
-
-
-
- {{offender.rule}} {{ '{' + offender.property}}: {{offender.value}} !important }
- @ {{offender.line}}:{{offender.column}}
-
-
-
- {{offender.browser}} fix:
-
- @ {{offender.line}}:{{offender.column}}
-
-
-
-
-
-
-
-
-
-
-
-
-
This is the colors palette, sized by total occurrences:
-
-
{{offender.color}} ({{offender.occurrences}} times)
-
-
-
-
-
-
-
Weight by MIME type
-
-
-
-
-
{{rule.offendersObj.list.byType[type].totalWeight | bytes}} of {{type}}
-
-
-
-
{{request.weight | bytes}}
-
-
-
-
-
-
-
-
- from
- getElementById()
- getElementsByTagName()
- getElementsByClassName()
- querySelector() or querySelectorAll()
- appendChild() or insertBefore()
- added nodes
- removed nodes
- attribute changes
- addEventListener()
-
-
-
-
#{{access.id}}
-
{{access.tag}} on {{access.node | lastDOMNode}}
-
.{{access.class}} on {{access.node | lastDOMNode}}
-
{{access.selector}} on {{access.node | lastDOMNode}}
-
{{access.append | lastDOMNode}} added to {{access.node | lastDOMNode}}
-
{{access.node}} added to {{access.target}}
-
{{access.node}} removed from {{access.target}}
-
{{access.attribute}} changed on {{access.node}}
-
{{access.eventType}} on {{access.path | lastDOMNode}}
-
-
-
-
-
-
-
{{rule.value | bytes}} could be saved on
-
-
-
-
-
Current weight: {{image.originalWeigth | bytes}}
-
Current weight: {{image.originalWeigth | bytes}} ({{image.originalCompressedWeight | bytes}} compressed)
-
-
With a lossless optimization: {{image.afterOptimizationAndCompression | bytes}} compressed (-{{image.gain | bytes}} compressed)
-
With a lossless optimization: {{image.lossless | bytes}} (-{{image.gain | bytes}} )
-
-
With a lossy optimization: {{image.afterOptimizationAndCompression | bytes}} compressed (-{{image.gain | bytes}} compressed )
-
With a lossy optimization: {{image.lossy | bytes}} (-{{image.gain | bytes}} )
-
-
-
-
-
-
-
{{rule.value | bytes}} could be saved on
-
-
-
-
-
-
-
{{file.originalSize | bytes}}
-
-
already gzipped
-
{{file.gzipped | bytes}}
-
-
{{file.brotlified | bytes}}
-
-
-{{file.gain | bytes}}
-
-
-
-
-
-
{{rule.value | bytes}} could be saved on
-
-
-
-
-
-
-
{{file.originalWeigth | bytes}} (compressed)
-
{{file.originalWeigth | bytes}} ({{file.originalCompressedWeight | bytes}} compressed)
-
{{file.afterOptimizationAndCompression | bytes}} (compressed)
-
{{file.optimized | bytes}} ({{file.afterOptimizationAndCompression | bytes}} compressed)
-
-{{file.gain | bytes}}
-
-
-
-
-
-
Requests by MIME type
-
-
-
Reduce the number of stylesheets by concatenating them.
-
Reduce the number of scripts by concatenating them.
-
Reduce the number of images by lazyloading them or by spriting them.
-
Fonts are generally loaded on the critical path of the head. Load as few as possible.
-
They can be Flash, XML, music or any undetected format.
-
-
-
-
-
-
-
A file of {{offender.weight | bytes}} is loaded {{offender.urls.length}} times:
-
-
-
-
-
-
-
Breakpoints list
-
-
-
Breakpoint {{offender.breakpoint}} involves
-
-
-
-
- No breakpoint
-
-
-
-
-
-
-
-
-
Weight
-
{{font.weight | bytes}}
-
{{font.weight | bytes}}
-
-
-
Number of glyphs
-
{{font.numGlyphs}}
-
{{font.numGlyphs}} (better < 500)
-
-
-
Average glyph complexity
-
{{font.averageGlyphComplexity}}
-
{{font.averageGlyphComplexity}} (better < 35)
-
-
-
-
-
-
-
-
({{font.weight | bytes}})
-
-
-
- This font seems to be an icon font
- but only {{font.numGlyphsInCommonWithPageContent}} of its {{font.glyphs}} glyphs possibly used!
- and {{font.numGlyphsInCommonWithPageContent}} of its {{font.glyphs}} glyphs possibly used.
-
-
-
-
-
-
{{range.name}}
-
{{range.numGlyphsInCommonWithPageContent}} of its {{range.charset.length}} glyphs possibly used
-
{{range.numGlyphsInCommonWithPageContent}} of its {{range.charset.length}} glyphs are used
-
-
-
-
Ligatures or hidden chars
-
{{font.ligaturesOrHiddenChars}} glyphs
-
-
-
-
-
-
-
-
{{rule.value | bytes}} could be saved on
-
-
-
-
-
-
-
{{file.originalSize | bytes}}
-
{{file.woff2Size | bytes}}
-
-{{file.gain | bytes}}
-
-
-
-
-
-
404
- Rule "{{policyName}}"" not found
-
-
-
-
\ No newline at end of file
diff --git a/front/src/views/screenshot.html b/front/src/views/screenshot.html
deleted file mode 100644
index 7c9ba57..0000000
--- a/front/src/views/screenshot.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
Screenshot
-
-
-
-
-
Screenshot not available
-
-
-
-
-
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
index 9064046..bbb5544 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -2,6 +2,8 @@ var Q = require('q');
var Runner = require('./runner');
+var packageJson = require('../package.json');
+
var yellowLabTools = function(url, options) {
'use strict';
@@ -39,4 +41,5 @@ var yellowLabTools = function(url, options) {
return deferred.promise;
};
-module.exports = yellowLabTools;
\ No newline at end of file
+module.exports = yellowLabTools;
+module.exports.version = packageJson.version;
\ No newline at end of file
diff --git a/lib/screenshotHandler.js b/lib/screenshotHandler.js
deleted file mode 100644
index 3632236..0000000
--- a/lib/screenshotHandler.js
+++ /dev/null
@@ -1,141 +0,0 @@
-var debug = require('debug')('ylt:screenshotHandler');
-var Jimp = require('jimp');
-var Q = require('q');
-var fs = require('fs');
-var path = require('path');
-
-
-var screenshotHandler = function() {
-
- var tmpFolderPath = 'tmp';
- var tmpFolderFullPath = path.join(__dirname, '..', tmpFolderPath);
- var tmpFileName = 'temp-screenshot.png';
- var tmpFileFullPath = path.join(tmpFolderFullPath, tmpFileName);
-
-
- this.findAndOptimizeScreenshot = function(width) {
- var that = this;
-
- debug('Starting screenshot transformation');
-
- return this.openImage(tmpFileFullPath)
-
- .then(function(image) {
- that.deleteTmpFile(tmpFileFullPath);
- return that.resizeImage(image, width);
- })
-
- .then(this.toBuffer);
- };
-
-
- this.openImage = function(imagePath) {
- var deferred = Q.defer();
-
- Jimp.read(imagePath, function(err, image){
- if (err) {
- debug('Could not open imagePath %s', imagePath);
- debug(err);
-
- deferred.reject(err);
- } else {
- debug('Image correctly open');
- deferred.resolve(image);
- }
- });
-
- return deferred.promise;
- };
-
-
- this.resizeImage = function(image, newWidth) {
- var deferred = Q.defer();
-
- var currentWidth = image.bitmap.width;
-
- if (currentWidth > 0) {
- var ratio = newWidth / currentWidth;
-
- image.scale(ratio, function(err, image){
- if (err) {
- debug('Could not resize image');
- debug(err);
-
- deferred.reject(err);
- } else {
- debug('Image correctly resized');
- deferred.resolve(image);
- }
- });
- } else {
- deferred.reject('Could not resize an empty image');
- }
-
- return deferred.promise;
- };
-
-
- this.toBuffer = function(image) {
- var deferred = Q.defer();
-
- image.quality(85).getBuffer(Jimp.MIME_JPEG, function(err, buffer){
- if (err) {
- debug('Could not save image to buffer');
- debug(err);
-
- deferred.reject(err);
- } else {
- debug('Image correctly transformed to buffer');
- deferred.resolve(buffer);
- }
- });
-
- return deferred.promise;
- };
-
-
- this.deleteTmpFile = function(tmpFilePath) {
- var deferred = Q.defer();
-
- fs.unlink(tmpFilePath, function (err) {
- if (err) {
- debug('Screenshot temporary file not found, could not be deleted. But it is not a problem.');
- } else {
- debug('Screenshot temporary file deleted.');
- }
-
- deferred.resolve();
- });
-
- return deferred.promise;
- };
-
- // Create a /tmp folder on the project's root directory
- this.createTmpScreenshotFolder = function() {
- var deferred = Q.defer();
-
- // Create the folder if it doesn't exist
- fs.exists(tmpFolderFullPath, function(exists) {
- if (exists) {
- deferred.resolve();
- } else {
- debug('Creating the tmp image folder', tmpFolderFullPath);
- fs.mkdir(tmpFolderFullPath, function(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- });
- }
- });
-
- return deferred.promise;
- };
-
- this.getTmpFileRelativePath = function() {
- return tmpFolderPath + '/' + tmpFileName;
- };
-};
-
-module.exports = new screenshotHandler();
diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js
deleted file mode 100644
index 090e6ec..0000000
--- a/lib/server/controllers/apiController.js
+++ /dev/null
@@ -1,352 +0,0 @@
-var debug = require('debug')('ylt:server');
-var Q = require('q');
-
-var ylt = require('../../index');
-var ScreenshotHandler = require('../../screenshotHandler');
-var RunsQueue = require('../datastores/runsQueue');
-var RunsDatastore = require('../datastores/runsDatastore');
-var ResultsDatastore = require('../datastores/resultsDatastore');
-
-var serverSettings = (process.env.IS_TEST) ? require('../../../test/fixtures/settings.json') : require('../../../server_config/settings.json');
-
-var ApiController = function(app) {
- 'use strict';
-
- var queue = new RunsQueue();
- var runsDatastore = new RunsDatastore();
- var resultsDatastore = new ResultsDatastore();
-
- // Create a new run
- app.post('/api/runs', function(req, res) {
-
- // Add http to the test URL
- if (req.body.url && req.body.url.toLowerCase().indexOf('http://') !== 0 && req.body.url.toLowerCase().indexOf('https://') !== 0) {
- req.body.url = 'http://' + req.body.url;
- }
-
- // Block requests to unwanted websites (=spam)
- if (req.body.url && isBlocked(req.body.url)) {
- console.error('Test blocked for URL: %s', req.body.url);
- res.status(403).send('Forbidden');
- return;
- }
-
- // Grab the test parameters and generate a random run ID
- var run = {
- runId: (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36),
- params: {
- url: req.body.url,
- waitForResponse: req.body.waitForResponse === true || req.body.waitForResponse === 'true' || req.body.waitForResponse === 1,
- partialResult: req.body.partialResult || null,
- screenshot: req.body.screenshot || false,
- device: req.body.device || 'desktop',
- proxy: req.body.proxy || null,
- waitForSelector: req.body.waitForSelector || null,
- cookie: req.body.cookie || null,
- authUser: req.body.authUser || null,
- authPass: req.body.authPass || null,
- blockDomain: req.body.blockDomain || null,
- allowDomain: req.body.allowDomain || null,
- noExternals: req.body.noExternals || false
- }
- };
-
- // Create the tmp folder if it doesn't exist
- ScreenshotHandler.createTmpScreenshotFolder(run.runId);
-
- // Add test to the testQueue
- debug('Adding test %s to the queue', run.runId);
- var queuePromise = queue.push(run.runId);
-
- // Save the run to the datastore
- runsDatastore.add(run, queuePromise.startingPosition);
-
-
- // Listening for position updates
- queuePromise.progress(function(position) {
- runsDatastore.updatePosition(run.runId, position);
- });
-
- // Let's start the run
- queuePromise.then(function() {
-
- runsDatastore.updatePosition(run.runId, 0);
-
- console.log('Launching test ' + run.runId + ' on ' + run.params.url);
-
- var runOptions = {
- screenshot: run.params.screenshot ? ScreenshotHandler.getTmpFileRelativePath() : false,
- device: run.params.device,
- proxy: run.params.proxy,
- waitForSelector: run.params.waitForSelector,
- cookie: run.params.cookie,
- authUser: run.params.authUser,
- authPass: run.params.authPass,
- blockDomain: run.params.blockDomain,
- allowDomain: run.params.allowDomain,
- noExternals: run.params.noExternals
- };
-
- return ylt(run.params.url, runOptions)
-
- // Update the progress bar on each progress
- .progress(function(progress) {
- runsDatastore.updateRunProgress(run.runId, progress);
- });
-
- })
-
- // Phantomas completed
- .then(function(data) {
-
- debug('Success');
- data.runId = run.runId;
-
-
- // Some conditional steps exist if there is a screenshot
- var screenshotPromise = Q.resolve();
-
- if (run.params.screenshot) {
-
- var screenshotSize = serverSettings.screenshotWidth ? serverSettings.screenshotWidth[run.params.device] : 400;
-
- // Replace the empty promise created earlier with Q.resolve()
- screenshotPromise = ScreenshotHandler.findAndOptimizeScreenshot(screenshotSize)
-
- // Read screenshot
- .then(function(screenshotBuffer) {
- if (screenshotBuffer) {
- debug('Image optimized');
- data.screenshotBuffer = screenshotBuffer;
- data.screenshotUrl = '/api/results/' + data.runId + '/screenshot.jpg';
- }
- })
-
- // Don't worry if there's an error
- .fail(function(err) {
- debug('An error occured while creating the screenshot\'s thumbnail. Ignoring and continuing...');
- debug(err);
- });
-
- }
-
- // Let's continue
- return screenshotPromise
-
- // Save results
- .then(function() {
- // Remove uneeded temp screenshot path
- delete data.params.options.screenshot;
-
- // Here we can remove tools results if not needed
- delete data.toolsResults.phantomas.offenders.requests;
-
- return resultsDatastore.saveResult(data);
- })
-
- // Mark as the run as complete and send the response if the request is still waiting
- .then(function() {
-
- debug('Result saved in datastore');
-
- runsDatastore.markAsComplete(run.runId);
-
- if (run.params.waitForResponse) {
-
- // If the user only wants a portion of the result (partialResult option)
- switch(run.params.partialResult) {
- case 'generalScores':
- res.redirect(302, '/api/results/' + run.runId + '/generalScores');
- break;
- case 'rules':
- res.redirect(302, '/api/results/' + run.runId + '/rules');
- break;
- case 'javascriptExecutionTree':
- res.redirect(302, '/api/results/' + run.runId + '/javascriptExecutionTree');
- break;
- case 'phantomas':
- res.redirect(302, '/api/results/' + run.runId + '/toolsResults/phantomas');
- break;
- default:
- res.redirect(302, '/api/results/' + run.runId);
- }
- }
-
- })
- .fail(function(err) {
- console.error('Test failed for URL: %s', run.params.url);
- console.error(err.toString());
-
- runsDatastore.markAsFailed(run.runId, err.toString());
-
- res.status(500).send('An error occured');
- });
-
- })
-
- .fail(function(err) {
-
- console.error('Test failed for URL: %s', run.params.url);
- console.error(err.toString());
-
- runsDatastore.markAsFailed(run.runId, err.toString());
-
- res.status(400).send('Bad request');
-
- })
-
- .finally(function() {
- queue.remove(run.runId);
- });
-
-
- // The user doesn't want to wait for the response, sending the run ID only
- if (!run.params.waitForResponse) {
- debug('Sending response without waiting.');
- res.setHeader('Content-Type', 'application/json');
- res.send(JSON.stringify({runId: run.runId}));
- }
-
- });
-
-
- // Retrive one run by id
- app.get('/api/runs/:id', function(req, res) {
- var runId = req.params.id;
-
- var run = runsDatastore.get(runId);
-
- if (run) {
- res.setHeader('Content-Type', 'application/json');
- res.send(JSON.stringify(run, null, 2));
- } else {
- res.status(404).send('Not found');
- }
- });
-
- // Counts all pending runs
- app.get('/api/runs', function(req, res) {
- res.setHeader('Content-Type', 'application/json');
- res.send(JSON.stringify({
- pendingRuns: queue.length(),
- timeSinceLastTestStarted: queue.timeSinceLastTestStarted()
- }, null, 2));
- });
-
- // Delete one run by id
- /*app.delete('/api/runs/:id', function(req, res) {
- deleteRun()
- });*/
-
- // Delete all
- /*app.delete('/api/runs', function(req, res) {
- purgeRuns()
- });
-
- // List all
- app.get('/api/runs', function(req, res) {
- listRuns()
- });
-
- // Exists
- app.head('/api/runs/:id', function(req, res) {
- existsX();
- // Returns 200 if the result exists or 404 if not
- });
- */
-
- // Retrive one result by id
- app.get('/api/results/:id', function(req, res) {
- getPartialResults(req.params.id, res, function(data) {
-
- // Some fields can be excluded from the response, this way:
- // /api/results/:id?exclude=field1,field2
- if (req.query.exclude && typeof req.query.exclude === 'string') {
- var excludedFields = req.query.exclude.split(',');
- excludedFields.forEach(function(fieldName) {
- if (data[fieldName]) {
- delete data[fieldName];
- }
- });
- }
-
- return data;
- });
- });
-
- // Retrieve one result and return only the generalScores part of the response
- app.get('/api/results/:id/generalScores', function(req, res) {
- getPartialResults(req.params.id, res, function(data) {
- return data.scoreProfiles.generic;
- });
- });
-
- app.get('/api/results/:id/generalScores/:scoreProfile', function(req, res) {
- getPartialResults(req.params.id, res, function(data) {
- return data.scoreProfiles[req.params.scoreProfile];
- });
- });
-
- app.get('/api/results/:id/rules', function(req, res) {
- getPartialResults(req.params.id, res, function(data) {
- return data.rules;
- });
- });
-
- app.get('/api/results/:id/javascriptExecutionTree', function(req, res) {
- getPartialResults(req.params.id, res, function(data) {
- return data.javascriptExecutionTree;
- });
- });
-
- app.get('/api/results/:id/toolsResults/phantomas', function(req, res) {
- getPartialResults(req.params.id, res, function(data) {
- return data.toolsResults.phantomas;
- });
- });
-
- function getPartialResults(runId, res, partialGetterFn) {
- resultsDatastore.getResult(runId)
- .then(function(data) {
- var results = partialGetterFn(data);
-
- if (typeof results === 'undefined') {
- res.status(404).send('Not found');
- return;
- }
-
- res.setHeader('Content-Type', 'application/json');
- res.send(JSON.stringify(results, null, 2));
-
- }).fail(function() {
- res.status(404).send('Not found');
- });
- }
-
- // Retrive one result by id
- app.get('/api/results/:id/screenshot.jpg', function(req, res) {
- var runId = req.params.id;
-
- resultsDatastore.getScreenshot(runId)
- .then(function(screenshotBuffer) {
-
- res.setHeader('Content-Type', 'image/jpeg');
- res.send(screenshotBuffer);
-
- }).fail(function() {
- res.status(404).send('Not found');
- });
- });
-
- function isBlocked(url) {
- if (!serverSettings.blockedUrls) {
- return false;
- }
-
- return serverSettings.blockedUrls.some(function(blockedUrl) {
- return (url.indexOf(blockedUrl) === 0);
- });
- }
-};
-
-module.exports = ApiController;
diff --git a/lib/server/controllers/frontController.js b/lib/server/controllers/frontController.js
deleted file mode 100644
index 89dc906..0000000
--- a/lib/server/controllers/frontController.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var path = require('path');
-var express = require('express');
-
-var serverSettings = (process.env.IS_TEST) ? require('../../../test/fixtures/settings.json') : require('../../../server_config/settings.json');
-var packageJson = require('../../../package.json');
-
-var FrontController = function(app) {
- 'use strict';
-
- var cacheDuration = 365 * 24 * 60 * 60 * 1000; // One year
- var assetsPath = (app.get('env') === 'development') ? '../../../front/src' : '../../../front/build';
-
- // Routes templating
- var routes = ['/', '/about', '/result/:runId', '/result/:runId/screenshot', '/result/:runId/rule/:policy', '/queue/:runId'];
-
- routes.forEach(function(route) {
- app.get(route, function(req, res) {
- res.setHeader('Cache-Control', 'public, max-age=20');
- res.render(path.join(__dirname, assetsPath, 'main.html'), {
- version: 'v' + packageJson.version,
- baseUrl: app.locals.baseUrl || '/',
- googleAnalyticsId: serverSettings.googleAnalyticsId,
- sponsoring: serverSettings.sponsoring || {}
- });
- });
- });
-
- // Views templating
- app.get('/views/:viewName', function(req, res) {
- res.setHeader('Cache-Control', 'public, max-age=' + cacheDuration);
- res.render(path.join(__dirname, assetsPath, 'views/' + req.params.viewName), {
- baseUrl: app.locals.baseUrl || '/',
- sponsoring: serverSettings.sponsoring || {}
- });
- });
-
- // Static assets
- app.use('/css', express.static(path.join(__dirname, assetsPath, 'css'), { maxAge: cacheDuration }));
- app.use('/fonts', express.static(path.join(__dirname, assetsPath, 'fonts'), { maxAge: cacheDuration }));
- app.use('/img', express.static(path.join(__dirname, assetsPath, 'img'), { maxAge: cacheDuration }));
- app.use('/js', express.static(path.join(__dirname, assetsPath, 'js'), { maxAge: cacheDuration }));
- app.use('/node_modules', express.static(path.join(__dirname, '../../../node_modules'), { maxAge: cacheDuration }));
-};
-
-module.exports = FrontController;
\ No newline at end of file
diff --git a/lib/server/datastores/resultsDatastore.js b/lib/server/datastores/resultsDatastore.js
deleted file mode 100644
index 99df91e..0000000
--- a/lib/server/datastores/resultsDatastore.js
+++ /dev/null
@@ -1,136 +0,0 @@
-var fs = require('fs');
-var rimraf = require('rimraf');
-var path = require('path');
-var Q = require('q');
-var debug = require('debug')('ylt:resultsDatastore');
-
-
-function ResultsDatastore() {
- 'use strict';
-
- var resultFileName = 'results.json';
- var resultScreenshotName = 'screenshot.jpg';
- var resultsFolderName = 'results';
- var resultsDir = path.join(__dirname, '..', '..', '..', resultsFolderName);
-
-
- this.saveResult = function(testResults) {
-
- var screenshotFilePath = path.join(resultsDir, testResults.runId, resultScreenshotName);
- var screenshotAPIPath = '/';
-
- return createResultFolder(testResults.runId)
-
- .then(function() {
- return saveScreenshotIfExists(testResults, screenshotFilePath);
- })
-
- .then(function() {
-
- debug('Saving results to disk...');
-
- var resultFilePath = path.join(resultsDir, testResults.runId, resultFileName);
- debug('Destination file is %s', resultFilePath);
-
- return Q.nfcall(fs.writeFile, resultFilePath, JSON.stringify(testResults, null, 2));
- });
- };
-
-
- this.getResult = function(runId) {
-
- var resultFilePath = path.join(resultsDir, runId, resultFileName);
-
- debug('Reading results (runID = %s) from disk...', runId);
-
- return Q.nfcall(fs.readFile, resultFilePath, {encoding: 'utf8'}).then(function(data) {
- return JSON.parse(data);
- });
- };
-
-
- this.deleteResult = function(runId) {
- var folder = path.join(resultsDir, runId);
-
- debug('Deleting results (runID = %s) from disk...', runId);
-
- return Q.nfcall(rimraf, folder);
- };
-
-
- // The folder /results/folderName/
- function createResultFolder(runId) {
- var folder = path.join(resultsDir, runId);
-
- debug('Creating the folder %s', runId);
-
- return createGlobalFolder().then(function() {
- return Q.nfcall(fs.mkdir, folder);
- });
- }
-
- // The folder /results/
- function createGlobalFolder() {
- var deferred = Q.defer();
-
- // Create the results folder if it doesn't exist
- fs.exists(resultsDir, function(exists) {
- if (exists) {
- deferred.resolve();
- } else {
- debug('Creating the global results folder', resultsDir);
- fs.mkdir(resultsDir, function(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- });
- }
- });
-
- return deferred.promise;
- }
-
- this.getResultFolder = function(runId) {
- return path.join(resultsDir, runId);
- };
-
- // If there is a screenshot, save it as screenshot.jpg in the same folder as the results
- function saveScreenshotIfExists(testResults, path) {
- var deferred = Q.defer();
-
- if (testResults.screenshotBuffer) {
-
- fs.writeFile(path, testResults.screenshotBuffer, function(err) {
- if (err) {
- debug('Could not save final screenshot');
- debug(err);
- // But it is OK, we don't need to fail the run
- deferred.resolve();
- } else {
- debug('Final screenshot saved: ' + path);
- deferred.resolve();
- }
- });
- delete testResults.screenshotBuffer;
-
- } else {
- debug('Screenshot not found');
- deferred.resolve();
- }
-
- return deferred.promise;
- }
-
- this.getScreenshot = function(runId) {
-
- var screenshotFilePath = path.join(resultsDir, runId, resultScreenshotName);
-
- debug('Getting screenshot (runID = %s) from disk...', runId);
-
- return Q.nfcall(fs.readFile, screenshotFilePath);
- };
-}
-
-module.exports = ResultsDatastore;
\ No newline at end of file
diff --git a/lib/server/datastores/runsDatastore.js b/lib/server/datastores/runsDatastore.js
deleted file mode 100644
index fd82fcc..0000000
--- a/lib/server/datastores/runsDatastore.js
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-function RunsDatastore() {
- 'use strict';
-
- // NOT PERSISTING RUNS
- // For the moment, maybe one day
- var runs = {};
-
- var STATUS_AWAITING = 'awaiting';
- var STATUS_RUNNING = 'running';
- var STATUS_COMPLETE = 'complete';
- var STATUS_FAILED = 'failed';
-
-
- this.add = function(run, position) {
- runs[run.runId] = run;
- this.updatePosition(run.runId, position);
- };
-
-
- this.get = function(runId) {
- return runs[runId];
- };
-
-
- this.updatePosition = function(runId, position) {
- var run = runs[runId];
-
- if (position > 0) {
- run.status = {
- statusCode: STATUS_AWAITING,
- position: position
- };
- } else {
- run.status = {
- statusCode: STATUS_RUNNING
- };
- }
-
- runs[runId] = run;
- };
-
-
- // When the test is launched, set the progress bar
- this.updateRunProgress = function(runId, progress) {
- var run = runs[runId];
-
- run.progress = progress;
-
- runs[runId] = run;
- };
-
-
- this.markAsComplete = function(runId) {
- var run = runs[runId];
-
- run.status = {
- statusCode: STATUS_COMPLETE
- };
-
- runs[runId] = run;
- };
-
-
- this.markAsFailed = function(runId, err) {
- var run = runs[runId];
-
- var errorMessage;
- switch(err) {
- case '1':
- errorMessage = "Error 1: unknown error";
- break;
- case '252':
- errorMessage = "Error 252: page timeout in Phantomas";
- break;
- case '253':
- errorMessage = "Error 253: Phantomas config error";
- break;
- case '254':
- errorMessage = "Error 254: page loading failed in PhantomJS";
- break;
- case '255':
- errorMessage = "Error 255: Phantomas error";
- break;
- case '1001':
- errorMessage = "Error 1001: JavaScript profiling failed";
- break;
- case '1002':
- errorMessage = "Error 1002: missing Phantomas metrics";
- break;
- case '1003':
- errorMessage = "Error 1003: Phantomas not returning";
- break;
- default:
- errorMessage = err;
- }
-
- run.status = {
- statusCode: STATUS_FAILED,
- error: errorMessage
- };
-
- runs[runId] = run;
- };
-
-
- this.delete = function(runId) {
- delete runs[runId];
- };
-
-
- this.list = function() {
- var runsArray = [];
- Object.keys(runs).forEach(function(key) {
- runsArray.push(runs[key]);
- });
- return runsArray;
- };
-}
-
-module.exports = RunsDatastore;
\ No newline at end of file
diff --git a/lib/server/datastores/runsQueue.js b/lib/server/datastores/runsQueue.js
deleted file mode 100644
index 5164d67..0000000
--- a/lib/server/datastores/runsQueue.js
+++ /dev/null
@@ -1,90 +0,0 @@
-var Q = require('q');
-var debug = require('debug')('ylt:runsQueue');
-
-
-function RunsQueue() {
- 'use strict';
-
- var queue = [];
- var lastTestTimestamp = 0;
-
- this.push = function(runId) {
- var deferred = Q.defer();
- var startingPosition = queue.length;
-
- debug('Adding run %s to the queue, position is %d', runId, startingPosition);
-
- if (startingPosition === 0) {
-
- // The queue is empty, let's run immediatly
- queue.push({
- runId: runId
- });
-
- lastTestTimestamp = Date.now();
- deferred.resolve();
-
- } else {
-
- queue.push({
- runId: runId,
- positionChangedCallback: function(position) {
- deferred.notify(position);
- },
- itIsTimeCallback: function() {
- lastTestTimestamp = Date.now();
- deferred.resolve();
- }
- });
- }
-
- var promise = deferred.promise;
- promise.startingPosition = startingPosition;
- return promise;
- };
-
-
- this.getPosition = function(runId) {
- // Position 0 means it's a work in progress (a run is removed AFTER it is finished, not before)
- var position = -1;
-
- queue.some(function(run, index) {
- if (run.runId === runId) {
- position = index;
- return true;
- }
- return false;
- });
-
- return position;
- };
-
-
- this.remove = function(runId) {
- var position = this.getPosition(runId);
- if (position >= 0) {
- queue.splice(position, 1);
- }
-
- // Update other runs' positions
- queue.forEach(function(run, index) {
- if (index === 0 && run.itIsTimeCallback) {
- run.itIsTimeCallback();
- } else if (index > 0 && run.positionChangedCallback) {
- run.positionChangedCallback(index);
- }
- });
-
- };
-
- this.length = function() {
- return queue.length;
- };
-
- // Returns the number of seconds since the last test was launched
- this.timeSinceLastTestStarted = function() {
- return Math.round((Date.now() - lastTestTimestamp) / 1000);
- };
-}
-
-module.exports = RunsQueue;
\ No newline at end of file
diff --git a/lib/server/middlewares/apiLimitsMiddleware.js b/lib/server/middlewares/apiLimitsMiddleware.js
deleted file mode 100644
index bd85e9b..0000000
--- a/lib/server/middlewares/apiLimitsMiddleware.js
+++ /dev/null
@@ -1,95 +0,0 @@
-var config = (process.env.IS_TEST) ? require('../../../test/fixtures/settings.json') : require('../../../server_config/settings.json');
-
-var debug = require('debug')('apiLimitsMiddleware');
-
-
-var apiLimitsMiddleware = function(req, res, next) {
- 'use strict';
-
- var ipAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
-
- debug('Entering API Limits Middleware with IP address %s', ipAddress);
-
- if (req.path.indexOf('/api/') === 0 && !res.locals.hasApiKey) {
-
-
- // Monitoring requests
- if (req.path === '/api/runs' && req.method === 'GET') {
- next();
- return;
- }
-
- // New tests
- if (req.path === '/api/runs' && req.method === 'POST') {
-
- if (!runsTable.accepts(ipAddress)) {
- // Sorry :/
- debug('Too many tests launched from IP address %s', ipAddress);
- res.status(429).send('Too many requests');
- return;
- }
-
- }
-
- // Every other calls
- if (!callsTable.accepts(ipAddress)) {
- // Sorry :/
- debug('Too many API requests from IP address %s', ipAddress);
- res.status(429).send('Too many requests');
- return;
- }
-
- debug('Not blocked by the API limits');
- // It's ok for the moment
- }
-
- next();
-};
-
-
-var RecordTable = function(maxPerDay) {
- var table = {};
-
- // Check if the user overpassed the limit and save its visit
- this.accepts = function(ipAddress) {
- if (table[ipAddress]) {
-
- this.cleanEntry(ipAddress);
-
- debug('%d visits in the last 24 hours', table[ipAddress].length);
-
- if (table[ipAddress].length >= maxPerDay) {
- return false;
- } else {
- table[ipAddress].push(Date.now());
- }
-
- } else {
- table[ipAddress] = [];
- table[ipAddress].push(Date.now());
- }
-
- return true;
- };
-
- // Clean the table for this guy
- this.cleanEntry = function(ipAddress) {
- table[ipAddress] = table[ipAddress].filter(function(date) {
- return date > Date.now() - 1000*60*60*24;
- });
- };
-
- // Clean the entire table once in a while
- this.removeOld = function() {
- for (var ipAddress in table) {
- this.cleanEntry(ipAddress);
- }
- };
-
-};
-
-// Init the records tables
-var runsTable = new RecordTable(config.maxAnonymousRunsPerDay);
-var callsTable = new RecordTable(config.maxAnonymousCallsPerDay);
-
-module.exports = apiLimitsMiddleware;
\ No newline at end of file
diff --git a/lib/server/middlewares/authMiddleware.js b/lib/server/middlewares/authMiddleware.js
deleted file mode 100644
index d7094e2..0000000
--- a/lib/server/middlewares/authMiddleware.js
+++ /dev/null
@@ -1,42 +0,0 @@
-var config = (process.env.IS_TEST) ? require('../../../test/fixtures/settings.json') : require('../../../server_config/settings.json');
-
-var debug = require('debug')('authMiddleware');
-
-
-var authMiddleware = function(req, res, next) {
- 'use strict';
-
- if (req.path.indexOf('/api/') === 0) {
-
-
- if (req.headers && req.headers['x-api-key']) {
-
- // Test if it's an authorized key
- if (isApiKeyValid(req.headers['x-api-key'])) {
-
- // Come in!
- debug('Authorized key: %s', req.headers['x-api-key']);
- res.locals.hasApiKey = true;
-
- } else {
-
- // Sorry :/
- debug('Unauthorized key %s', req.headers['x-api-key']);
- res.status(401).send('Unauthorized');
- return;
- }
- } else {
- debug('No authorization key');
- // It's ok for the moment but you might be blocked by the apiLimitsMiddleware, dude
- }
- }
-
- next();
-};
-
-
-function isApiKeyValid(apiKey) {
- return (config.authorizedKeys[apiKey]) ? true : false;
-}
-
-module.exports = authMiddleware;
\ No newline at end of file
diff --git a/lib/server/middlewares/wwwRedirectMiddleware.js b/lib/server/middlewares/wwwRedirectMiddleware.js
deleted file mode 100644
index 4560222..0000000
--- a/lib/server/middlewares/wwwRedirectMiddleware.js
+++ /dev/null
@@ -1,12 +0,0 @@
-var wwwRedirectMiddleware = function(req, res, next) {
- 'use strict';
-
- // Redirect www.yellowlab.tools to yellowlab.tools without "www" (for SEO purpose)
- if(/^www\.yellowlab\.tools/.test(req.headers.host)) {
- res.redirect(301, req.protocol + '://' + req.headers.host.replace(/^www\./, '') + req.url);
- } else {
- next();
- }
-};
-
-module.exports = wwwRedirectMiddleware;
\ No newline at end of file
diff --git a/lib/tools/phantomas/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js
index c7b9bc6..37701e1 100644
--- a/lib/tools/phantomas/phantomasWrapper.js
+++ b/lib/tools/phantomas/phantomasWrapper.js
@@ -1,6 +1,5 @@
var async = require('async');
var Q = require('q');
-var ps = require('ps-node');
var path = require('path');
var debug = require('debug')('ylt:phantomaswrapper');
var phantomas = require('phantomas');
diff --git a/package.json b/package.json
index d0e6ea1..682827f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "yellowlabtools",
- "version": "2.0.0",
- "description": "Online tool to audit a webpage for performance and front-end quality issues",
+ "version": "2.1.0",
+ "description": "A tool that audits a webpage for performance and front-end quality issues",
"license": "GPL-2.0",
"author": {
"name": "Gaël Métais",
@@ -20,25 +20,12 @@
},
"main": "./lib/index.js",
"dependencies": {
- "angular": "1.7.7",
- "angular-animate": "1.7.7",
- "angular-chart.js": "1.1.1",
- "angular-local-storage": "0.7.1",
- "angular-resource": "1.7.7",
- "angular-route": "1.7.7",
- "angular-sanitize": "1.7.7",
"async": "2.6.1",
- "body-parser": "1.18.3",
- "chart.js": "2.7.3",
"clean-css": "4.2.1",
"color-diff": "1.1.0",
- "compression": "1.7.3",
- "cors": "2.8.5",
"css-mq-parser": "0.0.3",
"debug": "4.1.1",
"easyxml": "2.0.1",
- "ejs": "2.6.1",
- "express": "4.16.4",
"fontkit": "1.7.8",
"html-minifier": "4.0.0",
"image-size": "0.7.1",
@@ -58,47 +45,24 @@
"is-webp": "1.0.1",
"is-woff": "1.0.3",
"is-woff2": "1.0.0",
- "jimp": "0.6.0",
"md5": "2.2.1",
"meow": "5.0.0",
"parse-color": "1.0.0",
- "phantomas": "github:macbre/phantomas#devel",
- "ps-node": "0.1.6",
+ "phantomas": "2.2.0",
"q": "1.5.1",
"request": "2.88.0",
- "rimraf": "2.6.3",
- "temporary": "0.0.8",
"ttf2woff2": "4.0.1",
"uglify-js": "3.4.9",
"woff-tools": "0.1.0"
},
"devDependencies": {
"chai": "~4.2.0",
- "grunt": "~1.0.3",
- "grunt-contrib-clean": "~2.0.0",
- "grunt-contrib-concat": "~1.0.1",
- "grunt-contrib-copy": "~1.0.0",
- "grunt-contrib-cssmin": "~3.0.0",
- "grunt-contrib-htmlmin": "~3.0.0",
- "grunt-contrib-jshint": "~2.0.0",
- "grunt-contrib-less": "~2.0.0",
- "grunt-contrib-uglify": "~4.0.0",
- "grunt-contrib-watch": "~1.1.0",
- "grunt-env": "~0.4.4",
- "grunt-express": "~1.4.1",
- "grunt-filerev": "~2.3.1",
- "grunt-inline-angular-templates": "~0.1.5",
- "grunt-mocha-test": "~0.13.3",
- "grunt-parallel": "~0.5.1",
- "grunt-usemin": "~3.1.1",
- "matchdep": "~2.0.0",
"mocha": "~5.2.0",
"sinon": "~7.2.3",
"sinon-chai": "~3.3.0"
},
"scripts": {
- "test": "grunt test",
- "build": "grunt build"
+ "test": "todo"
},
"keywords": [
"performance",
@@ -106,6 +70,7 @@
"webperf",
"pagespeed",
"budget",
- "phantomas"
+ "phantomas",
+ "puppeteer"
]
}
diff --git a/server_config/error50x.html b/server_config/error50x.html
deleted file mode 100644
index 4d9f95b..0000000
--- a/server_config/error50x.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
- Yellow Lab Tools
-
-
-
-
-
-
-
-
-
-
-
-
Sorry, Yellow Lab Tools looks broken :/
-
The admin has been alerted.
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/server_config/maintenance.js b/server_config/maintenance.js
deleted file mode 100644
index 406a6e0..0000000
--- a/server_config/maintenance.js
+++ /dev/null
@@ -1,13 +0,0 @@
-var express = require('express');
-var app = express();
-var server = require('http').createServer(app);
-
-var settings = require('./settings.json');
-
-app.all('*', function(req, res) {
- res.status(500).send('YellowLabTools is in maintenance. It should come back soon with a new version!');
-});
-
-server.listen(settings.serverPort, function() {
- console.log('Maintenance mode started on port %d', server.address().port);
-});
\ No newline at end of file
diff --git a/server_config/server_install.sh b/server_config/server_install.sh
deleted file mode 100644
index ce4bc55..0000000
--- a/server_config/server_install.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env bash
-
-# APT-GET
-sudo apt-get update
-sudo apt-get install lsb-release libfontconfig1 libfreetype6 libjpeg-dev libnss3 libatk1.0-0 libatk-bridge2.0-0 gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release libgbm1 xdg-utils wget -y --force-yes > /dev/null 2>&1
-sudo apt-get install curl git software-properties-common build-essential make g++ -y --force-yes > /dev/null 2>&1
-
-# Installation of NodeJS
-curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
-sudo apt-get install -y nodejs > /dev/null 2>&1
-source ~/.profile
-
-# Installation of some packages globally
-npm install forever grunt-cli -g
-source ~/.profile
-
-# Installation of YellowLabTools
-sudo chown -R $USER /space
-cd /space/YellowLabTools
-npm install || exit 1
-
-# Front-end compilation
-grunt build
-
-# Start the server
-rm server_config/settings.json
-cp server_config/settings-prod.json server_config/settings.json
-NODE_ENV=production forever start -c "node --stack-size=262000" bin/server.js
\ No newline at end of file
diff --git a/server_config/server_update.sh b/server_config/server_update.sh
deleted file mode 100755
index 641af18..0000000
--- a/server_config/server_update.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env bash
-
-cd /space/YellowLabTools
-
-# Stop the server and start the maintenance mode
-forever stopall
-forever start server_config/maintenance.js
-
-# Keep the settings.json file
-git stash
-git pull
-git stash pop
-
-# In case something was added in package.json
-rm -rf node_modules
-npm install || exit 1
-
-# Front-end compilation
-rm -rf front/build
-grunt build
-
-# Stop the maintenance mode and restart the server
-forever stopall
-NODE_ENV=production forever start -c "node --stack-size=262000" bin/server.js
\ No newline at end of file
diff --git a/server_config/settings-prod.json b/server_config/settings-prod.json
deleted file mode 100644
index 1390bbb..0000000
--- a/server_config/settings-prod.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "serverPort": 80,
- "googleAnalyticsId": "",
- "screenshotWidth": {
- "phone": 360,
- "tablet": 420,
- "desktop": 600,
- "desktop-hd": 600
- },
- "baseUrl": "/",
- "authorizedKeys": {
-
- },
- "maxAnonymousRunsPerDay": 1000,
- "maxAnonymousCallsPerDay": 100000,
- "blockedUrls": [],
-
- "sponsoring" : {
- "home": "(this is a private instance)",
- "dashboard": null,
- "about": "(this is a private instance)"
- }
-}
\ No newline at end of file
diff --git a/server_config/settings.json b/server_config/settings.json
deleted file mode 100644
index becee77..0000000
--- a/server_config/settings.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "serverPort": 8383,
- "googleAnalyticsId": "",
- "screenshotWidth": {
- "phone": 360,
- "tablet": 420,
- "desktop": 600,
- "desktop-hd": 600
- },
- "baseUrl": "/",
- "authorizedKeys": {
-
- },
- "maxAnonymousRunsPerDay": 99999999,
- "maxAnonymousCallsPerDay": 99999999,
- "blockedUrls": [],
-
- "sponsoring" : {
- "home": "(this is a private instance)",
- "dashboard": null,
- "about": "(this is a private instance)"
- }
-}
\ No newline at end of file
diff --git a/test/api/apiTest.js b/test/api/apiTest.js
deleted file mode 100644
index 88daaf8..0000000
--- a/test/api/apiTest.js
+++ /dev/null
@@ -1,681 +0,0 @@
-var should = require('chai').should();
-var request = require('request');
-var Q = require('q');
-
-var config = {
- "authorizedKeys": {
- "1234567890": "contact@gaelmetais.com"
- }
-};
-
-var serverUrl = 'http://localhost:8387';
-var wwwUrl = 'http://localhost:8388';
-
-describe('api', function() {
-
-
- var syncRunResultUrl;
- var asyncRunId;
- var screenshotUrl;
-
-
- it('should refuse a query with an invalid key', function(done) {
- this.timeout(5000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: wwwUrl + '/simple-page.html',
- waitForResponse: false
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': 'invalid'
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 401) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should fail without an URL when asynchronous', function(done) {
- this.timeout(15000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: '',
- waitForResponse: true
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 400) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should fail without an URL when synchronous', function(done) {
- this.timeout(15000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: '',
- waitForResponse: true
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 400) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should launch a synchronous run', function(done) {
- this.timeout(15000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: wwwUrl + '/simple-page.html',
- waitForResponse: true,
- screenshot: true,
- device: 'tablet',
- //waitForSelector: '*',
- cookie: 'foo=bar;domain=google.com',
- authUser: 'joe',
- authPass: 'secret'
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 302) {
-
- response.headers.should.have.a.property('location').that.is.a('string');
- syncRunResultUrl = response.headers.location;
-
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should return the rules only', function(done) {
- this.timeout(15000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: wwwUrl + '/simple-page.html',
- waitForResponse: true,
- partialResult: 'rules'
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 302) {
-
- response.headers.should.have.a.property('location').that.is.a('string');
- response.headers.location.should.contain('/rules');
-
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should retrieve the results for the synchronous run', function(done) {
- this.timeout(15000);
-
- request({
- method: 'GET',
- url: serverUrl + syncRunResultUrl,
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('runId').that.is.a('string');
- body.should.have.a.property('params').that.is.an('object');
- body.should.have.a.property('scoreProfiles').that.is.an('object');
- body.should.have.a.property('rules').that.is.an('object');
- body.should.have.a.property('toolsResults').that.is.an('object');
-
- // Check if settings are correctly sent and retrieved
- body.params.options.should.have.a.property('device').that.equals('tablet');
- //body.params.options.should.have.a.property('waitForSelector').that.equals('*');
- body.params.options.should.have.a.property('cookie').that.equals('foo=bar;domain=google.com');
- body.params.options.should.have.a.property('authUser').that.equals('joe');
- body.params.options.should.have.a.property('authPass').that.equals('secret');
-
- // Check if the screenshot temporary file was correctly removed
- body.params.options.should.not.have.a.property('screenshot');
- // Check if the screenshot buffer was correctly removed
- body.should.not.have.a.property('screenshotBuffer');
- // Check if the screenshot url is here
- body.should.have.a.property('screenshotUrl');
- body.screenshotUrl.should.equal('api/results/' + body.runId + '/screenshot.jpg');
-
- screenshotUrl = body.screenshotUrl;
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should launch a run without waiting for the response', function(done) {
- this.timeout(5000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: wwwUrl + '/simple-page.html',
- waitForResponse: false,
- jsTimeline: true
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- asyncRunId = body.runId;
- asyncRunId.should.be.a('string');
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should respond run status: running', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/runs/' + asyncRunId,
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.runId.should.equal(asyncRunId);
- body.status.should.deep.equal({
- statusCode: 'running'
- });
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should accept up to 10 anonymous runs to the API', function(done) {
- this.timeout(5000);
-
- function launchRun() {
- var deferred = Q.defer();
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: wwwUrl + '/simple-page.html',
- waitForResponse: false
- },
- json: true
- }, function(error, response, body) {
-
- lastRunId = body.runId;
-
- if (error) {
- deferred.reject(error);
- } else {
- deferred.resolve(response, body);
- }
- });
-
- return deferred.promise;
- }
-
- launchRun()
- .then(launchRun)
- .then(launchRun)
- .then(launchRun)
- .then(launchRun)
-
- .then(function(response, body) {
-
- // Here should still be ok
- response.statusCode.should.equal(200);
-
- launchRun()
- .then(launchRun)
- .then(launchRun)
- .then(launchRun)
- .then(launchRun)
- .then(launchRun)
-
- .then(function(response, body) {
-
- // It should fail now
- response.statusCode.should.equal(429);
- done();
-
- })
- .fail(function(error) {
- done(error);
- });
-
- }).fail(function(error) {
- done(error);
- });
-
- });
-
-
- it('should respond 404 to unknown runId', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/runs/unknown',
- json: true
- }, function(error, response, body) {
- if (!error && response.statusCode === 404) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should respond 404 to unknown result', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/unknown',
- json: true
- }, function(error, response, body) {
- if (!error && response.statusCode === 404) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should respond status complete to the first run', function(done) {
- this.timeout(12000);
-
- function checkStatus() {
- request({
- method: 'GET',
- url: serverUrl + '/api/runs/' + asyncRunId,
- json: true
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.runId.should.equal(asyncRunId);
-
- if (body.status.statusCode === 'running') {
- setTimeout(checkStatus, 250);
- } else if (body.status.statusCode === 'complete') {
- done();
- } else {
- done(body.status.statusCode);
- }
-
- } else {
- done(error || response.statusCode);
- }
- });
- }
-
- checkStatus();
- });
-
-
- it('should find the result of the async run', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId,
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('runId').that.equals(asyncRunId);
- body.should.have.a.property('params').that.is.an('object');
- body.params.url.should.equal(wwwUrl + '/simple-page.html');
-
- body.should.have.a.property('scoreProfiles').that.is.an('object');
- body.scoreProfiles.should.have.a.property('generic').that.is.an('object');
- body.scoreProfiles.generic.should.have.a.property('globalScore').that.is.a('number');
- body.scoreProfiles.generic.should.have.a.property('categories').that.is.an('object');
-
- body.should.have.a.property('rules').that.is.an('object');
-
- body.should.have.a.property('toolsResults').that.is.an('object');
- body.toolsResults.should.have.a.property('phantomas').that.is.an('object');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should return the generic score object', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '/generalScores',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
- body.should.have.a.property('globalScore').that.is.a('number');
- body.should.have.a.property('categories').that.is.an('object');
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should return the generic score object also', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/generic',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
- body.should.have.a.property('globalScore').that.is.a('number');
- body.should.have.a.property('categories').that.is.an('object');
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should not find an unknown score object', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/unknown',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 404) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should return the rules', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '/rules',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- var firstRule = body[Object.keys(body)[0]];
- firstRule.should.have.a.property('policy').that.is.an('object');
- firstRule.should.have.a.property('value').that.is.a('number');
- firstRule.should.have.a.property('bad').that.is.a('boolean');
- firstRule.should.have.a.property('abnormal').that.is.a('boolean');
- firstRule.should.have.a.property('score').that.is.a('number');
- firstRule.should.have.a.property('abnormalityScore').that.is.a('number');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should return the phantomas results', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '/toolsResults/phantomas',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('metrics').that.is.an('object');
- body.should.have.a.property('offenders').that.is.an('object');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should return the entire object and exclude toolsResults', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '?exclude=toolsResults',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('runId').that.equals(asyncRunId);
- body.should.have.a.property('params').that.is.an('object');
- body.should.have.a.property('scoreProfiles').that.is.an('object');
- body.should.have.a.property('rules').that.is.an('object');
-
- body.should.not.have.a.property('toolsResults').that.is.an('object');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should return the entire object and exclude params and toolsResults', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '?exclude=toolsResults,params',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('runId').that.equals(asyncRunId);
- body.should.have.a.property('scoreProfiles').that.is.an('object');
- body.should.have.a.property('rules').that.is.an('object');
-
- body.should.not.have.a.property('params').that.is.an('object');
- body.should.not.have.a.property('toolsResults').that.is.an('object');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should return the entire object and don\'t exclude anything', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '?exclude=',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('runId').that.equals(asyncRunId);
- body.should.have.a.property('scoreProfiles').that.is.an('object');
- body.should.have.a.property('rules').that.is.an('object');
- body.should.have.a.property('params').that.is.an('object');
- body.should.have.a.property('toolsResults').that.is.an('object');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should return the entire object and don\'t exclude anything', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/' + asyncRunId + '?exclude=null',
- json: true,
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
-
- body.should.have.a.property('runId').that.equals(asyncRunId);
- body.should.have.a.property('scoreProfiles').that.is.an('object');
- body.should.have.a.property('rules').that.is.an('object');
- body.should.have.a.property('params').that.is.an('object');
- body.should.have.a.property('toolsResults').that.is.an('object');
-
- done();
-
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should retrieve the screenshot', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/' + screenshotUrl
- }, function(error, response, body) {
- if (!error && response.statusCode === 200) {
- response.headers['content-type'].should.equal('image/jpeg');
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-
- it('should fail on a unexistant screenshot', function(done) {
- this.timeout(5000);
-
- request({
- method: 'GET',
- url: serverUrl + '/api/results/000000/screenshot.jpg'
- }, function(error, response, body) {
- if (!error && response.statusCode === 404) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
- it('should refuse a query on a blocked Url', function(done) {
- this.timeout(5000);
-
- request({
- method: 'POST',
- url: serverUrl + '/api/runs',
- body: {
- url: 'http://www.test.com/something.html',
- waitForResponse: false
- },
- json: true,
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
- }
- }, function(error, response, body) {
- if (!error && response.statusCode === 403) {
- done();
- } else {
- done(error || response.statusCode);
- }
- });
- });
-
-});
diff --git a/test/api/resultsDatastoreTest.js b/test/api/resultsDatastoreTest.js
deleted file mode 100644
index da181e7..0000000
--- a/test/api/resultsDatastoreTest.js
+++ /dev/null
@@ -1,121 +0,0 @@
-var should = require('chai').should();
-var resultsDatastore = require('../../lib/server/datastores/resultsDatastore');
-
-var fs = require('fs');
-var path = require('path');
-
-describe('resultsDatastore', function() {
-
- var datastore = new resultsDatastore();
-
- var testId1 = '123456789';
- var testData1 = {
- runId: testId1,
- other: {
- foo: 'foo',
- bar: 1
- }
- };
-
-
- it('should store a result', function(done) {
- datastore.should.have.a.property('saveResult').that.is.a('function');
-
- datastore.saveResult(testData1).then(function() {
- done();
- }).fail(function(err) {
- done(err);
- });
- });
-
- it('should store another result', function(done) {
- var testData2 = {
- runId: '987654321',
- other: {
- foo: 'foo',
- bar: 2
- }
- };
-
- datastore.saveResult(testData2).then(function() {
- done();
- }).fail(function(err) {
- done(err);
- });
- });
-
- it('should retrieve a result', function(done) {
- datastore.getResult(testId1)
- .then(function(results) {
-
- // Compare results with testData
- results.should.deep.equal(testData1);
-
- done();
- }).fail(function(err) {
- done(err);
- });
- });
-
- it('should delete a result', function(done) {
- datastore.deleteResult(testId1)
- .then(function() {
- done();
- }).fail(function(err) {
- done(err);
- });
- });
-
- it('should not find the result anymore', function(done) {
- datastore.getResult(testId1)
- .then(function(results) {
- done('Error, the result is still in the datastore');
- }).fail(function(err) {
- done();
- });
- });
-
-
- var testId3 = '555555';
- var testData3 = {
- runId: testId3,
- other: {
- foo: 'foo',
- bar: 2
- },
- screenshotBuffer: fs.readFileSync(path.join(__dirname, '../fixtures/logo-large.png'))
- };
-
- it('should store a test with a screenshot', function(done) {
-
- datastore.saveResult(testData3).then(function() {
- done();
- }).fail(function(err) {
- done(err);
- });
- });
-
- it('should have a normal result', function(done) {
- datastore.getResult(testId3)
- .then(function(results) {
-
- results.should.not.have.a.property('screenshot');
-
- done();
- })
- .fail(function(err) {
- done(err);
- });
- });
-
- it('should retrieve the saved image', function() {
- datastore.getScreenshot(testId3)
- .then(function(imageBuffer) {
- imageBuffer.should.be.an.instanceof(Buffer);
- done();
- })
- .fail(function(err) {
- done(err);
- });
- });
-});
diff --git a/test/api/runsDatastoreTest.js b/test/api/runsDatastoreTest.js
deleted file mode 100644
index ccc1e95..0000000
--- a/test/api/runsDatastoreTest.js
+++ /dev/null
@@ -1,82 +0,0 @@
-var should = require('chai').should();
-var runsDatastore = require('../../lib/server/datastores/runsDatastore');
-
-describe('runsDatastore', function() {
-
- var datastore = new runsDatastore();
-
- var firstRunId = 333;
- var secondRunId = 999;
-
- it('should accept new runs', function() {
- datastore.should.have.a.property('add').that.is.a('function');
-
- datastore.add({
- runId: firstRunId,
- otherData: 123456789
- }, 0);
-
- datastore.add({
- runId: secondRunId,
- otherData: 'whatever'
- }, 1);
- });
-
- it('should have stored the runs with a status "runnung"', function() {
- datastore.should.have.a.property('get').that.is.a('function');
-
- var firstRun = datastore.get(firstRunId);
- firstRun.should.have.a.property('runId').that.equals(firstRunId);
- firstRun.should.have.a.property('status').that.deep.equals({
- statusCode: 'running'
- });
-
- var secondRun = datastore.get(secondRunId);
- secondRun.should.have.a.property('runId').that.equals(secondRunId);
- secondRun.should.have.a.property('status').that.deep.equals({
- statusCode: 'awaiting',
- position: 1
- });
-
- });
-
- it('should have exactly 2 runs in the store', function() {
- var runs = datastore.list();
- runs.should.be.a('array');
- runs.should.have.length(2);
- runs[0].should.have.a.property('runId').that.equals(firstRunId);
- });
-
- it('shoud update statuses correctly', function() {
-
- datastore.markAsComplete(firstRunId);
- var firstRun = datastore.get(firstRunId);
- firstRun.should.have.a.property('status').that.deep.equals({
- statusCode: 'complete'
- });
-
- datastore.updatePosition(secondRunId, 0);
- var secondRun = datastore.get(secondRunId);
- secondRun.should.have.a.property('status').that.deep.equals({
- statusCode: 'running'
- });
-
- datastore.markAsFailed(secondRunId, 'Error message');
- secondRun = datastore.get(secondRunId);
- secondRun.should.have.a.property('status').that.deep.equals({
- statusCode: 'failed',
- error: 'Error message'
- });
-
- });
-
- it('should delete a run', function() {
- datastore.delete(firstRunId);
-
- var runs = datastore.list();
- runs.should.be.a('array');
- runs.should.have.length(1);
-
- runs[0].should.have.a.property('runId').that.equals(secondRunId);
- });
-});
diff --git a/test/api/runsQueueTest.js b/test/api/runsQueueTest.js
deleted file mode 100644
index df306cd..0000000
--- a/test/api/runsQueueTest.js
+++ /dev/null
@@ -1,68 +0,0 @@
-var should = require('chai').should();
-var runsQueue = require('../../lib/server/datastores/runsQueue');
-
-describe('runsQueue', function() {
-
- var queue = new runsQueue();
- var aaaRun = null;
- var bbbRun = null;
- var cccRun = null;
-
- it('should accept a new runId', function(done) {
- queue.should.have.a.property('push').that.is.a('function');
-
- aaaRun = queue.push('aaa');
- bbbRun = queue.push('bbb');
-
- aaaRun.then(function() {
- done();
- });
- });
-
- it('should return the right positions', function() {
- var aaaPosition = queue.getPosition('aaa');
- aaaPosition.should.equal(0);
- aaaRun.startingPosition.should.equal(0);
-
- var bbbPosition = queue.getPosition('bbb');
- bbbPosition.should.equal(1);
- bbbRun.startingPosition.should.equal(1);
-
- var cccPosition = queue.getPosition('ccc');
- cccPosition.should.equal(-1);
- });
-
- it('should refresh runs\' positions', function(done) {
- cccRun = queue.push('ccc');
-
- cccRun.progress(function(position) {
- position.should.equal(1);
-
- var positionDoubleCheck = queue.getPosition('ccc');
- positionDoubleCheck.should.equal(1);
-
- done();
- });
-
- queue.remove('aaa');
- });
-
- it('should fulfill the promise when first in the line', function(done) {
- cccRun.then(function() {
- done();
- });
-
- queue.remove('bbb');
- });
-
- it('should not keep removed runs', function() {
- var aaaPosition = queue.getPosition('aaa');
- aaaPosition.should.equal(-1);
-
- var bbbPosition = queue.getPosition('bbb');
- bbbPosition.should.equal(-1);
-
- var cccPosition = queue.getPosition('ccc');
- cccPosition.should.equal(0);
- });
-});
\ No newline at end of file
diff --git a/test/api/screenshotHandlerTest.js b/test/api/screenshotHandlerTest.js
deleted file mode 100644
index 82a77bd..0000000
--- a/test/api/screenshotHandlerTest.js
+++ /dev/null
@@ -1,78 +0,0 @@
-var should = require('chai').should();
-var ScreenshotHandler = require('../../lib/screenshotHandler');
-
-var fs = require('fs');
-var path = require('path');
-var rimraf = require('rimraf');
-
-describe('screenshotHandler', function() {
-
- var imagePath = path.join(__dirname, '../fixtures/logo-large.png');
- var screenshot, jimpImage;
-
-
- it('should open an image and return an jimp object', function(done) {
- ScreenshotHandler.openImage(imagePath)
- .then(function(image) {
- jimpImage = image;
-
- jimpImage.should.be.an('object');
- jimpImage.bitmap.width.should.equal(620);
- jimpImage.bitmap.height.should.equal(104);
-
- done();
- })
- .fail(function(err) {
- done(err);
- });
- });
-
-
- it('should resize an jimp image', function(done) {
- ScreenshotHandler.resizeImage(jimpImage, 310)
- .then(function(image) {
- jimpImage = image;
-
- jimpImage.bitmap.width.should.equal(310);
- jimpImage.bitmap.height.should.equal(52);
-
- done();
- })
- .fail(function(err) {
- done(err);
- });
- });
-
-
- it('should transform a jimp image into a buffer', function(done) {
- ScreenshotHandler.toBuffer(jimpImage)
- .then(function(buffer) {
- buffer.should.be.an.instanceof(Buffer);
- done();
- })
- .fail(function(err) {
- done(err);
- });
- });
-
-
- it('should create the tmp folder if it doesn\'t exist', function(done) {
- // Delete tmp folder if it exists
- rimraf.sync("/some/directory");
-
- // The function we want to test
- ScreenshotHandler.createTmpScreenshotFolder()
- .then(function(buffer) {
- fs.existsSync(path.join(__dirname, '../../tmp')).should.equal(true);
- done();
- })
- .fail(function(err) {
- done(err);
- });
- });
-
- it('should return the tmp folder path', function() {
- ScreenshotHandler.getTmpFileRelativePath().should.equal('tmp/temp-screenshot.png');
- });
-
-});
diff --git a/test/gatling/README.md b/test/gatling/README.md
deleted file mode 100644
index a15c7ca..0000000
--- a/test/gatling/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Stress tests with Gatling
-
-[Gatling](http://gatling.io/) is an open source stress test tool
-
-Objective: 500 simultaneous users on the website
-
-The YLTWebInterfaceSimulation.scala file is a scenario simulating a user coming on the web page and launching a run.
-
diff --git a/test/gatling/YLTWebInterfaceSimulation.scala b/test/gatling/YLTWebInterfaceSimulation.scala
deleted file mode 100644
index 4f2393b..0000000
--- a/test/gatling/YLTWebInterfaceSimulation.scala
+++ /dev/null
@@ -1,42 +0,0 @@
-package computerdatabase // 1
-
-import io.gatling.core.Predef._ // 2
-import io.gatling.http.Predef._
-import scala.concurrent.duration._
-
-class YLTWebInterfaceSimulation extends Simulation {
-
- val httpConf = http
- .baseURL("http://localhost:8383")
- .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
- .doNotTrackHeader("1")
- .acceptLanguageHeader("en-US,en;q=0.5")
- .acceptEncodingHeader("gzip, deflate")
- .userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0")
-
- val scn = scenario("YLTWebInterfaceSimulation")
- .exec(http("home page")
- .get("/")
- )
- .exec(http("static asset")
- .get("/front/fonts/icons.woff")
- )
- .pause(100 milliseconds)
- .exec(http("launch run")
- .post("/api/runs")
- .body(StringBody("""{ "url": "http://www.google.com", "waitForResponse":false }""")).asJSON
- )
- .repeat(10, "loop") {
- exec(http("get status")
- .get("/api/runs/dzlqsahu8d")
- )
- .pause(2000 milliseconds)
- }
- .exec(http("get result")
- .get("/api/results/dzlqsahu8d")
- )
-
- setUp(
- scn.inject(rampUsers(1000) over(60 seconds))
- ).protocols(httpConf)
-}
\ No newline at end of file