diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..21fec39f --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +static/js/*.min.js \ No newline at end of file diff --git a/.eslintrc-browser.js b/.eslintrc-browser.js new file mode 100644 index 00000000..06650854 --- /dev/null +++ b/.eslintrc-browser.js @@ -0,0 +1,36 @@ +module.exports = { + "env": { + "browser": true, + "es6": false + }, + "extends": [ + "eslint:recommended", + "standard" + ], + "globals": { + "$": true, + "Dygraph": true, + "Scout": true, + "ajaxForm": true, + "updateFormStatus": true, + }, + "rules": { + // Override some of standard js rules. + "semi": ["error", "always"], + "comma-dangle": [ + "error", { + "arrays": "only-multiline", + "objects": "only-multiline", + "imports": "never", + "exports": "never", + "functions": "never", + } + ], + + // Override some eslint base rules because we're using ES5. + "no-new": "off", + + // Custom rules. + "no-console": ["error", {"allow": ["warn", "error"]}], + } +}; \ No newline at end of file diff --git a/.eslintrc-node.js b/.eslintrc-node.js new file mode 100644 index 00000000..43534d52 --- /dev/null +++ b/.eslintrc-node.js @@ -0,0 +1,25 @@ +module.exports = { + "env": { + "browser": false, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:node/recommended", + "standard" + ], + "plugins": [ + "node" + ], + "rules": { + // Override some of standard js rules + "semi": ["error", "always"], + "comma-dangle": ["error", "only-multiline"], + "camelcase": "off", + "no-var": "error", + + // Override some eslint base rules because we're using node. + "no-console": "off", + } +}; \ No newline at end of file diff --git a/api/hosts-api.js b/api/hosts-api.js index c0f11bf2..4f2aaa09 100644 --- a/api/hosts-api.js +++ b/api/hosts-api.js @@ -141,7 +141,6 @@ hostAPI.post({ // No authentication. response.statusCode = 403; // Forbidden response.json({ error: 'Unauthorized' }, null, 2); - return; function createHost () { getHostProperties(properties => { diff --git a/api/user-api.js b/api/user-api.js index 6c584d72..059a8822 100644 --- a/api/user-api.js +++ b/api/user-api.js @@ -8,7 +8,6 @@ const configurations = require('../lib/configurations'); const db = require('../lib/db'); const log = require('../lib/log'); const machines = require('../lib/machines'); -const users = require('../lib/users'); // API resource to manage a Janitor user. const userAPI = module.exports = selfapi({ diff --git a/app.js b/app.js index 1f38a066..1c9ac176 100644 --- a/app.js +++ b/app.js @@ -19,7 +19,6 @@ boot.executeInParallel([ boot.ensureHttpsCertificates, boot.ensureDockerTlsCertificates ], () => { - // You can customize these values in './db.json'. const hostname = db.get('hostname', 'localhost'); const https = db.get('https'); @@ -495,11 +494,12 @@ boot.executeInParallel([ } let key = ''; + let match; switch (data.name) { case 'cloud9': // Extract a valid SSH public key from the user's input. // Regex adapted from https://gist.github.com/paranoiq/1932126. - var match = data.key.match(/ssh-rsa [\w+\/]+[=]{0,3}/); + match = data.key.match(/ssh-rsa [\w+/]+[=]{0,3}/); if (!match) { return end({ status: 'error', message: 'Invalid SSH key' }); } @@ -509,7 +509,7 @@ boot.executeInParallel([ case 'cloud9user': // Cloud9 usernames consist of lowercase letters, numbers and '_' only. - var match = data.key.trim().match(/^[a-z0-9_]+$/); + match = data.key.trim().match(/^[a-z0-9_]+$/); if (!match) { return end({ status: 'error', message: 'Invalid Cloud9 username' }); } diff --git a/join.js b/join.js index a4a062bd..45d952ec 100644 --- a/join.js +++ b/join.js @@ -16,9 +16,8 @@ const sessions = require('./lib/sessions'); const hostname = db.get('hostname', 'localhost'); if (!hostname || hostname === 'localhost') { - log('[fail] cannot join cluster as [hostname = ' + hostname + ']: ' + + throw new Error('Cannot join cluster as [hostname = ' + hostname + ']: ' + 'please fix the hostname in ./db.json and try again'); - return; } log('[ok] will try to join cluster as [hostname = ' + hostname + ']'); @@ -256,18 +255,17 @@ function routeRequest (proxyParameters, request, response) { switch (proxy) { case 'https': routes.webProxy(request, response, { port, path }); - return; + break; case 'none': - const url = 'https://' + hostname + ':' + port + path; - routes.redirect(response, url); - return; + routes.redirect(response, 'https://' + hostname + ':' + port + path); + break; default: log('[fail] unsupported proxy type:', proxy); response.statusCode = 500; // Internal Server Error response.end(); - return; + break; } } diff --git a/lib/boot.js b/lib/boot.js index 95b14c1f..d7b334ee 100644 --- a/lib/boot.js +++ b/lib/boot.js @@ -55,7 +55,7 @@ exports.forwardHttp = function (next) { response.end(); }); - forwarder.listen(ports.http , () => { + forwarder.listen(ports.http, () => { log('[ok] forwarding http:// → https://'); next(); }); @@ -263,7 +263,7 @@ exports.ensureDockerTlsCertificates = function (next) { return; } - fs.chmod(path, 0600 /* read + write by owner */, (error) => { + fs.chmod(path, 0o600 /* read + write by owner */, (error) => { if (error) { log('[fail] unable to protect ' + path, error); return; @@ -292,6 +292,7 @@ exports.ensureDockerTlsCertificates = function (next) { return; } + // eslint-disable-next-line no-func-assign done = null; db.save(); log('[ok] new docker-tls credentials installed'); @@ -344,4 +345,3 @@ exports.registerDockerClient = function (next) { next(); }); }; - diff --git a/lib/certificates.js b/lib/certificates.js index e83655be..400ec296 100644 --- a/lib/certificates.js +++ b/lib/certificates.js @@ -1,9 +1,9 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. -let child_process = require('child_process'); -let forge = require('node-forge'); -let letsencrypt = require('le-acme-core'); +const child_process = require('child_process'); +const forge = require('node-forge'); +const letsencrypt = require('le-acme-core'); // ACME protocol client to interact with the Let's Encrypt service. let client = letsencrypt.ACME.create({ @@ -18,11 +18,9 @@ let letsEncryptChallengePrefix = // ACME protocol challenge tokens proving our identity to Let's Encrypt. let letsEncryptChallenges = {}; - // Verify if a given certificate in PEM format satisfies validity constraints. exports.isValid = function (parameters, enableDebug) { - let debug = () => { if (enableDebug) { console.error(...arguments); @@ -101,17 +99,13 @@ exports.isValid = function (parameters, enableDebug) { } return true; - }; - // Generate an RSA public and private key pair in forge format (binary). exports.generateRSAKeyPair = function (callback) { - // Generate a new 4096-bit RSA private key (up to 100x faster than forge). child_process.exec('openssl genrsa 4096', (error, stdout, stderr) => { - if (error) { callback(error); return; @@ -127,18 +121,13 @@ exports.generateRSAKeyPair = function (callback) { privateKey: privateKey, publicKey: publicKey }); - }); - }; - // Generate an SSH public and private key pair in OpenSSH format. exports.createSSHKeyPair = function (callback) { - exports.generateRSAKeyPair((error, keypair) => { - if (error) { callback(error); return; @@ -156,16 +145,12 @@ exports.createSSHKeyPair = function (callback) { }; callback(null, sshKeyPair); - }); - }; - // Create a TLS certificate in PEM format. exports.createTLSCertificate = function (parameters, callback) { - let commonName = parameters.commonName; let altNames = parameters.altNames || []; let basicConstraints = parameters.basicConstraints || null; @@ -245,7 +230,6 @@ exports.createTLSCertificate = function (parameters, callback) { certificate.setExtensions(extensions); if (key) { - // A private key was provided, use it to finalize the certificate. let privateKey = forge.pki.privateKeyFromPem(key); let publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e); @@ -253,10 +237,7 @@ exports.createTLSCertificate = function (parameters, callback) { privateKey: privateKey, publicKey: publicKey }); - return; - } else { - // No private key was provided, generate a new one for this certificate. exports.generateRSAKeyPair((error, keypair) => { if (error) { @@ -264,14 +245,11 @@ exports.createTLSCertificate = function (parameters, callback) { return; } signOff(keypair); - return; }); - } // Use a given TLS key pair to sign and return the certificate. function signOff (keypair) { - certificate.publicKey = keypair.publicKey; if (caKey) { @@ -289,17 +267,12 @@ exports.createTLSCertificate = function (parameters, callback) { } callback(null, crt, key); - return; - } - }; - // Generate an HTTPS certificate using Let's Encrypt (https://letsencrypt.org/). exports.createHTTPSCertificate = function (parameters, callback) { - let hostname = parameters.hostname; let accountEmail = parameters.accountEmail; let accountKey = parameters.accountKey || null; @@ -345,7 +318,6 @@ exports.createHTTPSCertificate = function (parameters, callback) { // Wait for all required tasks to finish before proceding. function done () { - if (!acmeUrls || !accountKey || !httpsKey) { // Some tasks are not finished yet. Let's wait. return; @@ -369,6 +341,7 @@ exports.createHTTPSCertificate = function (parameters, callback) { } // All required tasks are now finished, destroy the `done` callback. + // eslint-disable-next-line no-func-assign done = null; // We can now actually request an HTTPS certificate from Let's Encrypt. @@ -384,21 +357,16 @@ exports.createHTTPSCertificate = function (parameters, callback) { } callback(null, certificate, accountKey); }); - } - }; - // Expose the URL prefix that Let's Encrypt will use to challenge our identity. exports.letsEncryptChallengePrefix = letsEncryptChallengePrefix; - // Look for an identity token that satisfies the given Let's Encrypt challenge. exports.getLetsEncryptChallengeToken = function (url) { - if (!url || !url.startsWith(letsEncryptChallengePrefix)) { // Not a Let's Encrypt challenge URL. return null; @@ -412,14 +380,11 @@ exports.getLetsEncryptChallengeToken = function (url) { } return token; - }; - // Register a new Let's Encrypt account. function registerLetsEncryptAccount (parameters, callback) { - let accountEmail = parameters.accountEmail; let accountKey = parameters.accountKey; let acmeUrls = parameters.acmeUrls; @@ -437,14 +402,11 @@ function registerLetsEncryptAccount (parameters, callback) { }; client.registerNewAccount(options, callback); - } // Don't export `registerLetsEncryptAccount`. - // Request HTTPS certificate issuance by Let's Encrypt via the ACME protocol. function requestLetsEncryptCertificate (parameters, callback) { - let hostname = parameters.hostname; let acmeUrls = parameters.acmeUrls; let accountKey = parameters.accountKey; @@ -473,5 +435,4 @@ function requestLetsEncryptCertificate (parameters, callback) { }; client.getCertificate(options, callback); - } // Don't export `requestLetsEncryptCertificate`. diff --git a/lib/configurations.js b/lib/configurations.js index 904714de..b06a97fb 100644 --- a/lib/configurations.js +++ b/lib/configurations.js @@ -10,23 +10,23 @@ const db = require('./db'); const log = require('./log'); exports.allowed = [ - ".config/hub", - ".emacs", - ".eslintrc", - ".gdbinit", - ".gitconfig", - ".gitignore", - ".hgrc", - ".nanorc", - ".netrc", - ".ssh/config", - ".vimrc", + '.config/hub', + '.emacs', + '.eslintrc', + '.gdbinit', + '.gitconfig', + '.gitignore', + '.hgrc', + '.nanorc', + '.netrc', + '.ssh/config', + '.vimrc', ]; exports.defaults = {}; load(); // Read and pre-compile the default configuration templates. -function load() { +function load () { const directory = './templates/configurations/'; fs.readdir(directory, (error, files) => { if (error) { @@ -70,7 +70,7 @@ exports.resetToDefault = function (user, file, callback) { } let content = ''; - stream.on('data', chunk => content += String(chunk)); + stream.on('data', chunk => { content += String(chunk); }); stream.on('end', () => { user.configurations[file] = content; db.save(); diff --git a/lib/db.js b/lib/db.js index 24212ed6..5cf2ba6d 100644 --- a/lib/db.js +++ b/lib/db.js @@ -1,18 +1,17 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. -var fs = require('fs'); +const fs = require('fs'); // The datastore. -var file = './db.json'; -var store = {}; +const file = './db.json'; +let store = {}; load(); // Load the datastore from disk synchronously. function load () { - - var json = '{}'; + let json = '{}'; try { json = fs.readFileSync(file, 'utf8'); @@ -25,38 +24,31 @@ function load () { } store = JSON.parse(json); - } - // Save the datastore to disk asynchronously. TODO gzip? exports.save = function () { - - var json = JSON.stringify(store, null, 2); + const json = JSON.stringify(store, null, 2); fs.writeFile(file, json + '\n', function (error) { if (error) { console.error('Can\'t write DB file!', error.stack); } - fs.chmod(file, 0600 /* read + write by owner */, function (error) { + fs.chmod(file, 0o600 /* read + write by owner */, function (error) { if (error) { console.error('Can\'t protect DB file!', error.stack); } }); }); - }; - // Get or create an entry in the datastore. exports.get = function (key, defaultValue) { - if (!store[key]) { store[key] = defaultValue || {}; } return store[key]; - }; diff --git a/lib/docker.js b/lib/docker.js index f14eac2e..1ff71528 100644 --- a/lib/docker.js +++ b/lib/docker.js @@ -1,7 +1,7 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. -const dockerode = require('dockerode'); +const Dockerode = require('dockerode'); const tar = require('tar-stream'); const stream = require('stream'); const stringdecoder = require('string_decoder'); @@ -9,6 +9,7 @@ const util = require('util'); const db = require('./db'); const hosts = require('./hosts'); +const log = require('./log'); // Get client access to a given Docker host. function getDocker (hostname, callback) { @@ -19,7 +20,7 @@ function getDocker (hostname, callback) { } const { ca, client } = db.get('tls'); - const docker = new dockerode({ + const docker = new Dockerode({ protocol: 'https', host: hostname, port: Number(host.properties.port), @@ -206,12 +207,14 @@ exports.removeContainer = function (parameters, callback) { }); }; - // Get the Docker version of a given host. exports.version = function (parameters, callback) { const { host } = parameters; getDocker(host, (error, docker) => { + if (error) { + log('[fail] could not get the docker client', error); + } docker.version((error, data) => { callback(error, data); }); diff --git a/lib/log.js b/lib/log.js index 9a5c72b6..0a9f1b59 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,16 +1,13 @@ // Copyright © 2015 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // Log messages to the console with a timestamp. -function log() { - - var args = [].slice.call(arguments); +function log () { + let args = [].slice.call(arguments); args.unshift('[' + new Date().toISOString() + ']'); console.log.apply(console, args); - } module.exports = log; diff --git a/lib/machines.js b/lib/machines.js index 43ff2380..8488981f 100644 --- a/lib/machines.js +++ b/lib/machines.js @@ -87,7 +87,7 @@ exports.rebuild = function (projectId, callback) { docker.buildImage({ host, tag, dockerfile }, (error, stream) => { if (error) { - log ('rebuild', image, error); + log('[fail] rebuild', image, error); callback(new Error('Unable to rebuild project: ' + projectId)); return; } @@ -288,6 +288,7 @@ exports.destroy = function (user, projectId, machineId, callback) { // Install or overwrite a configuration file in all the user's containers. exports.deployConfigurationInAllContainers = function (user, file) { let count = 0; + // eslint-disable-next-line node/no-unsupported-features const machines = Object.values(user.machines) .reduce((machines, projectMachines) => machines.concat(projectMachines), []) .filter(machine => machine.status === 'started'); diff --git a/lib/metrics.js b/lib/metrics.js index fa0b3287..fca172d3 100644 --- a/lib/metrics.js +++ b/lib/metrics.js @@ -4,11 +4,9 @@ let db = require('./db'); let log = require('./log'); - // Set a value for a metric. exports.set = function (object, metric, value) { - let data = object.data; if (!data) { @@ -17,14 +15,11 @@ exports.set = function (object, metric, value) { data[metric] = value; db.save(); - }; - // Push a value into a metric array. exports.push = function (object, metric, value) { - let data = object.data; if (!data || !data[metric]) { @@ -34,14 +29,11 @@ exports.push = function (object, metric, value) { data[metric].push(value); db.save(); - }; - // Get all available metrics. exports.get = function (callback) { - let time = Date.now(); let data = { users: exports.getUserData(), @@ -52,13 +44,11 @@ exports.get = function (callback) { callback(data); log('data collection took', Date.now() - time, 'ms.'); - }; // Get metrics about all users. exports.getUserData = function () { - let data = { users: [], waitlist: [] @@ -77,14 +67,11 @@ exports.getUserData = function () { data.waitlist.sort(); return data; - }; - // Get metrics about all projects. exports.getProjectData = function () { - let data = []; let projects = db.get('projects'); @@ -97,14 +84,11 @@ exports.getProjectData = function () { } return data; - }; - // Get metrics about all contributions. exports.getContributionData = function () { - let data = { 'new': 0, 'build-failed': 0, @@ -132,23 +116,20 @@ exports.getContributionData = function () { data.total = total; return data; - }; - // Get metrics about all connected Docker hosts. exports.getHostData = function () { - let data = { docker: [] }; let hosts = db.get('hosts'); + // eslint-disable-next-line no-unused-vars for (let hostname in hosts) { data.docker.push({}); } return data; - }; diff --git a/lib/oauth2.js b/lib/oauth2.js index c471a797..ad10a93c 100644 --- a/lib/oauth2.js +++ b/lib/oauth2.js @@ -5,6 +5,7 @@ const crypto = require('crypto'); const oauth = require('oauth'); const db = require('./db'); +const log = require('./log'); // Get client access to a given OAuth2 provider. function getClient (providerId, callback) { @@ -102,6 +103,10 @@ exports.request = function (parameters, callback) { } = parameters; getClient(providerId, (error, client, provider) => { + if (error) { + log('[fail] could not get the client', error); + } + const api = provider.api || provider.hostname; const body = data ? JSON.stringify(data, null, 2) : null; let url = 'https://' + api + path; diff --git a/lib/routes.js b/lib/routes.js index f17a6df7..619631a9 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -364,6 +364,10 @@ exports.webProxy = function (request, response, parameters) { }); proxy.on('error', error => { + if (error) { + log('[fail] could not process the request', error); + } + response.statusCode = 503; // Service Unavailable response.end(); }); diff --git a/lib/sessions.js b/lib/sessions.js index d5204289..1b5f2b69 100644 --- a/lib/sessions.js +++ b/lib/sessions.js @@ -1,11 +1,12 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. -const emaillogin = require('email-login'); +const EmailLogin = require('email-login'); const db = require('./db'); +const log = require('./log'); -const login = new emaillogin({ +const login = new EmailLogin({ db: './tokens/', mailer: db.get('mailer') }); @@ -69,6 +70,10 @@ exports.get = function (request, callback) { // Destroy the session associated to the given request. exports.destroy = function (request, callback) { exports.get(request, (error, session, token) => { + if (error) { + log('[fail] could not destroy the session', error); + } + if (request.cookies) { // Destroy the cookie. request.cookies.set(cookieNames.token, '', { diff --git a/lib/streams.js b/lib/streams.js index 3c35268a..97087a21 100644 --- a/lib/streams.js +++ b/lib/streams.js @@ -1,18 +1,18 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. -var stream = require('stream'); +const stream = require('stream'); -// Streams that are currently in progress, per object. -var objectStreams = new Map(); +const log = require('./log'); +// Streams that are currently in progress, per object. +let objectStreams = new Map(); // Get `object[key]` as a stream (may still be receiving new data). exports.get = function (object, key) { - - var passthrough = new stream.PassThrough(); - var streams = objectStreams.get(object); + let passthrough = new stream.PassThrough(); + let streams = objectStreams.get(object); if (object[key]) { passthrough.write(object[key], 'utf8'); @@ -25,15 +25,12 @@ exports.get = function (object, key) { } return passthrough; - -} - +}; // Read a stream into `object[key]`. exports.set = function (object, key, readable) { - - var streams = objectStreams.get(object); + let streams = objectStreams.get(object); if (!streams) { streams = {}; @@ -50,22 +47,23 @@ exports.set = function (object, key, readable) { // Remove the stream if an error occurs. readable.on('error', function (error) { - remove(object, key); + if (error) { + log('[fail] could not read the stream', error); + } + + exports.remove(object, key); }); // Clean up when the stream ends. readable.on('end', function () { - remove(object, key); + exports.remove(object, key); }); - }; - // Remove any stream affected to `object[key]`. exports.remove = function (object, key) { - - var streams = objectStreams.get(object); + let streams = objectStreams.get(object); // If the stream doesn't exist, do nothing. if (!streams || !streams[key]) { @@ -79,5 +77,4 @@ exports.remove = function (object, key) { if (Object.keys(streams).length < 1) { objectStreams.delete(object); } - }; diff --git a/lib/users.js b/lib/users.js index 25309126..e358cb6e 100644 --- a/lib/users.js +++ b/lib/users.js @@ -95,6 +95,10 @@ exports.resetSSHKeyPair = function (user) { // Send a single-use login link to the user's email address. exports.sendLoginEmail = function (email, request, callback) { sessions.get(request, (error, session, token) => { + if (error) { + log('[fail] could not send login email', error); + } + // Alpha version is invite-only. if (!(email in db.get('users')) && !exports.isAdmin({ email })) { // Add unknown emails to the waitlist. @@ -145,6 +149,10 @@ exports.sendLoginEmail = function (email, request, callback) { exports.sendInviteEmail = function (email, callback) { // Generate a dummy token for that invite. sessions.create((error, token, session) => { + if (error) { + log('[fail] could not create the session', error); + } + // Invite email template. const template = { subject () { diff --git a/package.json b/package.json index 4678d0c1..1f2d4282 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "scripts": { "app": "SCRIPT=app npm start", "join": "SCRIPT=join npm start", + "lint": "eslint -c .eslintrc-node.js *.js api/ lib/ && eslint -c .eslintrc-browser.js static/", + "lint-fix": "eslint -c .eslintrc-node.js *.js api/ lib/ --fix && eslint -c .eslintrc-browser.js static/ --fix", "rebase": "git pull -q --rebase origin master && git submodule -q update --rebase && npm update", "prestart": "npm stop && touch janitor.log janitor.pid && chmod 600 janitor.log janitor.pid", "start": "if [ -z \"$SCRIPT\" ] ; then printf \"Run which Janitor script? [join/app]:\" && read SCRIPT ; fi ; node \"$SCRIPT\" >> janitor.log 2>&1 & printf \"$!\\n\" > janitor.pid", @@ -27,7 +29,7 @@ "watch": "watch-run --initial --pattern 'app.js,package.json,api/**,lib/**,templates/**' --stop-on-error npm run app & tailf janitor.log -n 0" }, "dependencies": { - "camp": "~17.2.0", + "camp": "^17.2.0", "dockerode": "~2.5.0", "email-login": "~1.1.0", "fast-json-patch": "~2.0.3", @@ -40,6 +42,12 @@ "tar-stream": "~1.5.4" }, "devDependencies": { + "eslint": "^4.1.1", + "eslint-config-standard": "^10.2.1", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-node": "^5.1.0", + "eslint-plugin-promise": "^3.5.0", + "eslint-plugin-standard": "^3.0.1", "watch-run": "^1.2.5" }, "engines": { diff --git a/static/js/graphs.js b/static/js/graphs.js index 4a98a3b8..704a4426 100644 --- a/static/js/graphs.js +++ b/static/js/graphs.js @@ -1,11 +1,9 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // Set-up all time series graphs. Array.map(document.querySelectorAll('*[data-data]'), function (div) { - var data = JSON.parse(div.dataset.data); var title = div.dataset.title; @@ -23,16 +21,13 @@ Array.map(document.querySelectorAll('*[data-data]'), function (div) { includeZero: true } }, - labelsUTC: true, + labelsUTC: true }); - }); - // Format milliseconds into human readable text. function formatTime (milliseconds) { - var units = [ { code: 'ms', max: 1000 }, { code: 's', max: 60 }, @@ -50,14 +45,12 @@ function formatTime (milliseconds) { } return (Math.round(value * 10) / 10) + ' ' + unit.code; - } - // Format bytes into human readable text. +// eslint-disable-next-line no-unused-vars function formatMemory (bytes) { - var prefix = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; var p = 0; var value = Number(bytes); @@ -68,5 +61,4 @@ function formatMemory (bytes) { } return (Math.round(value * 100) / 100) + ' ' + prefix[p] + 'B'; - } diff --git a/static/js/janitor.js b/static/js/janitor.js index bf1431ff..8d1b0398 100644 --- a/static/js/janitor.js +++ b/static/js/janitor.js @@ -3,7 +3,7 @@ // Polyfill a few basic things. ['filter', 'forEach', 'map', 'reduce'].forEach(function (name) { - Array[name] = function(array, callback, init) { + Array[name] = function (array, callback, init) { return [][name].call(array, callback, init); }; }); @@ -99,7 +99,7 @@ function fetchAPI (method, url, data, callback) { }).then(function (response) { // The server is responding! responseStatus = response.status; - return responseStatus == 204 ? null : response.json(); + return responseStatus === 204 ? null : response.json(); }).then(function (data) { // The response body was successfully parsed as JSON! if (data && data.error) { @@ -137,7 +137,6 @@ function setupAsyncForm (form) { element.addEventListener('keydown', resetFormStatus); }); - // Elements can specify an event to submit the
. Array.forEach(form.querySelectorAll('[data-submit-on]'), function (element) { element.addEventListener(element.dataset.submitOn, function (event) { @@ -179,7 +178,7 @@ function updateFormStatus (form, status, message) { feedback.focus(); } - if (form.dataset.refreshAfterSuccess && status == 'success') { + if (form.dataset.refreshAfterSuccess && status === 'success') { setTimeout(function () { location.reload(); }, 400); diff --git a/static/js/landing.js b/static/js/landing.js index b58280dc..d9c57519 100644 --- a/static/js/landing.js +++ b/static/js/landing.js @@ -1,11 +1,9 @@ // Copyright © 2015 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // Alpha sign-up form. ajaxForm('#signup-form', 'signup', function (form, data) { - var status = 'error'; var message = data.message; @@ -25,5 +23,4 @@ ajaxForm('#signup-form', 'signup', function (form, data) { } updateFormStatus(form, status, message); - }); diff --git a/static/js/login.js b/static/js/login.js index c1d43929..d89e30f2 100644 --- a/static/js/login.js +++ b/static/js/login.js @@ -1,11 +1,9 @@ // Copyright © 2015 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // Email-login form. ajaxForm('#login-form', 'login', function (form, data) { - var status = 'error'; var message = data.message; @@ -22,5 +20,4 @@ ajaxForm('#login-form', 'login', function (form, data) { } updateFormStatus(form, status, message); - }); diff --git a/static/js/projects.js b/static/js/projects.js index 8374bb95..8281d331 100644 --- a/static/js/projects.js +++ b/static/js/projects.js @@ -1,11 +1,9 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // Spawn a project-specific machine when one of its links is clicked. Array.map(document.querySelectorAll('a[data-action="spawn"]'), function (link) { - link.addEventListener('click', Scout.send(function (query) { query.action = link.dataset.action; query.data = { @@ -15,14 +13,11 @@ Array.map(document.querySelectorAll('a[data-action="spawn"]'), function (link) { document.location = '/contributions/'; }; })); - }); - // Add status badges to elements with a 'data-status' attribute. Array.map(document.querySelectorAll('*[data-status]'), function (element) { - var span = document.createElement('span'); var status = element.dataset.status; @@ -43,14 +38,11 @@ Array.map(document.querySelectorAll('*[data-status]'), function (element) { span.classList.add('label', 'label-' + (label[status] || 'default')); element.appendChild(span); - }); - // Add fuzzy timestamps to elements with a 'data-timestamp' attribute. Array.map(document.querySelectorAll('*[data-timestamp]'), function (element) { - var date = new Date(parseInt(element.dataset.timestamp)); // GMT is deprecated (see https://en.wikipedia.org/wiki/UTC). @@ -59,14 +51,11 @@ Array.map(document.querySelectorAll('*[data-timestamp]'), function (element) { // Use jQuery's live-updating timeago plugin. $(element).timeago(); - }); - // Request confirmation before deleting a project-specific machine. $('#confirm').on('show.bs.modal', function (event) { - var link = event.relatedTarget; var title = this.querySelector('#confirm-title'); var details = this.querySelector('#confirm-details'); @@ -94,14 +83,11 @@ $('#confirm').on('show.bs.modal', function (event) { } }; }); - }); - // Clean up the confirmation screen when dismissed. $('#confirm').on('hidden.bs.modal', function (event) { - var title = this.querySelector('#confirm-title'); var details = this.querySelector('#confirm-details'); var button = this.querySelector('#confirm-button'); @@ -111,5 +97,4 @@ $('#confirm').on('hidden.bs.modal', function (event) { button.textContent = ''; button.onclick = null; - }); diff --git a/static/js/settings.js b/static/js/settings.js index 67a0b38b..6510a25a 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -1,11 +1,9 @@ // Copyright © 2015 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // Settings: Cloud9 username form. ajaxForm('#cloud9user-form', 'key', function (form, data) { - var status = 'error'; var message = data.message; @@ -14,14 +12,11 @@ ajaxForm('#cloud9user-form', 'key', function (form, data) { } updateFormStatus(form, status, message); - }); - // Settings: Cloud9 SSH key form. ajaxForm('#cloud9-form', 'key', function (form, data) { - var status = 'error'; var message = data.message; @@ -30,5 +25,4 @@ ajaxForm('#cloud9-form', 'key', function (form, data) { } updateFormStatus(form, status, message); - }); diff --git a/static/service-worker.js b/static/service-worker.js index 0fe9d6c2..e1f51abe 100644 --- a/static/service-worker.js +++ b/static/service-worker.js @@ -1,23 +1,17 @@ // Copyright © 2016 Jan Keromnes. All rights reserved. // The following code is covered by the AGPL-3.0 license. - // During the 'install' phase, set up our new Service Worker. self.addEventListener('install', function (event) { - // After this phase, don't wait for currently open clients to close, jump // straight to the 'activate' phase. event.waitUntil(self.skipWaiting()); - }); - // During the 'activate' phase, clean up behind older Service Workers. self.addEventListener('activate', function (event) { - // Take control of any currently open clients. event.waitUntil(self.clients.claim()); - });