From 2753446a576610a16ff94648bdaa1011aa23febf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 24 Apr 2021 01:15:45 +0100 Subject: [PATCH 1/5] Remove unneeded package ps-node --- lib/tools/phantomas/phantomasWrapper.js | 1 - package.json | 1 - 2 files changed, 2 deletions(-) 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..71ebf91 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "meow": "5.0.0", "parse-color": "1.0.0", "phantomas": "github:macbre/phantomas#devel", - "ps-node": "0.1.6", "q": "1.5.1", "request": "2.88.0", "rimraf": "2.6.3", From 97f779dc83663d918435906788638a1e5b767d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 24 Apr 2021 01:18:39 +0100 Subject: [PATCH 2/5] Remove uneeded module temporary --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 71ebf91..d0dd390 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "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" From c9f63138a0b9d2ec62e9068f22acb8d7fd7a7ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 24 Apr 2021 02:40:30 +0100 Subject: [PATCH 3/5] Split YLT into 2 projects. The server is now in the project YellowLabTools/YellowLabTools-server --- .gitignore | 3 - .travis.yml | 18 - Gruntfile.js | 252 ------- bin/cli.js | 0 bin/server.js | 49 -- front/src/css/about.css | 18 - front/src/css/dashboard.css | 212 ------ front/src/css/index.css | 196 ----- front/src/css/main.css | 280 ------- front/src/css/queue.css | 21 - front/src/css/rule.css | 277 ------- front/src/css/screenshot.css | 17 - front/src/img/favicon-fail.png | Bin 706 -> 0 bytes front/src/img/favicon-success.png | Bin 551 -> 0 bytes front/src/img/favicon.png | Bin 614 -> 0 bytes front/src/img/logo-large.png | Bin 14666 -> 0 bytes front/src/js/app.js | 85 --- front/src/js/controllers/dashboardCtrl.js | 37 - front/src/js/controllers/indexCtrl.js | 23 - front/src/js/controllers/queueCtrl.js | 100 --- front/src/js/controllers/ruleCtrl.js | 78 -- front/src/js/controllers/screenshotCtrl.js | 30 - front/src/js/directives/gradeDirective.js | 33 - .../src/js/directives/offendersDirectives.js | 307 -------- front/src/js/models/resultsFactory.js | 7 - front/src/js/models/runsFactory.js | 7 - front/src/js/services/apiService.js | 64 -- front/src/js/services/menuService.js | 31 - front/src/js/services/settingsService.js | 24 - front/src/less/about.less | 20 - front/src/less/dashboard.less | 199 ----- front/src/less/index.less | 223 ------ front/src/less/main.less | 298 -------- front/src/less/queue.less | 24 - front/src/less/rule.less | 307 -------- front/src/less/screenshot.less | 16 - front/src/main.html | 66 -- front/src/views/about.html | 13 - front/src/views/dashboard.html | 72 -- front/src/views/index.html | 117 --- front/src/views/queue.html | 46 -- front/src/views/resultSubHeader.html | 7 - front/src/views/rule.html | 514 ------------- front/src/views/screenshot.html | 13 - lib/screenshotHandler.js | 141 ---- lib/server/controllers/apiController.js | 352 --------- lib/server/controllers/frontController.js | 45 -- lib/server/datastores/resultsDatastore.js | 136 ---- lib/server/datastores/runsDatastore.js | 122 ---- lib/server/datastores/runsQueue.js | 90 --- lib/server/middlewares/apiLimitsMiddleware.js | 95 --- lib/server/middlewares/authMiddleware.js | 42 -- .../middlewares/wwwRedirectMiddleware.js | 12 - package.json | 45 +- server_config/error50x.html | 30 - server_config/maintenance.js | 13 - server_config/server_install.sh | 28 - server_config/server_update.sh | 24 - server_config/settings-prod.json | 23 - server_config/settings.json | 23 - test/api/apiTest.js | 681 ------------------ test/api/resultsDatastoreTest.js | 121 ---- test/api/runsDatastoreTest.js | 82 --- test/api/runsQueueTest.js | 68 -- test/api/screenshotHandlerTest.js | 78 -- test/gatling/README.md | 8 - test/gatling/YLTWebInterfaceSimulation.scala | 42 -- 67 files changed, 6 insertions(+), 6399 deletions(-) delete mode 100644 .travis.yml delete mode 100644 Gruntfile.js mode change 100644 => 100755 bin/cli.js delete mode 100644 bin/server.js delete mode 100644 front/src/css/about.css delete mode 100644 front/src/css/dashboard.css delete mode 100644 front/src/css/index.css delete mode 100644 front/src/css/main.css delete mode 100644 front/src/css/queue.css delete mode 100644 front/src/css/rule.css delete mode 100644 front/src/css/screenshot.css delete mode 100644 front/src/img/favicon-fail.png delete mode 100644 front/src/img/favicon-success.png delete mode 100644 front/src/img/favicon.png delete mode 100644 front/src/img/logo-large.png delete mode 100644 front/src/js/app.js delete mode 100644 front/src/js/controllers/dashboardCtrl.js delete mode 100644 front/src/js/controllers/indexCtrl.js delete mode 100644 front/src/js/controllers/queueCtrl.js delete mode 100644 front/src/js/controllers/ruleCtrl.js delete mode 100644 front/src/js/controllers/screenshotCtrl.js delete mode 100644 front/src/js/directives/gradeDirective.js delete mode 100644 front/src/js/directives/offendersDirectives.js delete mode 100644 front/src/js/models/resultsFactory.js delete mode 100644 front/src/js/models/runsFactory.js delete mode 100644 front/src/js/services/apiService.js delete mode 100644 front/src/js/services/menuService.js delete mode 100644 front/src/js/services/settingsService.js delete mode 100644 front/src/less/about.less delete mode 100644 front/src/less/dashboard.less delete mode 100644 front/src/less/index.less delete mode 100644 front/src/less/main.less delete mode 100644 front/src/less/queue.less delete mode 100644 front/src/less/rule.less delete mode 100644 front/src/less/screenshot.less delete mode 100644 front/src/main.html delete mode 100644 front/src/views/about.html delete mode 100644 front/src/views/dashboard.html delete mode 100644 front/src/views/index.html delete mode 100644 front/src/views/queue.html delete mode 100644 front/src/views/resultSubHeader.html delete mode 100644 front/src/views/rule.html delete mode 100644 front/src/views/screenshot.html delete mode 100644 lib/screenshotHandler.js delete mode 100644 lib/server/controllers/apiController.js delete mode 100644 lib/server/controllers/frontController.js delete mode 100644 lib/server/datastores/resultsDatastore.js delete mode 100644 lib/server/datastores/runsDatastore.js delete mode 100644 lib/server/datastores/runsQueue.js delete mode 100644 lib/server/middlewares/apiLimitsMiddleware.js delete mode 100644 lib/server/middlewares/authMiddleware.js delete mode 100644 lib/server/middlewares/wwwRedirectMiddleware.js delete mode 100644 server_config/error50x.html delete mode 100644 server_config/maintenance.js delete mode 100644 server_config/server_install.sh delete mode 100755 server_config/server_update.sh delete mode 100644 server_config/settings-prod.json delete mode 100644 server_config/settings.json delete mode 100644 test/api/apiTest.js delete mode 100644 test/api/resultsDatastoreTest.js delete mode 100644 test/api/runsDatastoreTest.js delete mode 100644 test/api/runsQueueTest.js delete mode 100644 test/api/screenshotHandlerTest.js delete mode 100644 test/gatling/README.md delete mode 100644 test/gatling/YLTWebInterfaceSimulation.scala 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/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 8192c94422bb4873fcf8c667ea4b03ef879ee198..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 706 zcmV;z0zLhSP)kl90&0JE6wgXS)H-@cu!O#5g+AbBb~sr z4o7(Hmx#Zwg9QY0J>WrzJ(2xJA}Qp2?CfA1*oni)Nk~Fq7OjrJYOXEn$LYFp?%Iq-~ldOMhC z5@=|Jd$Ru{QCr*czH}m(;bNbOvQMG9nVqa4xh;Uqvd?U&<%$&uw(T(U9nSCY7 z8bW16D+bsR%)!7PKC0+Zk)jfoZ(}Rmmi=!fcPkqcDg-p;PfCF|#66(Ybz()Z0JBd- zJ*t9&Xn6q@(G5g5h_s#PT3LssPk^&ZgEhZF&d2W$xVtnxypg7It&VzEFwJNr%)vjFfh@YSl>e5le8=(%sbCMTXz2ug z6KSIyEdXr7dK!UGM49X=*aKaJ@9sd>eleR+@S;Xl0SYfuj{rMyPKt%{jGNma30P>e}9MdqHmH+?%07*qoM6N<$f?i=eRR910 diff --git a/front/src/img/favicon-success.png b/front/src/img/favicon-success.png deleted file mode 100644 index 09f498fbbd27b7605817b219d2fbd38cb9efb097..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 551 zcmV+?0@(eDP)s}u05ji!_hz&gJt}IoNVX* zJI2OVd7*(W1}nR*0MHKT%B23ic4$~069zF=HlPQy z-X_gY3eS_gB6GOr2Vn|=vH-!=|8KJ6TAERJ=D0|hf}nIjhat@HUeSJ`1ZDWlViw4A|am{x!zMu{RvwvV8W`0JHL0lxDFSFjk z(Nw|<^DJ?67T5e#AVx?LfZ#fz{!NaXgw4!%OD4Ea6T}E96p$Rn46jM|h2jcFH*ZRg zVfuFpLJ(L00AF|rr2#rmaGk+){UlhyPy!@4*L|ut0IXmr0sLD9 zCh9TycL)jKIb6>G7)pT95~2Q6Z|<-PF5?oQ!vMUC^#-s!rVjmB+CAut8&9@Jq<>4= pPo*RT+8&a3H+2{!AJYa+{{YC7(v>DqbY1`e002ovPDHLkV1nBK`(OY7 diff --git a/front/src/img/favicon.png b/front/src/img/favicon.png deleted file mode 100644 index 23ff5bc5698141afb0568fad80dd13f5b12f825b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614 zcmV-s0-61ZP)M_R5daF*@)8A>`&Jl`SKa>;#0(bVW zt{PE?j7$ION>bvWB!Kj9^O=eGI-l9NxSo;#$DFN7Yp;yvL)@skWXJMqV0az!;~dtB zfg)&<5>85jT#ocdKoByUbAS2_mUpvQE87#fKmUBee*uEH&W{v;AZRKSyq(3~(ha9a zP$yu3^ixAcP;|NfKylp|%l|NkRo!Q#Du81S(IBWDFckeCqCd=L-5kePN~u-y#6~2f zpBRXUVwzU87eiC`O6u$dI@11l3ish;u38pnm|lYwR_WCYw%=&UMu1_?t=(`KB|;LLDg)&k(kl<1<# ziQQX@45_7zFL3LvuV=EU3*OIW88e4>5 zeB|m=2d=3I!QwU&dId9^$szLN7 AUjP6A diff --git a/front/src/img/logo-large.png b/front/src/img/logo-large.png deleted file mode 100644 index 88d1971904420a8d232c29ff14c8168d2e128525..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14666 zcmcJ0byOU|w{rF+u#;Fgy1m1;5rPhVfnqc zd*1%PXZ!RyU0tW{{i<$#Rek&3?pO^qd29?)3dELvwl_-ILf3_5D=lG6%Wwk1qU*3rqZYg3%hFYeYIq*@?hIeqtOr*Hk20rYQ-}e z$wEgj&BCh4_)(UYPVys@96f_H9laC{tt2yxg1LoHM#fJs?=Q+KpCcl_Sz3XFMD)I; z7bhm=Gcn6&XO}%ZJofaC`uN7)KRj$~Y#0BmHKdFku>6%d%Tzkg&3^vcVxsHkkovg348<5$~X$TV94N zEv=WAHv`SQCno+VC|Wi(^|ZA1X^RW*r&G(np=7wA0Ml$ z+qii&Ca30pjs50g6}v4U&v)SbYQ-S@W+pur>CdivkSU<&K>^D zv$Km&PtWWe%6|R{yL*SmCLTt{?)BcR3k$0WiFtST_dB}>42-hl#SlE#isp}`?v#l#IKC+9l5hDysC=jT_4h9<$_;r@Z~kgqf53#vA^c4lVgPflR4bNJrg5d^Y56T?(d(el-bGue`@ z+9*H(0f7%$QASb+^yXwK%3=@!3qdl9VA$F#`*x##(&^)#OAO%c>$3TOI{K{sNBsBU zP33_PVkpBy#8!FS)(^7lH=G5Zb?4OnqDZ97PsutXOxk*5!kG~w0ew|(LWfJL9U93I z{ zipJ5$yM-(PdJ_pi*h0nyzUutX4L`zb@&B$5-H`mh+q`=RBOmr0{o<<@-n)HuKAnB; z(GL;j9BYWyBo}XR7Cv5IG8AiYUTyu)xxI3WRB|QO&HEl4uXgQ~Z{{}{vV|oTV{bkb z9^BpzqJ0yFznw@YrlEPC$G{CHlv3ipWVVMOij%fDSvSl5;B%gGKP3$-Iu~?KF8|$6 z1O6a+Uzyg^C0sTj|L2dc9E}GvH|^@O_ErJwr%oyj{ZE~|QAKBma^&FhFC?OPME-`6 zZTu#G8AbKE9%%f%0mWt^cM5+(!$yAeH~cE3Mnb7993%m%zZd&W(to}eky6Y$AncE| z#_eN%mS6=EEt>&p65x|k%WE;l>@2y`kGt_cp(EgrG9bs*^ZQYqB zk6!f5Ny=w&*c91OSiCPZd@kYLQ^pUmD>;bK znOoPcAlnQcn~}|cRi*6WHhtSyQA}Z*_+sS{gZS0#?j_at!05%^Ez{x&qXtB@QV{au zEpPOqna?TlypK8->*+dU((`ryITyGG8+P@Fp$e6mwmjmC66u%!$VoXZ(iSCAMC8jU zj-Jj8pRc^yp}djZk+D@WYH877-sV4O6Y6n7-Va#SdCrVSYxfUW9D~Oj=fodvii`@8 zq&nO*Z(9zZ1l|uiTKocOUUKx0K$f3nOnd&T^UffvJnK^j`bdwOa>3c_BRAQJ;{N8` zo#w-lSt=J3OHg;k@KkG2Y01Dq&1zASZC~$}HRwGgyt6vJJm&X!rnjG2mnVN#K>l$1 z3)K?(NWL{lv=XMFc}dA9A(NaDW97pzc>D&d?s#IQvjuJ2e2VHZ=aJ8qa2rk_Ll6y6 zca8VIAx{~qc3^SvrFkL)z8bV9umzU0 z|EE6KHc&)Ssu?SAA6h@%I2Ibcm#XfesuWL zi1H7cV{qc!MYwQcg-qt}?DC@jr&lf{wkH;yg~UoKBO0Sk@E+c z!SBJu*UBN0SI26a`CSA5ZM(SXMm;pX@ozlAxzqo_>iNfhl-v+P6Zv-zN&+AH??y`c z%A~x)Qii9F+{FKd)w>t}@JRU!9kY>N9@`gocJAYt=SQ8l9pOi=H{Vv+SBz4hSXR8@Yr^9%Z`0q}#N6>(3( z&^QU33;hr1chQJN-#P*X%a7XdB9rj87!qe&*ukX6%W+p_`I&=x&sx|K`%(98_sjE3 zRi*e#PU4XxWt?x%pMaK)mEfo}BpqwEUDEnd`>Oc(_-g%|i;eX>a#^rTo!a>N{cf+T z_oD`w+|47vZ!53L^n74h0-UOE28A5QnbGK3pO$SP-aOgi^$>g3#`wakl-WFeMrWDG z@!K5P*=u3RT#UIf094NwtX`{kv;O%jgtj1Pb4 zM{GjVv64;H{I2Pxje*|pVdtA!yinbRe-RU8ti89U)pdf6Ah;Gzfy(*6ZNZim0`*Eo-L;Wa|g|DUz5IM&%7gVcYEp222WUV zSq$fPe_rnK1UwSe3B^l!yWD5fBfD^By3L$WAG9-ZQr%#rzs=|h_Z}ypRsW!Hs*g*H zsC-ry@P%m@*HzkHc{GZgV1I}5g3i9&F>#mAQLVrtM;j|6grOrwTLIV;eqX9Kit8GD zHaGdDa-%AdV{Q1$oOj2A-eeG;=Z&?R$;3ucWRQ?{ag*7#oA0U`{rM;_-|jVkxs42BRjoKnCpmK#h~6q_wj%7o zl>Rz#TGBU5dZ(5VOR)GX`lx*9yNd~i-tyKQmtpuQ$wjM8ldDoy{xHj|n5cG^^rTh* zM-SV=dMYS8Vm_!Ztd{%_5Q!YEj-FdaC>)fZyPJk&I7DtwbP?$wq4i`N?6HyG4O;j0 z8G(J;u;y9i)|N6o(KdXPhs^N4+MdfEtDkWOas)fe z^N#Xxb_!vN#0$;+DSi1pXj$AmIvSW`Ui2DvIJloX$*VPf-v}fc z$b0T?DrR>N1=Khi0pzRGa&(Na_w2MdRdW%lWBukQ7MTReL+948eP@SrKiro`8tks` zDl^znLl(Z-Rtzy8iL&iGQ(XNcXK@sPnBL=6_UvUrPm?A;QZF!xF{;0<{di1S9^?I# zTqDp6YK5LsG@k1UZQx~bM{44YhESwyX@AzFzh;awJbsr6(ZKWh>k^aKm<+-MbCjbC zPq$#@p6YkG*`zkIe{iMc&}7}xzBc=SL=(R#Xd#qdPxEnId0ZQuDl=nxk|W>LQP&@w z&MVgC{&K@_VCE?S@1*lGp*#7dc&3HODA&{RrM}o!_q&e|bpEWiu{>ipi`Ct$)gON@ z*AJBpeK0rcA8%sgsXw`e>SL>naLgj_oncN+J-zM2&+H%i{^^sPoc%%r?Z4m+aI1>) z+ZvkqHeOdN91BGg*$Ofac;|zP!_sY*>$?cH`)ga<+kefl7h%sQoRW8pz>-_Jn_}*> zaY^2GycX(eUIVK2A6~-Kd4utex zX0bx9g1+F_O(>LBq{~{zl-{lLs46(;#ghD_jXQnD@1ZXh6W-tb7|^Dq?O0je{anss z1|K=NdQ0FXRAF@?72BU_7ue!C44(N*(zEvRgN+db9sd+1Now?01wY2L(9-3hmJ!C z?m)~VXFc>VfxprRkwuT01gRyg9Qr|p&qQcs-lez6tbjlG>Tatn$j`crq0Z6&sxH^} ztvsCXF8v@VV$a>naAWM`#GuB9hOg^~*OSAj(V)T+u)C{<*|9h4*_cKA!dE5elqHD{ z*tV%l9nEdM@PMkA4dFl?02Q>k{)u1_jJ~+;E$AEXT0WG+(>LF~*dg-v%N<$00LvG% zwDvmE`Iz?c21C6I&1Q{tx3akm(((@oI!?osY5?^c;erFdD8|+NVD}n-ib`w$0w<8q z?VmGrQP)%I){N)k0j#TzyLJ~s5xdh3Wz($$u8ztL78&PWHf9EAS)XkPd@PsdoN-pM zr-U82R=#chOG2$4VBeSj9BqA4NqP6P{iOg`7i@uwh0jCHkt#$0ZpSBSwEd_%orI`Sh9V)M0)NQ@y*|&)Q!*pCJg`MdhZvs7Mw_ z0_CIVzgKGET;>SeKcD==W(l&@HyjmAlzi`mM0Tn6^O4L&VtGFsUz^jDU~Pf4E)sqq zA8r5Z$KWC;_LeUA>w`a|=gaip>0kT@VPN;SduXX@%p>-Ju;yDW!=5~$_&2b>aYj;2 zwzu$nwULdKC7vrOv*>^vCkCLeGz`W2Efy&RiJpxT{eV-WPRWI9uSTDf6a$I)PN(R6 z)L<&8oqmX5h_G&v8M%;Yto=xn3K{7q_LTP{`=7-cr%$MGKCCE>2=C{0w>18=+oW%z zI)}Gegub~t3u3Xp1&a3@_cywoJ$oOa9|X<6I>aNZr|)9Rx`-Tebu|kaNv4n+S$;z`Vz!|x7C&?frZHglSIuphhQl|2|N52 ztPm0m;uyQxT-Nxq?|fPR1_2ZJa*IbbXZ(@N3gAYCXSzW52hBNO1%Y`PvAK=DkfNqV z9fU;wyPB_|x@))E%4n{{#lQW?_U3%!n1Ie-fZ!@W6QAXT<24@_A&DuMiV~LP_*(~} zcXfY>RJHe3X1#}udl7x<*BH`U%+MPFCoM3}>y~7pI8c)?n(DOZMmbu=7<)j)i3xe3 zMqX`$p8jgl{(Ae(WGPcx@gy4qOd{!@!=f#VFMI^5(ks6BV?rRY`DbuFtHTP0=NC5z zG+j~Tn8_l`0%`L%i>g;DhJI%b)mW2SA1Ksqflco|ugZWMrG>h6LZUY!m(x-ci4I*O zyRr}&zH*C^n&b7Kl_0fXvf{fF`~@~T;0>?V7o}1t0YoG6*;F__1!swA^?tNugRn3= zehiqo=H?? z{{4uI_M^KZmj}qo3N6886|VlmM)sKS0*=2EnsR2-Z6uA~*Xm}E31~~(cBeILdBeL~ zWnE_%-s;&8HsB#cV{dKZ3w&>{4c{-_e>wZ;mOo|$j&U$f7>cuL)?XZ%U42|!s&Bl3 ziQ^sk^@u;lNyjY8hCI$2Re#8}gy&2%VuZi-mkUMC(Lx_S+8-n~ZWP({A}$2AMmK@D zv(@T{mUVRYweb`dEZ+FI{4iBUE-H}zM(%ss0$QUpRa?ujGliq$l;gl=Jn@0x!Wp#T z(6Sc#v}T(aHGO7gl`Hf^7p_5qDr)-^ghD^N7K+8VKY=9F1t9t_2PS!Y%Q0`O>zCr$ z9X*L*1vJE@Q#fUF*e%j$tyR@ieA4GQ24;_`NHO}*;v(9msV|i^rVii7D8k7FT?*UN z?!pU-7ybDSlzXwyre{~L(r0wRWH!O67JD!fMRpbUC_y>8bf=GnB7_2GmKFA*(&dhV zLk?-jznUZ}zb&%1gYx5~&{9`MEK4YJ%8I$EiW!VJ!tfG800xC>s&>o~{TO#VMFnM4 z1)$r4;wp!_MFh-|=%jO^x(CQypn2HeeLQc;IXxvOUBkz%Oxs7p7nPulJ7~=qm4frI zA3t%6ZfCnRhSj97kg25KtrUnizScup+PGVBTobHs^Pn2P+ywIo>yQgZ{WNAzZ$K4| zky4qV`i7(iEZ$q#6H>2ER~IqT|UCE363_Dl?=$8s6M&a`@X(mNTCo%^fbiew2~|JgWb&?UZc z%&65k$8~jIRy9xj8H<655{_J5{mdk$c*_`m_VsRAJcbo=fvR)Txy4mqviD{_FE`yB zBRfxN|1bl=*vF}=B~Nv+OGN1 zt3|cnRoe&U9}y%c1T;TNR-+rTqRZ;yOE2(-A~$LtcF{v)`L(2{qLKT;a&#T0p}K1t z9vGNUjRvft9hb!YeE=B1?f7jhEDHek_ zhj%V99)#(|EiUl(GF}V&lSC6bt2;^VO?6qq>fG-Npn^(-H?CL=g*6&%yglngT*S+PwU=^Ce3UdTK7uOQ z9?u9tVBXwF#mRH4nsXzpt5g(KH6N1yQ(3L*t!u>4?<8 zV&%zsn)*o(?Q#1$$4GK_cihG_Q)VQY)V!C*fb-sH<3Of8RGozgJE*hla~jKFU&_T7 z^5NWRzc}}l?b%ZTDgA}u^Z5<*A@L_1T{8l8aJQ?5F}y?XU1LT4Rge2Q{RdsE>uzPl zyqJR@E1z=XLD^z@@7)OYh|VC?kv7@Gvs;0gq?JYp5NcQVHgl+V=~@1g05W;@52cow zzL=+DR*EG~JQ-1pw-L=470dAj39AfbLjotr^{h*fz!CXIk4-(tgP+%pF7m_$pBox$QA+X#6zq2A*hM z-V}Z$Zxz8k=fez{R#h&GB@7CWZF_wCZdze8Zh~!X30{ltEDuE@n;gOQyR&WC+2+w8 zv+~LAzZ&dQ!&L|O=p+o%ZfymANdtX%-h~qdUom0m7&ftezfZM;UNbah&s?&^M2U)s zVN|2I>6_Ov&L^>x4+)2lrM={b-bdl2?^W0MA}WK*-N$|)PxGKcDWqdHprSI~{Pb?6 z4^hL6tajO_s16++6uDux`qEJ;t}fi3pGxYq^)*G{dUd1kDA7d>vWF$XVVZprIdUht z0a4`<&#N@XmkTqp_^<`6lQBqNKB3pKAOMps`wS%yu2tC9BpB01PC%72+2E&Vwi?b? zv-E~oP`ZZyr^p3qTZ7U%%Qw&*I@0$uhOxn#WM5UM?7of%sh;;QI zp(yhz6(Fdy-nkMPbvKzn%x$enc87jGg5@LHg<2 z5c1P9{id1>4+Lv&R1(aobUN2g)N5a5nrRFTPq3BT||z==D@HzQU67YaSSuVZq@ z627J&@K_qN3^}~i+;K=b6g7X(n5B)_c;!6^H;U2sOFXE{`zn4-|7Ji$>isXia+2B2 z8z-P_VVdDmGh*y5zCZgI(OszFauc$7q$hs1MTcSskCk_05ddMqL^7q#XjxSFkhK7` zehzylf(|~f{LBWaP7_Iw0I>X04IR(H?tjP;fIgcg#1)0&LN~G;df>^aA*?RO-pvP$GLN z6`?j^gq-~~N_(N~ucYf1n&kP0#6fp!5YOL21fmv=X}JkEgta|09nH=jJFX3Pp%xbf z27BL}l5l;T+P##L3&r7Lt=`Yurvk=wtLExovQmY-iEB$Tf85xZjF=P5>g{Dp05$Nu zIx^-9=1DgR6=yL%!n-!m16qT+pci$c%BE&&{53{+2DUHWaFs}WHp=E0_5w5rLFCZV z-_|75N`K+2*^|jzxC7j`)HD>p)-Ec)5sG)~9#NlaD_kT#*b$?DL?C15!xn#(&dvkZ z!`=F2Pb33Qom-+e4FjmPV;mt6G=GZL8V<+v2(bYSCXl#!)LndP_{|^~wfyrVk!9eNRv5@rDV}d8I7B)s- zx74kgf(=3|ph5Zdof&U=XuaNrGfE3+{ahQuY-~Msh`7EOzM~f=P5@PH&?9Ow#(>g4 zLdyEml$Q#u^!R|PO4xbp5y|67D2ux`bLjI?Ivd2g;5mbBBnY_`6U2(QFOx8j>RXbT z;ow6~_i%jrrw^`hv7!7Fo77%W;V?!I@7>rTv}fFZItiWkIFmds&F0!<|1 znXi|{g`ZVhuUP!HfqFxxuM()cVP!z~^b?zhsr#sIiCN*;N+%+Huz>APB-h#|LRV@u zD#%wulLqmZ6`IG3F|$=eaMve5l4;8A=fFM{QmaA2^S9!~k=>w_1>-ShO9>5|vv6Dv zZ_m^2gyoBN)mJoGc+z?8lraH^)3~KihNFF=7TKs$m<^%t_h_VHG%+P-^cWV%JL;jw ztQru#WgLLT@tv93HU|Xcqz+L&x95hkNyDQN)xoxmz^`RMJbx3e4S8g_|eh1gNYIUXCt>3dV$h2IZf zj|*!3hYlZx12jUREB4%eFIsD3!@{HS!{S8paGHD5i3MbQ(UtDY{TClD!n!rhp)MrK zTtdMB_{ea$z2%=QZ+x+gM1S&W%)tf9ih*=WA*SRGYkw+$3^5FYx-|{Cfw1m@YqE#5 zC$CK-xLM(?*e!Bb9DsHlAF{|aIGK@qNs@C8QQM2J-C+gNd0BhW&RS8;1VJ_=!dQ;U zl4yW{sS}uzYQI!P2baAV`@jwNd?P|aYkXhm#pk@u_oHfCwHtQ&4a4zx3be4bieuRf zC_OsoBb(ZxXX)#b;(d$-xFS`0qXf4T<#cpS#&e$6{>&Q~ZC2=r2gHhsbmaU#csH@v z0kQz}jPB+{#4I`^JYsAhPQOoho@7W+iqD?njOAWD#cK^}gTJvKA=`Jc)~uT*GzOO# zG(+=Ie_>m&K#Kba_{ps|@(5n3t`A$%24|%TPVnOhyU$Ku5@MW(DP8yB0La~;mR+P`0{Uw; zp1nq1aEJM(kgo%UO-{qPZ$+4XWnW8s^jL3BA} z49oS1tR66!#3&L!5U}z2(LjNiRgb+xooJayeZK?L<(30?CxlZ3dA6$aCk$v<5M&|q z$IMfNm=(99BxUB{lZoj$TKx+s4W<}9sWoEm0r{Fs!4PNejB_T9!08bpy-;?s#}Wh( zJkczc0Rjsg3x-BnT{YF(cI2{_&#xI9RpN&MirbMsX0?Iv?9WZd!SClUjlqKg6*jLy zAS*ZHL8eT@3}Yfc0<^40F0fCmx z)=H4g0}D2w-iGSy=*Lu0ZXBjqH12T5%nN0tt9k&;D!9o~n|!+JN$p}z99I);_E_*dWUOw}vD(zLaYCzy`2_bN z1M;41(XIh~#=?1L8irZ}fbp3Sd@Vu5K(r}<5%@1R>R;2oh&>7N6}5~DCEqK;(8H;( zgq*QE-lbM{(?~K-ecNrXSt?9scT#7BGxLnxM9`z3>^xWX{=?-e34Axih>iiaqz(qQ zH+cwuUupYHni(8jyc+aD47-vHq#;Lw_!ghz^LC7qclHMKy1&(Q#Br;=n*O%54=S1$ z5oXPS6Y#{5hIgRYKpyyM@z9cgXJZ7*zPuwP)V6g1J16T!cMU6v3OJVBFi7c*bVgU| zfy*(rb4f&@n`Lr>A{O}QSrm7xE#8m|sxtd(*e&Z_H~xe|ZC{#m$P%nYk<`!lT#AiJ z4=lg6h4y9Lvq2VE+6$d9+UEd873b6I%;lg=6pP$~x6@dbiB1xj8FuECnn#+M{(FY| zh7i^`qKd(LoO7|ez%RHQf4gHLPP?D1@A2P3cfB)Trs%@;u|aVD_@Uix9|8`qS3gm> zoqwNi@(84qumd8Vk-*FojKf=f4(uPQnlzWXQhjAvDDNIQMTkRxntnhXa}u$_TS5+v z2<~-c0jPgmy&7TqC($N1D9^OSp1c%*_}|kscS-0!LN_e<9Q67USDM{}0vFEZZH5>3kb$*j0+b5VB`%Eqs=!3o>+9~Ry3 z-@W=q4&$ADei6oXX5{>EpvtBQkmdX!41II3E`+ubByKGZms7!yPSV(n4q%`FFAD^x zj1w(|bua|c<#Hn0f5+7T-!;y&U7CQosyVM8l>~^D-p#})*94d{=Yaw~J?PZ^@lX+h z6?f*g?6wlzO!54U1!V2pqos+D)as+W;vZm83#7Z{zr#mC$&dTVm5IJ=X8w`d% zjNXyPt4St>9*Br>0L1gXMF|(O;!5ErH*X*E>aasZogi?0iL$Xmf?YC^8ThUlJsLmh zIxAXnKo|Us+A$Vni%U_Z^cMJJnX>dW9SmI$dk(8!AFE*zDb*8axR#e4lGh5w#Djwl zLWV;_q3K-6IrfWw?2wApgS;SuzOW~+@YG9$H^ai-VSkJe0hbwC-uLng$I9K0G1T_` z=IG*9+OD1+B=7-u=AS0R6}(l?%!2Wmc>-AEjp5!SdI22=V_C>{UA?$8@c2{_toI^&Y19CTHiA+72$hOosB^hlDP zV!WC(6miQ0oLi+FJf%MmVUF{cC+&>}*QPrE*$lfiR_$6|q+~gMS?z;#l?0$kZv%0) zz>Kf&11Wy&!0uMl4~A3zKjrkc++RLmO2q*_kG)+zljN)aOjALb96;R9zzLGPLKhjT z{TxQ;>`eui`YSsQ?!oKJIB$te2F-{G&2;LI$HUvJ6#(_qU!YMo4&N$P{Fj5Ds6V88 zV(h6tEa$;mAC>AQgU2mC~ z*W-qI5ytrPu@fZjs1EO}{#EqHCFO)YxB3{JsQrYG?4xUu5;=9x$t27AV<;(Ca&w<1 z_)K-bEvJ{&^+(5EFUWLP$MPJAQT4F`T$eKL;qy__96lcBQQn_FU5Tsh)|MJ)a=e6G zGom(L0>ZbCg)mriSBbbRXzV^pe z9WHfC8~)^LD`-%5dw?7+0_PHs+h+Qr$7VH`M$ZTv(v|8gJtg|)Og@;Sp!3PLW+nN*^UBij;BIcN2%u6l--#(Av#d$CI_C_BI zGM8bKD}!;bH{Mo&Q>KcZZNG60yd4-p4XI_~F| zz>3`Y9}0DXy4PBq6Vp=Rk*q9ktt($u-~R>8dI@Aqy&7X$`2^rgojQvnZ( zcvpag$DfDYOS-DiQd`FqL2#)hq7^XNMYUJ^0$koJ;fV&%fGd37%;$EZ*@cR*iU$hJq;5$ceDBfuCF7}X zhIit7AS8{?d-`k1UM8#N?4D|{gQ2g>p!F!^XX#oJ@YQH3b9K}9p6ykNb$>00`T)e? zp!G|sgI3ks20FpmOkaz4g>x78=18ykt^>4Ti6_O&T@riPu5(3XQ3K%;v^nk_4o06) zkk|Ph<~v46#XfcOZtY(pgWp9uWc7l$Um#)&P*q5{n1Zs4!`B>r$)+-(n0!hz;oYTs zL-*qxY*`=tytm%in{CTV?!L)F<^tI%SQ zCkq6|vx=m0RT~3;a}>LTDFw!Lm#cH;-}B4P)hhwYv*;1z*b-zM3(&-ANODlA?MwxGO$VXI@;0;7tfvsbreA#s^6OEzJ`?2r(ggH5?oEB?oBKX* zS#?g*Kf)%xMWIO7HG`q$@c}$(7nC>}Cne=MgDMcmf3vJoqOsVx$a#=|;8d0;*1|c2 zN$|mckIheG-zpr)!_1`K&M2H-ERd?l2!m*RSuHESq1>hILCp>CIypobK& zKv+~X&@L9|6;f9-WZWp8jm$hU5dZWSwwOe zk7G%|Ip-o}8$U_!N#=1V%H$o0>oW-yS{4tUID^~-E@qi(LrzDbh`8?ZOyCm z0FH>Iwud|P0?lVj5hK7ZgiR$DVq>pNypeufye1GuRA=lvxl3)QhB5ZKhQOsRFUX~e z6@!A}Q@C6>xl-t^a$V zy2lWQ%GEI3g{MNzlr_x-b_*!-1(bY~!zL!oQ~#*?eqHp#Pw^~XHtjxDFlTD@hOLL1 zNOsZ;D~5rG$mUVS-Bm&uc`+MqT0f`pzdKtR|JxZcY?>Ir00Z`F)61{Z!*%`d6m{wq z;!jscX9uHI*c`17DPPFye>{GhP0>{sg6_V7B{vpn!%@BDu$S8_NZaH1_X>MN;z9!T zDKyN?C^tJE^9s^99{L9GI6$(tct}sWo!9RlQ&Sn#zpti|N+qDh-mGA>;&SBh$B?A8 z3blwH^sZ`ipy1Q`hd+JmON88IGkSI*3ZsZ?v~=Q-tAMjkh12B{G?0m00<-c0L#zs? z?Pv!>T?O5UI1=bylQq+oC|awidbhX)S1<^A)866>b%Afl&2*+L9tW0|JD$hqAEt}t zj+7dBR)lF&@nJ-~?V~wGJC>ETpUOqOdDFZ50yWJz+=%N|?@#5-xPHtXvXk1GZl8jw zhT<2^WCcFem!R~OLXoZeMqa=3X+OT+?Mt6vguqng4XYlo1HF2!VgcD-VKgV2uH^cN zzVLx*BGQ3^n|%T*1j_qJyH7BjUL0r)z=K<#fSp`%#@f`P;M`G%;GGUc@DOWl7&AVo zw(0(AU)WqS7O?PleVa?K?v^*SW*D zP=r3>I#1VRiyBD`ihOE^YoYkk(W8fpcqktX|Df6Y*wNO5WBIBP1n@wUNFo|W47fO^ zymmut=ek8a$Kzm4^0%8mZ<#YPFo#ARNh{Jvey~L?BFO9Wfy#R9I!YTPodo1}Xl?eV zw(HCLB08>Z7;~2pg(AfR=%!Y^PD+1!TX*1ZG6LP+85GDZ)juSBVL=%CBXMD6aq zKM@b493tRY1Gl`qwUAfT(thh^0pC^053esea$nWjWC9wHi_|n;z*q4t`eOkmo$)SB zjd`WC631_S@HkEmI{R(Xbpzau^?BWpDd&EYZR2{jbmz3s^-~YWH@DI|R;w3QZEKsn zxr+msSjxdFYJ9VFA=p&p;x7j{y^b5c2toTzFSoNC5m`{=%#+Shr;HH6ws?$o6spwU z+lsnQa?3)33v;hA4HJg(+PoGXQ8QB{Oa-ul>$m-&H7=W+zRDe9aKMNSRCb}cqz%~X>5bsnq_uzLew&hgaV@Q9)m z2i$M4kca$`mzs6p?WJqTLwfQ*-cSSRaF=Ev&ff>)*|WXEa^PlCvjjxb$@*J&(ALN} z8Uh7W*1yZ=crr*eL=MP1X*w?bBu?dqj^m6&Oi#%z&QyVfS;kMa6XBhKJe@Zp#z_}? zZ&)0uDxNPu{_=6u$>e%3MZPQ7hFhDs{3+f_;99FUuWwEXHxKZSd>JhxR-rneSfh|{ zE~902JK1&)+bzaE^UIleIQ3qDG{<Yd3F}Ax@`PTx8n^k3 za~b}6Z1I-|N6N>Mjemo}FRm<*`!5?o$C!0NZzJWj7{5~wM{O5&u(VZonp=8RN@J+o zQjB{?8~#3}?rmxiogO;r_@{Z5+KtV699n#VQ_juyC+D0jm+ZP%wq`rWDL}2jxXckJLHp;?f~jE(>yP&>JwM#tYwX#(z3<%Ck8P(#di|S& ztq*revoT}havhI<%#1(i?|-|Wnl8KE60<0*WgV!%ad(Gg|4zSXKCrfWXr1TxHcd)* zMlM`FqF7y<|JG=KFq!R|@j)H^cInuu>fPnm0jt3ByiV`ft>0d&>%s?@Bc*fv6C+pe zd6Y~@B@RhYf#2S)ZLia47Mec))~H9(==h*FKiBtw#kvzxcfNxpN-bcmPDZAycb{h} z0>9X2cmQDfy^}I5poSJX_mX;^gRB`0CU**(qg<&&!bCtIK?#49wq@s%#y#@^1u|D< j24#$!nTP)`_VUv= 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: '
css file
', - 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 @@ -
-
- -
- : -
- {{request}} -
-
- -
-
-

Global score

-
- -
{{globalScore}}/100
-
-
- -
- -

Score details

- - - <%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!

- -
- - -
-
-
Choose the simulated device:
- Phone - Tablet - Desktop - Desktop -
- [ - Advanced settings  ✚ - Hide advanced settings  ✖ - ] - - Currently set: - wait for selector - cookie - authentication - proxy - domain blocking - -
- -
-
- Cookie - - -
Cookie

Adds cookies, separated by a pipe character.

Example: "bar1=foo1;domain=.domain1.com|bar2=foo2;domain=www.domain2.com"
-
-
-
-
-
-
- Authent - - -
Basic HTTP authentication

Enter your credentials here if you need to bypass a basic authentication.

PS: if your authentication is not basic, you might be able to copy the session cookie from your browser, paste it in the "Cookie" setting and launch a run before your cookie expires.
-
-
-
-
-
username
-
-
-
-
password
-
-
-
-
-
-
- HTTP proxy - - -
HTTP proxy

Insert here your proxy settings with the format "host:port".

Example: "192.168.10.0:3333"
-
-
-
-
-
-
- Block domains - - -
Block some domains

One line per domain or subdomain.

Example:
google-analytics.com
ads.yahoo.com
ajax.googleapis.com


An empty allow list will block all domains except the main domain.
-
-
-
-
- block list - allow list -
- -
-
-
-
-
- - -
-
-

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) { %> -
<%- 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}}

- -
-
-
Test failed
-

{{status.error}}

- - Report the issue on GitHub - New test -
-
-
- - -
-

(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 @@ -
Tested url:   {{result.params.url}}
- -
- New test - Test again - -
\ 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
-
- backtrace -
-
- {{obj.functionName}}() - - line {{obj.line}} -
-
-
-
- -
- {{offender.error}} -
no backtrace
-
- backtrace -
-
- {{obj.functionName}}() - - line {{obj.line}} -
-
-
-
- -
- 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.error}} - - (Check on the W3C validator) -
- -
- {{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 file: -
-
-
-

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
-
Current weight
-
Gzip weight
-
Brotli
-
Gain
-
-
-
- -
-
{{file.originalSize | bytes}}
- -
already gzipped
-
{{file.gzipped | bytes}}
- -
{{file.brotlified | bytes}}
- -
-{{file.gain | bytes}}
-
-
-
- -
-

{{rule.value | bytes}} could be saved on

-
-
-
File
-
Current weight
-
Minified
-
Gain
-
-
-
- -
-
{{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
-
-
- glyphes list -
{{range.charset | addSpaces}}
-
-
-
-
-
Ligatures or hidden chars
-
{{font.ligaturesOrHiddenChars}} glyphs
-
-
-
-
-
- -
-

{{rule.value | bytes}} could be saved on

-
-
-
File
-
Current weight
-
WOFF 2 weight
-
Gain
-
-
-
- -
-
{{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/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/package.json b/package.json index d0dd390..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,45 +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", + "phantomas": "2.2.0", "q": "1.5.1", "request": "2.88.0", - "rimraf": "2.6.3", "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", @@ -104,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 From ed588ba43c77c0a31db8c25f1a3c0b3e0213bef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 25 Apr 2021 00:03:31 +0100 Subject: [PATCH 4/5] Export version number --- lib/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 2f26c7db960fd42cb1aa61eaa4d047a875aa59a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 7 May 2021 16:03:39 +0100 Subject: [PATCH 5/5] Fix documentation after split --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)).