' + err + '
'; + if (msg) res.locals.message = '' + msg + '
'; +next(); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..c4f3f0e --- /dev/null +++ b/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('ztnc:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on loopback network interface. + */ + +server.listen(port, 'localhost'); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/controllers/auth.js b/controllers/auth.js new file mode 100644 index 0000000..5dc53e7 --- /dev/null +++ b/controllers/auth.js @@ -0,0 +1,48 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const argon2 = require('argon2'); +const usersController = require('../controllers/usersController'); + +hash_check = async function(user, password) { + let verified = false; + try { + var users = await usersController.get_users(); + } catch (err) { + throw err; + } + try { + verified = await argon2.verify(users[user].hash, password); + } catch (err) { + throw err; + } + return verified; +} + +exports.authenticate = async function(name, pass, callback) { + try { + var users = await usersController.get_users(); + } catch (err) { + throw err; + } + let user = users[name]; + if (!user) return callback(new Error('cannot find user')); + let verified = await hash_check(name, pass); + if (verified) { + return callback(null, user); + } else { + return callback(new Error('invalid password')); + } +} + +exports.restrict = function(req, res, next) { + if (req.session.user) { + next(); + } else { + req.session.error = 'Access denied!'; + res.redirect('/login'); + } +} diff --git a/controllers/networkController.js b/controllers/networkController.js new file mode 100644 index 0000000..0363704 --- /dev/null +++ b/controllers/networkController.js @@ -0,0 +1,559 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const fs = require('fs'); +const ipaddr = require('ip-address'); +const storage = require('node-persist'); +const zt = require('./zt'); + +storage.initSync({dir: 'etc/storage'}); + +// ZT network controller home page +exports.index = async function(req, res) { + const page = 'controller_home'; + + try { + zt_address = await zt.get_zt_address(); + res.render('index', {title: 'ztncui', page: page, zt_address: zt_address}); + } catch (err) { + res.render('index', {title: 'ztncui', + page: page, error: 'ERROR resolving ZT address: ' + err}); + } +}; + +// Display list of all networks on this ZT network controller +exports.network_list = async function(req, res) { + const page = 'networks'; + + try { + networks = await zt.network_list(); + res.render('networks', {title: 'Networks on this controller', page: page, networks: networks}); + } catch (err) { + res.render('networks', {title: 'Networks on this controller', page: page, error: 'Error retrieving list of networks on this controller: ' + err}); + } +}; + +// Display detail page for specific network +exports.network_detail = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + const members = await zt.members(req.params.nwid); + res.render('network_detail', {title: 'Detail for network', page: page, network: network, members: members}); + } catch (err) { + res.render('network_detail', {title: 'Detail for network', page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); + } +}; + +// Display Network create form on GET +exports.network_create_get = function(req, res) { + const page = 'add_network'; + + res.render('network_create', {title: 'Create network', page: page}); +}; + +// Handle Network create on POST +exports.network_create_post = async function(req, res) { + const page = 'add_network'; + + req.checkBody('name', 'Network name required').notEmpty(); + + req.sanitize('name').escape(); + req.sanitize('name').trim(); + + const errors = req.validationErrors(); + + const name = { name: req.body.name }; + + if (errors) { + res.render('network_create', {title: 'Create Network', page: page, name: name, errors: errors}); + return; + } else { + try { + const network = await zt.network_create(name); + res.redirect('/controller/networks'); + } catch (err) { + res.render('network_detail', {title: 'Create Network - error', page: page, error: 'Error creating network ' + name.name}); + } + } +}; + +// Display Network delete form on GET +exports.network_delete_get = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + res.render('network_delete', {title: 'Delete network', page: page, + nwid: req.params.nwid, network: network}); + } catch (err) { + res.render('network_delete', {title: 'Delete network', page: page, error: 'Error resolving network ' + req.params.nwid + ': ' + err}); + } +}; + +// Handle Network delete on POST +exports.network_delete_post = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_delete(req.params.nwid); + res.render('network_delete', {title: 'Delete network', page: page, network: network}); + } catch (err) { + res.render('network_delete', {title: 'Delete network', page: page, error: 'Error deleting network ' + req.params.nwid + ': ' + err}); + } +}; + +// Network object GET +exports.network_object = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + res.render(req.params.object, {title: req.params.object, page: page, network: network}, function(err, html) { + if (err) { + if (err.message.indexOf('Failed to lookup view') !== -1 ) { + return res.render('not_implemented', {title: req.params.object, page: page, network: network}); + } + throw err; + } + res.send(html); + }); + } catch (err) { + res.render(req.params.object, {title: req.params.object, page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); + } +} + +// Handle Network rename form on POST +exports.name = async function(req, res) { + const page = 'networks'; + + req.checkBody('name', 'Network name required').notEmpty(); + req.sanitize('name').escape(); + req.sanitize('name').trim(); + + const errors = req.validationErrors(); + + const name = { name: req.body.name }; + + if (errors) { + try { + const network = await zt.network_detail(req.params.nwid); + res.render('name', {title: 'Rename network', page: page, network: network, name: name, errors: errors}); + } catch (err) { + res.render('name', {title: 'Rename network', page: page, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err}); + } + } else { + try { + const network = await zt.network_object(req.params.nwid, name); + res.redirect('/controller/networks'); + } catch ( err) { + res.render('name', {title: 'Rename network', page: page, error: 'Error renaming network ' + req.params.nwid + ': ' + err}); + } + } + +}; + +// ipAssignmentPools POST +exports.ipAssignmentPools = async function(req, res) { + const page = 'networks'; + + req.checkBody('ipRangeStart', 'IP range start required').notEmpty(); + req.checkBody('ipRangeStart', 'IP range start needs a valid IPv4 or IPv6 address').isIP(); + req.sanitize('ipRangeStart').escape(); + req.sanitize('ipRangeStart').trim(); + req.checkBody('ipRangeEnd', 'IP range end required').notEmpty(); + req.checkBody('ipRangeEnd', 'IP range end needs a valid IPv4 or IPv6 address').isIP(); + req.sanitize('ipRangEnd').escape(); + req.sanitize('ipRangEnd').trim(); + + const errors = req.validationErrors(); + + const ipAssignmentPool = + { + ipRangeStart: req.body.ipRangeStart, + ipRangeEnd: req.body.ipRangeEnd + }; + + if (errors) { + try { + const network = await zt.network_detail(req.params.nwid); + res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, ipAssignmentPool: ipAssignmentPool, network: network, errors: errors}); + } catch (err) { + res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err}); + } + } else { + try { + const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'add'); + res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, ipAssignmentPool: ipAssignmentPool, network: network}); + } catch (err) { + res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, error: 'Error applying IP Assignment Pools for network ' + req.params.nwid + ': ' + err}); + } + } +} + +isValidPrefix = function(str, max) { + const num = Math.floor(Number(str)); + return String(num) == str && num >= 0 && num <= max; +} + +// routes POST +exports.routes = async function (req, res) { + const page = 'networks'; + + req.checkBody('target', 'Target network is required').notEmpty(); + req.sanitize('target').trim(); + req.checkBody('target', 'Target network must be valid CIDR format') + .custom(value => { + const parts = value.split('/'); + const ipv4 = new ipaddr.Address4(parts[0]); + const ipv6 = new ipaddr.Address6(parts[0]); + let isValidIPv4orIPv6 = false; + let prefixMax = 32; + if (ipv4.isValid()) { + isValidIPv4orIPv6 = true; + } else { + } + if (ipv6.isValid()) { + isValidIPv4orIPv6 = true; + prefixMax = 128; + } else { + } + return isValidIPv4orIPv6 && isValidPrefix(parts[1], prefixMax); + }); + req.checkBody('via', 'Gateway must be a valid IPv4 or IPv6 address').optional({ checkFalsy: true }).isIP(); + req.sanitize('via').escape(); + req.sanitize('via').trim(); + if (! req.body.via) { + req.body.via = null; + } + + const errors = req.validationErrors(); + + const route = + { + target: req.body.target, + via: req.body.via + }; + + if (errors) { + try { + const network = await zt.network_detail(req.params.nwid); + res.render('routes', {title: 'routes', page: page, route: route, network: network, errors: errors}); + } catch (err) { + res.render('routes', {title: 'routes', page: page, error: 'Error resolving network detail'}); + } + } else { + try { + const network = await zt.routes(req.params.nwid, route, 'add'); + res.render('routes', {title: 'routes', page: page, route: route, network: network}); + } catch (err) { + res.render('routes', {title: 'routes', page: page, error: 'Error adding route for network ' + req.params.nwid + ': ' + err}); + } + } + +} + +// route_delete GET +exports.route_delete = async function (req, res) { + const page = 'networks'; + + const route = + { + target: req.params.target_ip + '/' + req.params.target_prefix, + via: null + }; + + + try { + const network = await zt.routes(req.params.nwid, route, 'delete'); + res.render('routes', {title: 'routes', page: page, route: route, network: network}); + } catch (err) { + res.render('routes', {title: 'routes', page: page, error: 'Error deleting route for network ' + req.params.nwid + ': ' + err}); + } +} + +// ipAssignmentPool_delete GET +exports.ipAssignmentPool_delete = async function (req, res) { + const page = 'networks'; + + const ipAssignmentPool = + { + ipRangeStart: req.params.ipRangeStart, + ipRangeEnd: req.params.ipRangeEnd + }; + + + try { + const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'delete'); + res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, ipAssignmentPool: ipAssignmentPool, network: network}); + } catch (err) { + res.render('ipAssignmentPools', {title: 'ipAssignmentPools', page: page, error: 'Error deleting IP Assignment Pool for network ' + req.params.nwid + ': ' + err}); + } +} + +// v4AssignMode POST +exports.v4AssignMode = async function (req, res) { + const page = 'networks'; + + const v4AssignMode = + { + v4AssignMode: { zt: req.body.zt } + }; + + try { + const network = await zt.network_object(req.params.nwid, v4AssignMode); + res.render('v4AssignMode', {title: 'v4AssignMode', page: page, network: network}); + } catch (err) { + res.render('v4AssignMode', {title: 'v4AssignMode', page: page, error: 'Error applying v4AssignMode for network ' + req.params.nwid + ': ' + err}); + } +} + +// v6AssignMode POST +exports.v6AssignMode = async function (req, res) { + const page = 'networks'; + + const v6AssignMode = + { + v6AssignMode: + { + '6plane': req.body['6plane'], + rfc4193: req.body.rfc4193, + zt: req.body.zt + } + }; + + try { + const network = await zt.network_object(req.params.nwid, v6AssignMode); + res.render('v6AssignMode', {title: 'v6AssignMode', page: page, network: network}); + } catch (err) { + res.render('v6AssignMode', {title: 'v6AssignMode', page: page, error: 'Error applying v6AssignMode for network ' + req.params.nwid + ': ' + err}); + } +} + +// Display detail page for specific member +exports.member_detail = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + const member = await zt.member_detail(req.params.nwid, req.params.id); + res.render('member_detail', {title: 'Network member detail', page: page, network: network, member: member}); + } catch (err) { + res.render(req.params.object, {title: req.params.object, page: page, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err}); + } +}; + +// Member object GET +exports.member_object = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + const member = await zt.member_detail(req.params.nwid, req.params.id); + res.render(req.params.object, {title: req.params.object, page: page, network: network, member: member}, function(err, html) { + if (err) { + if (err.message.indexOf('Failed to lookup view') !== -1 ) { + return res.render('not_implemented', {title: req.params.object, page: page, network: network, member: member}); + } + throw err; + } + res.send(html); + }); + } catch (err) { + res.render(req.params.object, {title: req.params.object, page: page, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err}); + } +} + +// Member authorized POST +exports.member_authorized = async function(req, res) { + const page = 'networks'; + + const authorized = { authorized: req.body.authorized }; + + try { + const network = await zt.network_detail(req.params.nwid); + const member = await zt.member_object(req.params.nwid, req.params.id, authorized); + res.render('authorized', {title: 'authorized', page: page, network: network, member: member}); + } catch (err) { + res.render('authorized', {title: 'authorized', page: page, error: 'Error authorizing member ' + req.params.id + ' on network ' + req.params.nwid + ': ' + err}); + } +} + +// Easy network setup GET +exports.easy_get = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + res.render('network_easy', {title: 'Easy setup of network', page: page, network: network}); + } catch (err) { + res.render('network_easy', {title: 'Easy setup of network', page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); + } +} + +// Easy network setup POST +exports.easy_post = async function(req, res) { + const page = 'networks'; + + req.checkBody('networkCIDR', 'Network address is required').notEmpty(); + req.sanitize('networkCIDR').trim(); + req.checkBody('networkCIDR', 'Network address must be in CIDR notation') + .custom(value => { + const parts = value.split('/'); + const ipv4 = new ipaddr.Address4(parts[0]); + return ipv4.isValid() && isValidPrefix(parts[1], 32); + }); + req.checkBody('poolStart', 'Start of IP assignment pool is required') + .notEmpty(); + req.checkBody('poolStart', 'Start of IP assignment pool must be valid IPv4 address') + .isIP(4); + req.sanitize('poolStart').escape(); + req.sanitize('poolStart').trim(); + req.checkBody('poolEnd', 'End of IP assignment pool is required') + .notEmpty(); + req.checkBody('poolEnd', 'End of IP assignment pool must be valid IPv4 address') + .isIP(4); + req.sanitize('poolEnd').escape(); + req.sanitize('poolEnd').trim(); + + const errors = req.validationErrors(); + + const ipAssignmentPools = + [{ + ipRangeStart: req.body.poolStart, + ipRangeEnd: req.body.poolEnd + }]; + + const routes = + [{ + target: req.body.networkCIDR, + via: null + }]; + + const v4AssignMode = + { + zt: true + }; + + if (errors) { + network = + { + ipAssignmentPools: ipAssignmentPools, + routes: routes, + v4AssignMode: v4AssignMode + }; + + res.render('network_easy', {title: 'Easy setup of network', page: page, network: network, errors: errors}); + } else { + try { + const network = await zt.network_easy_setup(req.params.nwid, + routes, + ipAssignmentPools, + v4AssignMode); + res.render('network_easy', {title: 'Easy setup of network', page: page, network: network, message: 'Network setup succeeded'}); + } catch (err) { + res.render('network_easy', {title: 'Easy setup of network', page: page, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); + } + } +} + +// Easy members auth GET or POST +exports.members = async function(req, res) { + const page = 'networks'; + + let errors = null; + + if (req.method === 'POST') { + + req.checkBody('id', 'Member ID is required').notEmpty(); + req.sanitize('id').trim(); + req.sanitize('id').escape(); + + if (req.body.auth) { + req.checkBody('auth', 'Authorization state must be boolean').isBoolean(); + req.sanitize('auth').trim(); + req.sanitize('auth').escape(); + + errors = req.validationErrors(); + + if (!errors) { + const auth = + { + authorized: req.body.auth + }; + + try { + const mem = await zt.member_object(req.params.nwid, req.body.id, auth); + } catch (err) { + throw err; + } + } + } else if (req.body.name) { + req.sanitize('name').trim(); + req.sanitize('name').escape(); + + errors = req.validationErrors(); + + if (!errors) { + try { + const ret = await storage.setItem(req.body.id, req.body.name); + } catch (err) { + throw err; + } + } + } + } + + try { + const network = await zt.network_detail(req.params.nwid); + const member_ids = await zt.members(req.params.nwid); + const members = []; + for (id in member_ids) { + let member = await zt.member_detail(req.params.nwid, id); + let name = await storage.getItem(member.id); + if (!name) name = ''; + member.name = name; + members.push(member); + } + + res.render('members', {title: 'Members of this network', page: page, + network: network, members: members, errors: errors}); + } catch (err) { + res.render('members', {title: 'Members of this network', page: page, + error: 'Error resolving detail for network ' + req.params.nwid + + ': ' + err}); + } +} + +// Member delete GET and POST +exports.member_delete = async function(req, res) { + const page = 'networks'; + + try { + const network = await zt.network_detail(req.params.nwid); + let member = null; + let name = null; + if (req.method === 'POST') { + member = await zt.member_delete(req.params.nwid, req.params.id); + if (member.deleted) { + name = await storage.removeItem(member.id); + } + } else { + member = await zt.member_detail(req.params.nwid, req.params.id); + name = await storage.getItem(member.id); + } + if (!name) name = ''; + member.name = name; + res.render('member_delete', {title: 'Delete member from ' + network.name, + network: network, member: member}); + } catch (err) { + res.render('member_delete', {title: 'Delete member from network', page: page, + error: 'Error resolving detail for member ' + req.params.id + + ' of network ' + req.params.nwid + ': ' + err}); + } +} + diff --git a/controllers/token.js b/controllers/token.js new file mode 100644 index 0000000..6931b0b --- /dev/null +++ b/controllers/token.js @@ -0,0 +1,25 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const fs = require('fs'); +const util = require('util'); + +const readFile = util.promisify(fs.readFile); + +let _token = null; + +exports.get = async function() { + if (_token) { + return _token; + } else { + try { + _token = await readFile('/var/lib/zerotier-one/authtoken.secret', 'utf8'); + return _token; + } catch(err) { + throw(err); + } + } +} diff --git a/controllers/usersController.js b/controllers/usersController.js new file mode 100644 index 0000000..7695b64 --- /dev/null +++ b/controllers/usersController.js @@ -0,0 +1,173 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const fs = require('fs'); +const argon2 = require('argon2'); +const util = require('util'); + +const passwd_file = 'etc/passwd'; +const min_pass_len = 10; + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); +const chmod = util.promisify(fs.chmod); + +let _users = null; + +get_users = async function() { + if (_users) { + return _users; + } else { + try { + _users = JSON.parse(await readFile(passwd_file, 'utf8')); + return _users; + } catch(err) { + throw(err); + } + } +} +exports.get_users = get_users; + +update_users = async function(users) { + try { + await writeFile(passwd_file, JSON.stringify(users), 'utf8'); + await chmod(passwd_file, 0600); + + } catch (err) { + throw err; + } + _users = null; + return await get_users(); +} + +exports.users_list = async function(req, res) { + const page = 'users'; + + try { + const users = await get_users(); + res.render('users', { title: 'Admin users', page: page, message: 'List of users with admin priviledges', users: users }); + } catch (err) { + res.render('users', { title: 'Admin users', page: page, message: 'Error', users: null, error: 'Error returning list of users: ' + err }); + } +} + +exports.password_get = async function(req, res) { + const page = 'users'; + + const user = + { + name: req.params.name, + password1: null, + password2: null + }; + res.render('password', { title: 'Set password', page: page, user: user, readonly: true, message: '' }); +} + +exports.password_post = async function(req, res) { + const page = 'users'; + + req.checkBody('username', 'Username required').notEmpty(); + req.sanitize('username').escape(); + req.sanitize('username').trim(); + + req.checkBody('password1', 'Password required').notEmpty(); + req.checkBody('password1', 'Minimum password length is ' + min_pass_len + ' characters').isLength({ min: min_pass_len, max: 160 }); + + req.checkBody('password2', 'Please re-enter password').notEmpty(); + req.checkBody('password2', 'Minimum password length is ' + min_pass_len + ' characters').isLength({ min: min_pass_len, max: 160 }); + req.checkBody('password2', 'Passwords are not the same').equals(req.body.password1); + + const errors = req.validationErrors(); + + if (errors) { + const user = + { + name: req.body.username, + password1: req.body.password1, + password2: req.body.password2 + }; + const message = 'Please check errors below'; + res.render('password', { title: 'Set password', page: page, user: user, readonly: true, message: message, errors: errors }); + } else { + let pass_set = true; + if (req.body.pass_set === 'check') pass_set = false; + + const hash = await argon2.hash(req.body.password1); + + const user = + { + name: req.body.username, + pass_set: pass_set, + hash: hash + }; + + const passwd_user = + { + [req.body.username]: user + }; + + let users = await get_users(); + users[req.body.username] = user; + + users = await update_users(users); + + const message = 'Successfully set password for ' + req.body.username; + res.render('password', { title: 'Set password', page: page, user: user, readonly: true, message: message }); + } +} + +exports.user_create_get = async function(req, res) { + const page = 'create_user'; + + const user = + { + name: null, + password1: null, + password2: null + }; + + res.render('password', { title: 'Create new admin user', page: page, user: user, readonly: false}); +} + +exports.user_create_post = async function(req, res) { + const page = 'create_user'; + + res.redirect(307, '/users/' + req.body.username + '/password'); +} + +exports.user_delete = async function(req, res) { + const page = 'users'; + + try { + var users = await get_users(); + } catch (err) { + throw err; + } + + const user = users[req.params.name]; + + if (user && (req.session.user.name === user.name)) { + res.render('user_delete', { title: 'Delete user', page: page, user: user, self_delete: true }); + } + + if (req.body.delete === 'delete') { + if (user) { + const deleted_user = { name: user.name }; + delete users[user.name]; + users = await update_users(users); + res.render('user_delete', { title: 'Deleted user', page: page, user: deleted_user, deleted: true }); + } else { + res.render('user_delete', { title: 'Delete user', page: page, user: null }); + } + } else { + if (user) { + res.render('user_delete', { title: 'Delete user', page: page, user: user }); + } else { + res.render('user_delete', { title: 'Delete user', page: page, user: null }); + } + } +} + diff --git a/controllers/zt.js b/controllers/zt.js new file mode 100644 index 0000000..f079755 --- /dev/null +++ b/controllers/zt.js @@ -0,0 +1,266 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const got = require('got'); +const ipaddr = require('ip-address'); +const token = require('./token'); + +init_options = async function() { + let tok = null; + + try { + tok = await token.get(); + } catch (err) { + throw(err); + } + + options = { + json: true, + headers: { + 'X-ZT1-Auth': tok + } + } + + return options; +} + +get_zt_address = async function() { + const options = await init_options(); + + try { + const response = await got('localhost:9993/status', options); + return response.body.address; + } catch(err) { + throw(err); + } +} +exports.get_zt_address = get_zt_address; + +exports.network_list = async function() { + const options = await init_options(); + + let network = {}; + let networks = []; + let nwids = []; + + try { + const response = await got('localhost:9993/controller/network', options); + nwids = response.body; + } catch(err) { + throw(err); + } + + for (let nwid of nwids) { + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + network = (({name, nwid}) => ({name, nwid}))(response.body); + networks.push(network); + } catch(err) { + throw(err); + } + } + return networks; +} + +network_detail = async function(nwid) { + const options = await init_options(); + + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + return response.body; + } catch(err) { + throw(err); + } +} +exports.network_detail = network_detail; + +exports.network_create = async function(name) { + const options = await init_options(); + options.method = 'POST'; + options.body = name; + + const zt_address = await get_zt_address(); + + try { + const response = await got('localhost:9993/controller/network/' + + zt_address + '______', options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.network_delete = async function(nwid) { + const options = await init_options(); + options.method = 'DELETE'; + + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + response.body.deleted = true; + return response.body; + } catch(err) { + throw(err); + } +} + +exports.ipAssignmentPools = async function(nwid, ipAssignmentPool, action) { + const options = await init_options(); + options.method = 'POST'; + + const network = await network_detail(nwid); + let ipAssignmentPools = network.ipAssignmentPools; + + if (action === 'add') { + ipAssignmentPools.push(ipAssignmentPool); + } else if (action === 'delete') { + const pool = ipAssignmentPools.find(pool => + pool.ipRangeStart === ipAssignmentPool.ipRangeStart && + pool.ipRangeEnd === ipAssignmentPool.ipRangeEnd); + ipAssignmentPools = ipAssignmentPools.filter(p => p != pool); + } + + options.body = { ipAssignmentPools: ipAssignmentPools }; + + + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.routes = async function(nwid, route, action) { + const options = await init_options(); + options.method = 'POST'; + + const network = await network_detail(nwid); + let routes = network.routes; + const target6 = new ipaddr.Address6(route.target); + if (target6.isValid()) { + const parts = route.target.split('/'); + route.target = target6.canonicalForm() + '/' + parts[1]; + } + + const route_to_del = routes.find(rt => rt.target === route.target); + + if (!route_to_del) { + if (action === 'add') { + routes.push(route); + } else if (action === 'delete') { + throw new Error('Cannot delete non-existent route target'); + } + } else { + if (action === 'add') { + throw new Error('Route target is not unique'); + } else if (action === 'delete') { + routes = routes.filter(rt => rt != route_to_del); + } + } + + options.body = { routes: routes }; + + + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.network_object = async function(nwid, object) { + const options = await init_options(); + options.method = 'POST'; + options.body = object; + + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.members = async function(nwid) { + const options = await init_options(); + + try { + const response = await got('localhost:9993/controller/network/' + + nwid + '/member', options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.member_detail = async function(nwid, id) { + const options = await init_options(); + + try { + const response = await got('localhost:9993/controller/network/' + + nwid + '/member/' + id, options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.member_object = async function(nwid, id, object) { + const options = await init_options(); + options.method = 'POST'; + options.body = object; + + try { + const response = await got('localhost:9993/controller/network/' + + nwid + '/member/' + id, options); + return response.body; + } catch(err) { + throw(err); + } +} + +exports.member_delete = async function(nwid, id) { + const options = await init_options(); + options.method = 'DELETE'; + + try { + const response = await got('localhost:9993/controller/network/' + + nwid + '/member/' + id, options); + response.body.deleted = true; + return response.body; + } catch(err) { + throw(err); + } +} + +exports.network_easy_setup = async function(nwid, + routes, + ipAssignmentPools, + v4AssignMode) { + const options = await init_options(); + options.method = 'POST'; + options.body = + { + ipAssignmentPools: ipAssignmentPools, + routes: routes, + v4AssignMode: v4AssignMode + }; + + try { + const response = await got('localhost:9993/controller/network/' + + nwid, options); + return response.body; + } catch(err) { + throw(err); + } +} diff --git a/etc/passwd b/etc/passwd new file mode 100644 index 0000000..55573c1 --- /dev/null +++ b/etc/passwd @@ -0,0 +1 @@ +{"admin":{"name":"admin","pass_set":false,"hash":"$argon2i$v=19$m=4096,t=3,p=1$/VYxjWHBzbkuCEO6Hh0AUw$nJaTJtth57vCAyYvg+UbtnscilR0UcE02AfLOhERe3A"}} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d17ff6b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3401 @@ +{ + "name": "ztncui", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/body-parser": { + "version": "1.16.7", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.7.tgz", + "integrity": "sha512-Obn1/GG0sYsnlAlhhSR1hvYRGBpQT+fzSi2IlGN8emCE4iu6f6xIjaq499B1sa7N9iBLzxyOUBo5bzgJd16BvA==", + "requires": { + "@types/express": "4.0.39", + "@types/node": "8.0.49" + } + }, + "@types/express": { + "version": "4.0.39", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.39.tgz", + "integrity": "sha512-dBUam7jEjyuEofigUXCtublUHknRZvcRgITlGsTbFgPvnTwtQUt2NgLakbsf+PsGo/Nupqr3IXCYsOpBpofyrA==", + "requires": { + "@types/body-parser": "1.16.7", + "@types/express-serve-static-core": "4.0.56", + "@types/serve-static": "1.13.0" + } + }, + "@types/express-serve-static-core": { + "version": "4.0.56", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.56.tgz", + "integrity": "sha512-/0nwIzF1Bd4KGwW4lhDZYi5StmCZG1DIXXMfQ/zjORzlm4+F1eRA4c6yJQrt4hqX//TDtPULpSlYwmSNyCMeMg==", + "requires": { + "@types/node": "8.0.49" + } + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==" + }, + "@types/node": { + "version": "8.0.49", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.49.tgz", + "integrity": "sha512-Oq3cV/mrMKy6Tv42llfS8YIH30ooDdhbJ40h1zoWl+goOJw8Kjy8j8RfjGZtZIUDO0gLwCfcbYM7+LModnbeMw==" + }, + "@types/serve-static": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.0.tgz", + "integrity": "sha512-wvQkePwCDZoyQPGb64DTl2TEeLw54CQFXjY+tznxYYxNcBb4LG40ezoVbMDa0epwE4yogB0f42jCaH0356x5Mg==", + "requires": { + "@types/express-serve-static-core": "4.0.56", + "@types/mime": "2.0.0" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=", + "dev": true + }, + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "4.0.13" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "longest": "1.0.1", + "repeat-string": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "2.1.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", + "dev": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, + "argon2": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.16.2.tgz", + "integrity": "sha512-R9IcqgINNGsMdLNHUkVEtKGSDrnGYvGbdg2h9Agldhfx013G5ssrPVgvf6poC9yd2+flHiPQlOxO4+/qAfprhA==", + "requires": { + "any-promise": "1.3.0", + "bindings": "1.3.0", + "nan": "2.8.0" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-auth": { + "version": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + }, + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "debug": "2.6.9", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + } + } + }, + "bootstrap": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", + "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.3.0", + "cli-boxes": "1.0.0", + "string-width": "2.1.1", + "term-size": "1.2.0", + "widest-line": "2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "capture-stack-trace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "1.0.4" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.1.3", + "glob-parent": "2.0.0", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, + "clean-css": { + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", + "requires": { + "commander": "2.8.1", + "source-map": "0.4.4" + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "configstore": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", + "integrity": "sha1-CU7mYquD+tmRdnjeEU+q6o/NypA=", + "dev": true, + "requires": { + "dot-prop": "4.2.0", + "graceful-fs": "4.1.11", + "make-dir": "1.1.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.3.0", + "xdg-basedir": "3.0.0" + } + }, + "constantinople": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.0.tgz", + "integrity": "sha1-dWnKqKo/jVk11i4fqW+fcCzYHHk=", + "requires": { + "acorn": "3.3.0", + "is-expression": "2.1.0" + } + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" + }, + "cookie": { + "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-parser": { + "version": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + } + }, + "cookie-signature": { + "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "crc": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", + "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "1.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "dev": true, + "requires": { + "is-obj": "1.0.1" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "ee-first": { + "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "etag": { + "version": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "content-disposition": "0.5.2", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "debug": "2.6.9", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "finalhandler": "1.1.0", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.2", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "range-parser": "1.2.0", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "utils-merge": "1.0.1", + "vary": "1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "express-session": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", + "integrity": "sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA==", + "requires": { + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "crc": "3.4.4", + "debug": "2.6.9", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "on-headers": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "uid-safe": "2.1.5", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + } + } + }, + "express-validator": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-4.3.0.tgz", + "integrity": "sha512-EYU+JJ2EoLpcw+GKwbB1K8UGb/w1A70Wf3gD/zE9QScQxeSt8qad93lxGtsLwZFoiYM0EByVoSzHJnskp+eVHQ==", + "requires": { + "@types/express": "4.0.39", + "lodash": "4.17.4", + "validator": "8.2.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "statuses": "1.3.1", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "1.3.5" + } + }, + "got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "requires": { + "decompress-response": "3.3.0", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-plain-obj": "1.1.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "isurl": "1.0.0", + "lowercase-keys": "1.0.0", + "p-cancelable": "0.3.0", + "p-timeout": "1.2.1", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "timed-out": "4.0.1", + "url-parse-lax": "1.0.0", + "url-to-options": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "requires": { + "function-bind": "1.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "has-symbol-support-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz", + "integrity": "sha512-JkaetveU7hFbqnAC1EV1sF4rlojU2D4Usc5CmS69l6NfmPDnpnFUegzFg33eDkkpNCxZ0mQp65HwUDrNFS/8MA==" + }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "requires": { + "has-symbol-support-x": "1.4.1" + } + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + } + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "ip-address": { + "version": "5.8.9", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.8.9.tgz", + "integrity": "sha512-7ay355oMN34iXhET1BmCJVsHjOTSItEEIIpOs38qUC23AIhOy+xIPnkrTuEFjeLMrTJ7m8KMXWgWfy/2Vn9sDw==", + "requires": { + "jsbn": "1.1.0", + "lodash.find": "4.6.0", + "lodash.max": "4.0.1", + "lodash.merge": "4.6.0", + "lodash.padstart": "4.6.1", + "lodash.repeat": "4.1.0", + "sprintf-js": "1.1.0" + } + }, + "ipaddr.js": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, + "is-absolute": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", + "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", + "requires": { + "is-relative": "0.2.1", + "is-windows": "0.2.0" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.11.0" + } + }, + "is-buffer": { + "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-expression": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-2.1.0.tgz", + "integrity": "sha1-kb6dR968/vB3l36XIr5tz7RGXvA=", + "requires": { + "acorn": "3.3.0", + "object-assign": "4.1.1" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "1.0.1" + } + }, + "is-relative": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", + "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", + "requires": { + "is-unc-path": "0.1.2" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-unc-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", + "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", + "requires": { + "unc-path-regex": "0.1.2" + } + }, + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "requires": { + "has-to-string-tag-x": "1.4.1", + "is-object": "1.0.1" + } + }, + "jquery": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz", + "integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=" + }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA=" + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "2.1.0", + "promise": "7.3.1" + } + }, + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + } + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "4.0.1" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dev": true, + "requires": { + "lodash._bindcallback": "3.0.1", + "lodash._isiterateecall": "3.0.9", + "lodash.restparam": "3.6.1" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._createassigner": "3.1.1", + "lodash.keys": "3.1.2" + } + }, + "lodash.defaults": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", + "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", + "dev": true, + "requires": { + "lodash.assign": "3.2.0", + "lodash.restparam": "3.6.1" + } + }, + "lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.max": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.max/-/lodash.max-4.0.1.tgz", + "integrity": "sha1-hzVWbGGLNan3YFILSHrnllivE2o=" + }, + "lodash.merge": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz", + "integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU=" + }, + "lodash.padstart": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", + "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" + }, + "lodash.repeat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-4.1.0.tgz", + "integrity": "sha1-/H3oEx2MisB+S0n3T/6CnR8r7EQ=" + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "make-dir": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz", + "integrity": "sha1-GbQ2n+SMEW9Twq+VrRAsDjnoXVE=", + "dev": true, + "requires": { + "pify": "3.0.0" + } + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "media-typer": { + "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" + } + }, + "mimic-response": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", + "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "morgan": { + "version": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", + "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "requires": { + "basic-auth": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "debug": "2.6.9", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "on-headers": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + } + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "node-persist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz", + "integrity": "sha1-5lK784haBNrWo1PXQXYXfIORRwc=", + "requires": { + "is-absolute": "0.2.6", + "mkdirp": "0.5.1", + "q": "1.1.2" + } + }, + "nodemon": { + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.12.5.tgz", + "integrity": "sha512-Kwx492h2buPPOie50cht/PdV+jXLqk28l79Nzs1udrFWIXpYHKCskLict1hTrln4ux61azehZcwm8M5McmiuAw==", + "dev": true, + "requires": { + "chokidar": "1.7.0", + "debug": "2.6.9", + "es6-promise": "3.3.1", + "ignore-by-default": "1.0.1", + "lodash.defaults": "3.1.2", + "minimatch": "3.0.4", + "ps-tree": "1.1.0", + "touch": "3.1.0", + "undefsafe": "0.0.3", + "update-notifier": "2.3.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "on-finished": { + "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + } + }, + "on-headers": { + "version": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "requires": { + "p-finally": "1.0.0" + } + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "6.7.1", + "registry-auth-token": "3.3.1", + "registry-url": "3.1.0", + "semver": "5.4.1" + }, + "dependencies": { + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "1.0.4" + } + } + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "requires": { + "asap": "2.0.6" + } + }, + "proxy-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.5.2" + } + }, + "ps-tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", + "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "dev": true, + "requires": { + "event-stream": "3.3.4" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "pug": { + "version": "2.0.0-rc.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.0-rc.4.tgz", + "integrity": "sha512-SL7xovj6E2Loq9N0UgV6ynjMLW4urTFY/L/Fprhvz13Xc5vjzkjZjI1QHKq31200+6PSD8PyU6MqrtCTJj6/XA==", + "requires": { + "pug-code-gen": "2.0.0", + "pug-filters": "2.1.5", + "pug-lexer": "3.1.0", + "pug-linker": "3.0.3", + "pug-load": "2.0.9", + "pug-parser": "4.0.0", + "pug-runtime": "2.0.3", + "pug-strip-comments": "1.0.2" + } + }, + "pug-attrs": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.2.tgz", + "integrity": "sha1-i+KyIlVo/6ddG4Zpgr/59BEa/8s=", + "requires": { + "constantinople": "3.1.0", + "js-stringify": "1.0.2", + "pug-runtime": "2.0.3" + } + }, + "pug-code-gen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.0.tgz", + "integrity": "sha512-E4oiJT+Jn5tyEIloj8dIJQognbiNNp0i0cAJmYtQTFS0soJ917nlIuFtqVss3IXMEyQKUew3F4gIkBpn18KbVg==", + "requires": { + "constantinople": "3.1.0", + "doctypes": "1.1.0", + "js-stringify": "1.0.2", + "pug-attrs": "2.0.2", + "pug-error": "1.3.2", + "pug-runtime": "2.0.3", + "void-elements": "2.0.1", + "with": "5.1.1" + } + }, + "pug-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", + "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" + }, + "pug-filters": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-2.1.5.tgz", + "integrity": "sha1-Zr9ugNl/vvgpurCqNe3f8z/JZPM=", + "requires": { + "clean-css": "3.4.28", + "constantinople": "3.1.0", + "jstransformer": "1.0.0", + "pug-error": "1.3.2", + "pug-walk": "1.1.5", + "resolve": "1.5.0", + "uglify-js": "2.8.29" + } + }, + "pug-lexer": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-3.1.0.tgz", + "integrity": "sha1-/QhzdtSmdbT1n4/vQiiDQ06VgaI=", + "requires": { + "character-parser": "2.2.0", + "is-expression": "3.0.0", + "pug-error": "1.3.2" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "requires": { + "acorn": "4.0.13", + "object-assign": "4.1.1" + } + } + } + }, + "pug-linker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.3.tgz", + "integrity": "sha512-DCKczglCXOzJ1lr4xUj/lVHYvS+lGmR2+KTCjZjtIpdwaN7lNOoX2SW6KFX5X4ElvW+6ThwB+acSUg08UJFN5A==", + "requires": { + "pug-error": "1.3.2", + "pug-walk": "1.1.5" + } + }, + "pug-load": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.9.tgz", + "integrity": "sha1-7iF8kUzB2TJNRLhsMtHfJB023no=", + "requires": { + "object-assign": "4.1.1", + "pug-walk": "1.1.5" + } + }, + "pug-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-4.0.0.tgz", + "integrity": "sha512-ocEUFPdLG9awwFj0sqi1uiZLNvfoodCMULZzkRqILryIWc/UUlDlxqrKhKjAIIGPX/1SNsvxy63+ayEGocGhQg==", + "requires": { + "pug-error": "1.3.2", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.3.tgz", + "integrity": "sha1-mBYmB7D86eJU1CfzOYelrucWi9o=" + }, + "pug-strip-comments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.2.tgz", + "integrity": "sha1-0xOvoBvMN0mA4TmeI+vy65vchRM=", + "requires": { + "pug-error": "1.3.2" + } + }, + "pug-walk": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.5.tgz", + "integrity": "sha1-kOlDrLz3Ah5kVM8bMiRYkcum+FE=" + }, + "q": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", + "integrity": "sha1-Y1fikSBnAdmfGXq4TlforRlvKok=" + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + } + } + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + } + }, + "rc": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", + "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", + "dev": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "registry-auth-token": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", + "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "dev": true, + "requires": { + "rc": "1.2.2", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "1.2.2" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha1-HwmsznlsmnYlefMbLBzEw83fnzY=", + "requires": { + "path-parse": "1.0.5" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "0.1.4" + } + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "5.4.1" + } + }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "requires": { + "debug": "2.6.9", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "destroy": "1.0.4", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "mime": "1.4.1", + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "range-parser": "1.2.0", + "statuses": "1.3.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "serve-favicon": { + "version": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.4.5.tgz", + "integrity": "sha1-SdmkaGMVOpJAaRyJPSsOfYXW1DY=", + "requires": { + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "requires": { + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "send": "0.16.1" + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": "1.0.1" + } + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "sprintf-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.0.tgz", + "integrity": "sha1-z/yvcC2vZeo5u04PorKZzsGhvkY=" + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "dev": true, + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "0.7.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=", + "dev": true, + "requires": { + "nopt": "1.0.10" + } + }, + "type-is": { + "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "1.0.0" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" + }, + "undefsafe": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz", + "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=", + "dev": true + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "1.0.0" + } + }, + "unpipe": { + "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "update-notifier": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", + "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=", + "dev": true, + "requires": { + "boxen": "1.3.0", + "chalk": "2.3.0", + "configstore": "3.1.1", + "import-lazy": "2.1.0", + "is-installed-globally": "0.1.0", + "is-npm": "1.0.0", + "latest-version": "3.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "1.0.4" + } + }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "validator": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz", + "integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "widest-line": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", + "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "dev": true, + "requires": { + "string-width": "2.1.1" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "requires": { + "acorn": "3.3.0", + "acorn-globals": "3.1.0" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha1-H/YVdcLipOjlENb6TiQ8zhg5mas=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5e6040f --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "ztncui", + "version": "0.1.0", + "private": true, + "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, + "dependencies": { + "argon2": "^0.16.2", + "body-parser": "~1.18.2", + "bootstrap": "^3.3.7", + "cookie-parser": "~1.4.3", + "debug": "~3.1.0", + "express": "~4.16.2", + "express-session": "^1.15.6", + "express-validator": "^4.3.0", + "got": "^7.1.0", + "ip-address": "^5.8.9", + "jquery": "^3.2.1", + "morgan": "~1.9.0", + "node-persist": "^2.1.0", + "pug": "2.0.0-rc.4", + "serve-favicon": "~2.4.5" + }, + "devDependencies": { + "nodemon": "^1.12.5" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..297466d Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..496f442 --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,107 @@ +html, body { + height: 100%; + font: 16px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +input[type=checkbox] { + transform: scale(1.5); +} + +input[type=radio] { + transform: scale(1.5); +} + +.navbar-inverse { + background-color: #315b80; + border-color: #23415c; +} +.navbar-inverse .navbar-brand { + color: #ecf0f1; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; +} +.navbar-inverse .navbar-text { + color: #ecf0f1; +} +.navbar-inverse .navbar-nav > li > a { + color: #ecf0f1; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; +} +.navbar-inverse .navbar-nav > li > .dropdown-menu { + background-color: #315b80; +} +.navbar-inverse .navbar-nav > li > .dropdown-menu > li > a { + color: #ecf0f1; +} +.navbar-inverse .navbar-nav > li > .dropdown-menu > li > a:hover, +.navbar-inverse .navbar-nav > li > .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: #23415c; +} +.navbar-inverse .navbar-nav > li > .dropdown-menu > li.divider { + background-color: #23415c; +} +.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, +.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, +.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #23415c; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #23415c; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #ffffff; + background-color: #23415c; +} +.navbar-inverse .navbar-toggle { + border-color: #23415c; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #23415c; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ecf0f1; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #ecf0f1; +} +.navbar-inverse .navbar-link { + color: #ecf0f1; +} +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} + +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #ecf0f1; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #23415c; + } +} + +.navbar { + background-image: none; +} + diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..4872270 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,54 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const express = require('express'); +const auth = require('../controllers/auth'); +const authenticate = auth.authenticate; +const restrict = auth.restrict; +const router = express.Router(); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('front_door', {title: 'ztncui'}); +}); + +router.get('/logout', function(req, res) { + req.session.destroy(function() { + res.redirect('/'); + }); +}); + +router.get('/login', function(req, res) { + let message = null; + if (req.session.error) { + if (req.session.error !== 'Access denied!') { + message = req.session.error; + } + } else { + message = req.session.success; + } + res.render('login', { title: 'Login', message: message }); +}); + +router.post('/login', async function(req, res) { + await authenticate(req.body.username, req.body.password, function(err, user) { + if (user) { + req.session.regenerate(function() { + req.session.user = user; + req.session.success = 'Authenticated as ' + user.name; + if (user.pass_set) { + res.redirect('/controller'); + } else { + res.redirect('/users/' + user.name + '/password'); + } + }); + } else { + req.session.error = 'Authentication failed, please check your username and password.' + res.redirect('/login'); + } + }); +}); +module.exports = router; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..fbc1168 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,34 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const express = require('express'); +const router = express.Router(); +const auth = require('../controllers/auth'); +const restrict = auth.restrict; +const usersController = require('../controllers/usersController'); + +// GET request for users +router.get('/', restrict, usersController.users_list); + +// GET request for password +router.get('/:name/password', restrict, usersController.password_get); + +// POST request for password +router.post('/:name/password', restrict, usersController.password_post); + +// GET request for user create +router.get('/create', restrict, usersController.user_create_get); + +// POST request for user create +router.post('/create', restrict, usersController.user_create_post); + +// GET request for user delete +router.get('/:name/delete', restrict, usersController.user_delete); + +// POST request for user delete +router.post('/:name/delete', restrict, usersController.user_delete); + +module.exports = router; diff --git a/routes/zt_controller.js b/routes/zt_controller.js new file mode 100644 index 0000000..b74a5b9 --- /dev/null +++ b/routes/zt_controller.js @@ -0,0 +1,90 @@ +/* + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. +*/ + +const express = require('express'); +const auth = require('../controllers/auth'); +const restrict = auth.restrict; +const router = express.Router(); + +var networkController = require('../controllers/networkController'); + +// network routes // + +// GET ZT network controller home page +router.get('/', restrict, networkController.index); + +// Get request for creating a network +router.get('/network/create', restrict, networkController.network_create_get); + +// POST request for creating a network +router.post('/network/create', restrict, networkController.network_create_post); + +// GET request to delete network +router.get('/network/:nwid/delete', restrict, networkController.network_delete_get); + +// POST request to delete network +router.post('/network/:nwid/delete', restrict, networkController.network_delete_post); + +// POST request for Network name +router.post('/network/:nwid/name', restrict, networkController.name); + +// GET request for ipAssignmentPool delete +router.get('/network/:nwid/ipAssignmentPools/:ipRangeStart/:ipRangeEnd/delete', restrict, networkController.ipAssignmentPool_delete); + +// POST request for ipAssignmentPools +router.post('/network/:nwid/ipAssignmentPools', restrict, networkController.ipAssignmentPools); + +// GET request for route delete +router.get('/network/:nwid/routes/:target_ip/:target_prefix/delete', restrict, networkController.route_delete); + +// POST request for routes +router.post('/network/:nwid/routes', restrict, networkController.routes); + +// POST request for v4AssignMode +router.post('/network/:nwid/v4AssignMode', restrict, networkController.v4AssignMode); + +// POST request for v6AssignMode +router.post('/network/:nwid/v6AssignMode', restrict, networkController.v6AssignMode); + +// GET request for member delete +router.get('/network/:nwid/member/:id/delete', restrict, networkController.member_delete); + +// POST request for member delete +router.post('/network/:nwid/member/:id/delete', restrict, networkController.member_delete); + +// GET request for any member object +router.get('/network/:nwid/member/:id/:object', restrict, networkController.member_object); + +// POST request for member authorized +router.post('/network/:nwid/member/:id/authorized', restrict, networkController.member_authorized); + +// GET request for member detail +router.get('/network/:nwid/member/:id', restrict, networkController.member_detail); + +// GET request for easy network setup +router.get('/network/:nwid/easy', restrict, networkController.easy_get); + +// POST request for easy network setup +router.post('/network/:nwid/easy', restrict, networkController.easy_post); + +// GET request for easy member (de)authorization +router.get('/network/:nwid/members', restrict, networkController.members); + +// POST request for easy member (de)authorization +router.post('/network/:nwid/members', restrict, networkController.members); + + + +// GET request for any network object +router.get('/network/:nwid/:object', restrict, networkController.network_object); + +// GET request for one network +router.get('/network/:nwid', restrict, networkController.network_detail); + +// GET request for list of all networks +router.get('/networks', restrict, networkController.network_list); + +module.exports = router; diff --git a/views/authorized.pug b/views/authorized.pug new file mode 100644 index 0000000..dcd936d --- /dev/null +++ b/views/authorized.pug @@ -0,0 +1,31 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + + h4 for member + a(href='../' + member.address) #{member.address} + + form(method='POST' action='') + .radio + label + input(type='radio' name='authorized' value='true' checked=member.authorized) + | Yes + .radio + label + input(type='radio' name='authorized' value='false' checked=!member.authorized) + | No + + .form-group(style='padding-top: 10px') + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='../' + member.address name='cancel' role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/controller_layout.pug b/views/controller_layout.pug new file mode 100644 index 0000000..6c37d64 --- /dev/null +++ b/views/controller_layout.pug @@ -0,0 +1,36 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends head_layout + +block body_content + nav.navbar.navbar-inverse.navbar-fixed-top + .container-fluid + .navbar-header + button.navbar-toggle(type='button' data-toggle='collapse' data-target='#BarNav') + span.icon-bar + span.icon-bar + span.icon-bar + a.navbar-brand(href='https://key-networks.com' target='_blank') Key Networks + .collapse.navbar-collapse(id='BarNav') + ul.nav.navbar-nav + li(class=(page === 'controller_home'? 'active' : '')) + a(href='/controller') Home + li(class=(page === 'users'? 'active' : '')) + a(href='/users') Users + li(class=(page === 'networks'? 'active' : '')) + a(href='/controller/networks') Networks + li(class=(page === 'add_network'? 'active' : '')) + a(href='/controller/network/create') Add network + ul.nav.navbar-nav.navbar-right + li + a(href='/logout') + span.glyphicon.glyphicon-log-out + | Logout + + .container(style='margin-top:50px') + .row + .col-sm-12 + block content diff --git a/views/error.pug b/views/error.pug new file mode 100644 index 0000000..5d5790a --- /dev/null +++ b/views/error.pug @@ -0,0 +1,6 @@ +extends controller_layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/views/front_door.pug b/views/front_door.pug new file mode 100644 index 0000000..2ad7a27 --- /dev/null +++ b/views/front_door.pug @@ -0,0 +1,13 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends login_layout + +block login_content + h1!= title + + h2 + a(href='https://zerotier.com' target='_blank') ZeroTier + | network controller UI diff --git a/views/head_layout.pug b/views/head_layout.pug new file mode 100644 index 0000000..562e99e --- /dev/null +++ b/views/head_layout.pug @@ -0,0 +1,17 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +doctype html +html(lang='en') + head + title= title + meta(charset='utf-8') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='/bscss/bootstrap.min.css') + link(rel='stylesheet', href='/stylesheets/style.css') + script(src='/jqjs/jquery.min.js') + script(src='/bsjs/bootstrap.min.js') + body + block body_content diff --git a/views/index.pug b/views/index.pug new file mode 100644 index 0000000..ad9f7d2 --- /dev/null +++ b/views/index.pug @@ -0,0 +1,22 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends controller_layout + +block content + h1!= title + + h2 + a(href='https://zerotier.com' target='_blank') ZeroTier + | network controller UI + + h3 Network controller details + + if error + b #{error} + else + p This network controller has a ZeroTier address of #{zt_address} + + a(href='/controller/networks') List all networks on this network controller diff --git a/views/ipAssignmentPools.pug b/views/ipAssignmentPools.pug new file mode 100644 index 0000000..7800b8a --- /dev/null +++ b/views/ipAssignmentPools.pug @@ -0,0 +1,52 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + .row + .col-sm-12 + table.table.table-responsive.table-striped.table-hover + tr + th + th IP range start + th IP range end + each ipAssignmentPool in network.ipAssignmentPools + tr + td(width='3%') + a(href='/controller/network/' + network.nwid + '/ipAssignmentPools/' + ipAssignmentPool.ipRangeStart + '/' + ipAssignmentPool.ipRangeEnd + '/delete') + i.glyphicon.glyphicon-trash + td= ipAssignmentPool.ipRangeStart + td= ipAssignmentPool.ipRangeEnd + + .row + .col-sm-12 + h3 Add new IP Assignment Pool: + + form(method='POST' action='/controller/network/' + network.nwid + '/ipAssignmentPools') + .form-group.row + .col-sm-2 + label(for='ipRangeStart') IP range start: + .col-sm-12 + input#ipRangeStart.form-control(type='text' name='ipRangeStart' placeholder='IP range start' value=(undefined===ipAssignmentPool? '' : ipAssignmentPool.ipRangeStart)) + + .form-group.row + .col-sm-2 + label(for='ipRangeEnd') IP range end: + .col-sm-12 + input#ipRangeEnd.form-control(type='text' name='ipRangeEnd' placeholder='IP range end' value=(undefined===ipAssignmentPool? '' : ipAssignmentPool.ipRangeEnd)) + + .form-group.row + .col-sm-12 + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='/controller/network/' + network.nwid name='cancel' role='button') Cancel + + if errors + .row + .col-sm-12 + ul + for err in errors + li!= err.msg diff --git a/views/login.pug b/views/login.pug new file mode 100644 index 0000000..8ef6671 --- /dev/null +++ b/views/login.pug @@ -0,0 +1,44 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends login_layout + +block login_content + + if error + b #{error} + else + .row + .col-sm-12 + h1= title + + if message + .alert.alert-info + strong= message + + form.form-horizontal(method='POST' action='') + .form-group.row + .col-sm-2 + label.control-label(for='username') Username: + .col-sm-10 + .input-group + span.input-group-addon + i.glyphicon.glyphicon-user + input#username.form-control(type='text' name='username' placeholder='Enter your username') + + .form-group.row + .col-sm-2 + label.control-label(for='password') Password: + .col-sm-10 + .input-group + span.input-group-addon + i.glyphicon.glyphicon-lock + input#password.form-control(type='password' name='password' placeholder='Enter your password') + + .form-group.row + .col-sm-12 + button.btn.btn-primary(type='submit') Login + = ' ' + a.btn.btn-default(href='/' name='cancel' role='button') Cancel diff --git a/views/login_layout.pug b/views/login_layout.pug new file mode 100644 index 0000000..c142f6f --- /dev/null +++ b/views/login_layout.pug @@ -0,0 +1,27 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends head_layout + +block body_content + nav.navbar.navbar-inverse.navbar-fixed-top + .container-fluid + .navbar-header + button.navbar-toggle(type='button' data-toggle='collapse' data-target='#BarNav') + span.icon-bar + span.icon-bar + span.icon-bar + a.navbar-brand(href='https://key-networks.com' target='_blank') Key Networks + .collapse.navbar-collapse(id='BarNav') + ul.nav.navbar-nav.navbar-right + li + a(href='/login') + span.glyphicon.glyphicon-log-in + | Login + + .container(style='margin-top:50px') + .row + .col-sm-12 + block login_content diff --git a/views/member_delete.pug b/views/member_delete.pug new file mode 100644 index 0000000..96d0063 --- /dev/null +++ b/views/member_delete.pug @@ -0,0 +1,28 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + if member.deleted + .alert.alert-success + strong #{member.name} (#{member.id}) was deleted + a.btn.btn-default(href='../../members' name='networks' role='button') Members + + else + .alert.alert-info + strong. + Note: To undo a member deletion, just get the member to + join the network again + form(method='POST' action='') + button.btn.btn-primary(type='submit', name='delete') Delete #{member.name} (#{member.id}) + = ' ' + a.btn.btn-default(href='/controller/network/' + network.nwid + '/members', + name='cancel', role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/member_detail.pug b/views/member_detail.pug new file mode 100644 index 0000000..461bbf1 --- /dev/null +++ b/views/member_detail.pug @@ -0,0 +1,33 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + h4 for member + a(href= member.address) #{member.address} + + each value, key in member + .row + .col-sm-2 + a(href= member.address + '/' + key) #{key}: + .col-sm-10 + - if ((!!value ) && (value.constructor == Object)) + p { + each v2, k2 in value + p #{k2}: #{v2}, + p } + - else if ((!!value ) && (value.constructor == Array)) + p [ + each elem in value + p { + each v2, k2 in elem + p #{k2}: #{v2}, + p } + p ] + - else + | #{value} + + a.btn.btn-default(href='../members' name='networks' role='button') Members diff --git a/views/members.pug b/views/members.pug new file mode 100644 index 0000000..9dbde9f --- /dev/null +++ b/views/members.pug @@ -0,0 +1,64 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + script. + $(function() { + $('.checkbox').on('click', function() { + $.post('', {id: this.value, auth: this.checked}); + }); + }); + + $(function() { + $('.text').on('change', function() { + $.post('', {id: this.name, name: this.value}); + }); + }); + + form(method='POST' action='') + table.table.table-responsive.table-striped.table-hover + tr + td(width='3%') + = '' + td(width='20%') + | Member name + td(width='10%') + | Member ID + td(width='10%') + | Authorized + td(width='57%') + | IP assignment + each member in members + tr + - let url = '/controller/network/' + network.nwid + '/member/' + member.id + td + a(href=url + '/delete') + i.glyphicon.glyphicon-trash + td + input.form-control.text(type='text' name=member.id value=member.name) + td + a(href=url) #{member.id} + td + input.checkbox(type='checkbox' name='authCheckBox' value=member.id checked=(member.authorized? true : false)) + td + each ipAssignment in member.ipAssignments + each digit in ipAssignment + = digit + = ' ' + + else + .alert.alert-info + strong There are no members on this network - invite users to join #{network.nwid} + + a.btn.btn-default(href='/controller/networks' name='networks' role='button') Networks + = ' ' + a.btn.btn-default(href='' name='refresh' role='button') Refresh + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/name.pug b/views/name.pug new file mode 100644 index 0000000..c089ea2 --- /dev/null +++ b/views/name.pug @@ -0,0 +1,28 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + form(method='POST' action='') + + .form-group.row + .col-sm-2 + label(for='name') Network name: + .col-sm-10 + input#name.form-control(type='text' name='name' placeholder='New network name' value=(undefined===network.name? '' : network.name)) + + .form-group.row + .col-sm-12 + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='/controller/networks' name='cancel' role='button') Cancel + + if errors + .row + .col-sm-12 + ul + for err in errors + li!= err.msg diff --git a/views/network_create.pug b/views/network_create.pug new file mode 100644 index 0000000..2d2de35 --- /dev/null +++ b/views/network_create.pug @@ -0,0 +1,33 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends controller_layout + +block content + .row + .col-sm-12 + h1= title + + if error + b #{error} + else + + form(method='POST' action='') + .form-group.row + .col-sm-2 + label(for='name') Network name: + .col-sm-10 + input#name.form-control(type='text' name='name' placeholder='Enter new network name' value=(undefined===name ? '' : name.name)) + + .form-group.row + .col-sm-12 + button.btn.btn-primary(type='submit') Create Network + + if errors + .row + .col-sm-12 + ul + for err in errors + li!= err.msg diff --git a/views/network_delete.pug b/views/network_delete.pug new file mode 100644 index 0000000..37582c9 --- /dev/null +++ b/views/network_delete.pug @@ -0,0 +1,24 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + if network.deleted + .alert.alert-success + strong #{network.name} (#{network.nwid}) was deleted + + else + .alert.alert-danger + strong Warning! Deleting a network cannot be undone + form(method='POST' action='') + button.btn.btn-danger(type='submit', name='delete') Delete #{network.name} (#{network.nwid}) + = ' ' + a.btn.btn-default(href='/controller/networks', name='cancel', role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/network_detail.pug b/views/network_detail.pug new file mode 100644 index 0000000..11f2cdd --- /dev/null +++ b/views/network_detail.pug @@ -0,0 +1,47 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends controller_layout + +block content + if error + b #{error} + else + h2 + a(href= network.nwid + '/name') #{network.name} + | (#{network.nwid}): + + - if (members !== undefined) + h3 Members + each value, key in members + .row + .col-sm-2 + a(href= network.nwid + '/member/' + key) #{key} + .col-sm-10 + | revision: #{value} + + h3= title + each value, key in network + .row + .col-sm-2 + a(href= network.nwid + '/' + key) #{key}: + .col-sm-10 + - if ((!!value ) && (value.constructor == Object)) + p { + each v2, k2 in value + p #{k2}: #{v2}, + p } + - else if ((!!value ) && (value.constructor == Array)) + p [ + each elem in value + p { + each v2, k2 in elem + p #{k2}: #{v2}, + p }, + p ] + - else + | #{value} + + a.btn.btn-default(href='/controller/networks' name='networks' role='button') Networks diff --git a/views/network_easy.pug b/views/network_easy.pug new file mode 100644 index 0000000..7f22f36 --- /dev/null +++ b/views/network_easy.pug @@ -0,0 +1,88 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + script. + function randomOctet() { + return Math.floor(Math.random() * 255); + } + + function randomIPv4() { + const networkCIDR = document.getElementById('networkCIDR'); + const CIDR = '10.' + randomOctet() + '.' + randomOctet() + '.0/24'; + networkCIDR.value = CIDR; + CIDRtoPool(CIDR); + } + + function int32toIPv4String(int32) { + let ipv4 = ''; + ipv4 = ((int32 & 0xff000000)>>>24).toString(); + ipv4 += '.' + ((int32 & 0x00ff0000)>>>16).toString(); + ipv4 += '.' + ((int32 & 0x0000ff00)>>>8).toString(); + ipv4 += '.' + (int32 & 0x000000ff).toString(); + return ipv4; + } + + function CIDRtoPool(CIDR) { + const [start, prefix] = CIDR.split('/'); + if (undefined !== start && undefined !== prefix && + prefix > 0 && prefix < 33 && + /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(start) + ){ + const host32 = ((1 << (32 - parseInt(prefix))) - 1) >>> 0; + const net = start.split('.').map(oct => {return parseInt(oct)}); + let net32 = 0 >>> 0; + net32 = (net[0]<<24) + (net[1]<<16) + (net[2]<<8) + (net[3]); + net32 &= ~host32; + bcast32 = net32 + host32; + const networkCIDR = document.getElementById('networkCIDR'); + const poolStart = document.getElementById('poolStart'); + const poolEnd = document.getElementById('poolEnd'); + networkCIDR.value = int32toIPv4String(net32) + '/' + prefix; + poolStart.value = int32toIPv4String(net32 + 1); + poolEnd.value = int32toIPv4String(bcast32 - 1); + } else { + poolStart.value = 'Invalid network CIDR'; + poolEnd.value = 'Invalid network CIDR'; + } + } + + if message + .alert.alert-info + strong= message + + form(method='POST' action='') + .form-group + button.btn.btn-link.float-right(type='button' data-toggle='collapse' data-target='#help') Help + .collapse(id='help') + p Please note that this utility only supports IPv4 at this stage. + p Use the following button to automatically generate a random network address, otherwise fill in the network address CIDR manually and the IP assignment pool will be automatically calculated for you. You can manually alter these calculated values. + + .form-group + button.btn.btn-primary(id='genIPv4' type='button' onclick='randomIPv4()') Generate network address + + .form-group + label(for='networkCIDR') Network address in CIDR notation + input#networkCIDR.form-control(type='text' name='networkCIDR' onchange='CIDRtoPool(this.value)' placeholder='e.g. 10.11.12.0/24' value=(undefined===network.routes[0]? '' : network.routes[0].target)) + + .form-group + label(for='poolStart') Start of IP assignment pool + input#poolStart.form-control(type='text' name='poolStart' placeholder='e.g. 10.11.12.1' value=(undefined===network.ipAssignmentPools[0]? '' : network.ipAssignmentPools[0].ipRangeStart)) + + .form-group + label(for='poolEnd') End of IP assignment pool + input#poolEnd.form-control(type='text' name='poolEnd' placeholder='e.g. 10.11.12.254' value=(undefined===network.ipAssignmentPools[0]? '' : network.ipAssignmentPools[0].ipRangeEnd)) + + .form-group(style='padding-top: 10px') + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='/controller/networks' name='cancel' role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/network_layout.pug b/views/network_layout.pug new file mode 100644 index 0000000..d2394d3 --- /dev/null +++ b/views/network_layout.pug @@ -0,0 +1,19 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends controller_layout + +block content + if error + b #{error} + else + .row + .col-sm-12 + h2 + a(href='/controller/network/' + network.nwid) #{network.name} + | (#{network.nwid}): + h3= title + + block net_content diff --git a/views/networks.pug b/views/networks.pug new file mode 100644 index 0000000..56b83d1 --- /dev/null +++ b/views/networks.pug @@ -0,0 +1,47 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends controller_layout + +block content + h1= title + + if error + b #{error} + else + + table.table.table-responsive.table-striped.table-hover + tr + td(width='3%') + = '' + td(width='20%') + | Network name + td(width='10%') + | Network ID + td(width='7%') + = '' + td(width='10%') + = '' + td(width='50%') + = '' + each network in networks + tr + td + a(href='/controller/network/' + network.nwid + '/delete') + i.glyphicon.glyphicon-trash + td + a(href='/controller/network/' + network.nwid + '/name') #{network.name} + td + = network.nwid + td + a(href='/controller/network/' + network.nwid) detail + td + a(href='/controller/network/' + network.nwid + '/easy') easy setup + td + a(href='/controller/network/' + network.nwid + '/members') members + + else + tr + td There are no networks on this network controller diff --git a/views/not_implemented.pug b/views/not_implemented.pug new file mode 100644 index 0000000..5196046 --- /dev/null +++ b/views/not_implemented.pug @@ -0,0 +1,14 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + - if (member !== undefined) + h4 for member + a(href='../' + member.address) #{member.address} + h4 Editing of + b #{title} + | has not been implemented diff --git a/views/password.pug b/views/password.pug new file mode 100644 index 0000000..cca4098 --- /dev/null +++ b/views/password.pug @@ -0,0 +1,60 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends users_layout + +block users_content + if message + .row + .col-sm-12 + .alert.alert-info + strong= message + + form.form-horizontal(method='POST' action='') + .form-group.row + .col-sm-2 + label(for='username') Username: + .col-sm-10 + .input-group + span.input-group-addon + i.glyphicon.glyphicon-user + input#username.form-control(type='text' name='username' placeholder='Enter username' value=user.name readonly=readonly) + + .form-group.row + .col-sm-2 + label(for='password1') Enter new password: + .col-sm-10 + .input-group + span.input-group-addon + i.glyphicon.glyphicon-lock + input#password1.form-control(type='password' name='password1' placeholder='Enter new password' value=(undefined===user.password1? '' : user.password1)) + + .form-group.row + .col-sm-2 + label(for='password2') Re-enter password: + .col-sm-10 + .input-group + span.input-group-addon + i.glyphicon.glyphicon-lock + input#password2.form-control(type='password' name='password2' placeholder='Re-enter password' value=(undefined===user.password2? '' : user.password2)) + + .form-group.row + .col-sm-2 + label(for='pass_set') Change password on next login: + .col-sm-10 + input#pass_set(type='checkbox' name='pass_set' value='check') + + .form-group.row + .col-sm-12 + button.btn.btn-primary(type='submit') Set password + = ' ' + a.btn.btn-default(href='/users' name='cancel' role='button') Cancel + + if errors + .form-group.row + .col-sm-12 + ul + for err in errors + li!= err.msg diff --git a/views/routes.pug b/views/routes.pug new file mode 100644 index 0000000..adb22f6 --- /dev/null +++ b/views/routes.pug @@ -0,0 +1,52 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + .row + .col-sm-12 + table.table.table-responsive.table-striped.table-hover + tr + th + th Target + th Gateway + each route in network.routes + tr + td(width='3%') + a(href='/controller/network/' + network.nwid + '/routes/' + route.target + '/delete') + i.glyphicon.glyphicon-trash + td= route.target + td= route.via + + .row + .col-sm-12 + h3 Add new route: + + form(method='POST' action='/controller/network/' + network.nwid + '/routes') + .form-group.row + .col-sm-2 + label(for='target') Target: + .col-sm-12 + input#target.form-control(type='text' name='target' placeholder='e.g. 10.11.12.0/24' value=(undefined===route? '' : route.target)) + + .form-group.row + .col-sm-2 + label(for='via') Gateway: + .col-sm-12 + input#via.form-control(type='text' name='via' placeholder='e.g. 172.16.2.1 or leave blank if the target is the ZT network' value=(undefined===route? '' : route.via)) + + .form-group.row + .col-sm-2 + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='/controller/network/' + network.nwid name='cancel' role='button') Cancel + + if errors + .row + .col-sm-12 + ul + for err in errors + li!= err.msg diff --git a/views/user_delete.pug b/views/user_delete.pug new file mode 100644 index 0000000..56acef8 --- /dev/null +++ b/views/user_delete.pug @@ -0,0 +1,32 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends users_layout + +block users_content + if user === null + .alert.alert-warning + strong No such user + + else if self_delete === true + .alert.alert-danger + strong You may not delete yourself + + else if deleted + .alert.alert-success + strong #{user.name} was deleted + + else + .alert.alert-danger + strong Warning! Deleting a user cannot be undone + form(method='POST' action='') + button.btn.btn-danger(type='submit', name='delete' value='delete') Delete #{user.name} + = ' ' + a.btn.btn-default(href='/users', name='cancel', role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/users.pug b/views/users.pug new file mode 100644 index 0000000..91c3df3 --- /dev/null +++ b/views/users.pug @@ -0,0 +1,22 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends users_layout + +block users_content + table.table.table-responsive.table-striped.table-hover + each user in users + tr + td(width='3%') + a(href='/users/' + user.name + '/delete') + i.glyphicon.glyphicon-trash + td(width='15%') + a(href='/users/' + user.name + '/password') #{user.name} + td(width='82%') + a(href='/users/' + user.name + '/password') set password + + else + tr + td There are no users on this system diff --git a/views/users_layout.pug b/views/users_layout.pug new file mode 100644 index 0000000..3ef89cc --- /dev/null +++ b/views/users_layout.pug @@ -0,0 +1,38 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends head_layout + +block body_content + nav.navbar.navbar-inverse.navbar-fixed-top + .container-fluid + .navbar-header + button.navbar-toggle(type='button' data-toggle='collapse' data-target='#BarNav') + span.icon-bar + span.icon-bar + span.icon-bar + a.navbar-brand(href='https://key-networks.com' target='_blank') Key Networks + .collapse.navbar-collapse(id='BarNav') + ul.nav.navbar-nav + li(class=(page === 'home'? 'active' : '')) + a(href='/controller') Home + li(class=(page === 'users'? 'active' : '')) + a(href='/users') Users + li(class=(page === 'create_user'? 'active' : '')) + a(href='/users/create') Create user + ul.nav.navbar-nav.navbar-right + li + a(href='/logout') + span.glyphicon.glyphicon-log-out + | Logout + + .container(style='margin-top:50px') + .row + .col-sm-12 + if error + b #{error} + else + h1= title + block users_content diff --git a/views/v4AssignMode.pug b/views/v4AssignMode.pug new file mode 100644 index 0000000..8aa336d --- /dev/null +++ b/views/v4AssignMode.pug @@ -0,0 +1,29 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + p Let ZT assign IPv4 addresses? + + form(method='POST' action='') + .radio + label + input(type='radio' name='zt' value='true' checked=network.v4AssignMode.zt) + | Yes + .radio + label + input(type='radio' name='zt' value='false' checked=!network.v4AssignMode.zt) + | No + + .form-group(style='padding-top: 10px') + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='/controller/network/' + network.nwid name='cancel' role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg diff --git a/views/v6AssignMode.pug b/views/v6AssignMode.pug new file mode 100644 index 0000000..d4cc5e4 --- /dev/null +++ b/views/v6AssignMode.pug @@ -0,0 +1,54 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + form(method='POST' action='') + table.table.table-responsive.table-striped.table-hover + tr + th(width='25%') + th(width='2%') Yes + th(width='73%') No + tr + td ZT 6plane (/80 routable for each device) + td + .radio + label + input(type='radio' name='6plane' value='true' checked=network.v6AssignMode['6plane']) + td + .radio + label + input(type='radio' name='6plane' value='false' checked=!network.v6AssignMode['6plane']) + tr + td ZT rfc4193 (/128 for each device) + td + .radio + label + input(type='radio' name='rfc4193' value='true' checked=network.v6AssignMode.rfc4193) + td + .radio + label + input(type='radio' name='rfc4193' value='false' checked=!network.v6AssignMode.rfc4193) + tr + td Auto-assign from IP Assignment Pool + td + .radio + label + input(type='radio' name='zt' value='true' checked=network.v6AssignMode.zt) + td + .radio + label + input(type='radio' name='zt' value='false' checked=!network.v6AssignMode.zt) + + .form-group(style='padding-top: 10px') + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href='/controller/network/' + network.nwid name='cancel' role='button') Cancel + + if errors + ul + for err in errors + li!= err.msg