mirror of
https://github.com/key-networks/ztncui.git
synced 2024-08-31 04:28:00 +00:00
Building RPM and DEB packages with pkg and fpm
This commit is contained in:
6
src/.gitignore
vendored
Normal file
6
src/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
etc/passwd
|
||||
etc/storage/
|
||||
node_modules/
|
||||
*.swp
|
||||
.env
|
||||
ztncui
|
||||
76
src/app.js
Normal file
76
src/app.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
ztncui - ZeroTier network controller UI
|
||||
Copyright (C) 2017 Key Networks (https://key-networks.com)
|
||||
Licensed under GPLv3 - see LICENSE for details.
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const favicon = require('serve-favicon');
|
||||
const logger = require('morgan');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
const expressValidator = require('express-validator');
|
||||
const session = require('express-session');
|
||||
const helmet = require('helmet');
|
||||
|
||||
const index = require('./routes/index');
|
||||
const users = require('./routes/users');
|
||||
const zt_controller = require('./routes/zt_controller');
|
||||
|
||||
const app = express();
|
||||
|
||||
const session_secret = Math.random().toString(36).substring(2,12);
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'pug');
|
||||
|
||||
app.use(helmet());
|
||||
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
|
||||
app.use(logger('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(session({
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
secret: session_secret
|
||||
}));
|
||||
app.use(expressValidator());
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use('/fonts', express.static(path.join(__dirname, 'node_modules/bootstrap/fonts')));
|
||||
app.use('/bscss', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css')));
|
||||
app.use('/jqjs', express.static(path.join(__dirname, 'node_modules/jquery/dist')));
|
||||
app.use('/bsjs', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js')));
|
||||
|
||||
app.use('/', index);
|
||||
app.use('/users', users);
|
||||
app.use('/controller', zt_controller);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
var err = req.session.error;
|
||||
var msg = req.session.success;
|
||||
delete req.session.error;
|
||||
delete req.session.success;
|
||||
res.locals.message = '';
|
||||
if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
|
||||
if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
|
||||
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;
|
||||
116
src/bin/www
Executable file
116
src/bin/www
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('ztncui:server');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const options = {
|
||||
cert: fs.readFileSync('etc/tls/fullchain.pem'),
|
||||
key: fs.readFileSync('etc/tls/privkey.pem')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ports from environment and store in Express.
|
||||
*/
|
||||
|
||||
const http_port = normalizePort(process.env.HTTP_PORT || '3000');
|
||||
app.set('http_port', http_port);
|
||||
const https_port = normalizePort(process.env.HTTPS_PORT || null);
|
||||
app.set('https_port', https_port);
|
||||
|
||||
/**
|
||||
* Get interface address on which to listen for HTTPS requests from env.
|
||||
*/
|
||||
const https_host = process.env.HTTPS_HOST || null;
|
||||
app.set('https_host', https_host);
|
||||
|
||||
/**
|
||||
* Create HTTPS server and listen on localhost only for HTTP and
|
||||
* on all network interfaces for HTTPS if HTTPS_PORT is set in env,
|
||||
* or on specific interface if HTTPS_HOST is set in env.
|
||||
*/
|
||||
|
||||
app.listen(http_port, 'localhost');
|
||||
const server = https.createServer(options, app);
|
||||
|
||||
if (https_port) {
|
||||
if (https_host) {
|
||||
console.log('Listening for HTTPS requests on port ' + https_port + ' on address ' + https_host);
|
||||
} else {
|
||||
console.log('Listening for HTTPS requests on port ' + https_port + ' on all interfaces');
|
||||
}
|
||||
server.listen(https_port, https_host);
|
||||
}
|
||||
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
const port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP/S server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const bind = typeof http_port === 'string'
|
||||
? 'Pipe ' + http_port
|
||||
: 'Port ' + http_port;
|
||||
|
||||
const sbind = typeof https_port === 'string'
|
||||
? 'Pipe ' + https_port
|
||||
: 'Port ' + https_port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' and ' + sbind + ' require elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' and/or ' + sbind + ' already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTPS server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
48
src/controllers/auth.js
Normal file
48
src/controllers/auth.js
Normal file
@@ -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');
|
||||
}
|
||||
}
|
||||
559
src/controllers/networkController.js
Normal file
559
src/controllers/networkController.js
Normal file
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
25
src/controllers/token.js
Normal file
25
src/controllers/token.js
Normal file
@@ -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 = process.env.ZT_TOKEN;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
173
src/controllers/usersController.js
Normal file
173
src/controllers/usersController.js
Normal file
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
268
src/controllers/zt.js
Normal file
268
src/controllers/zt.js
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
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');
|
||||
|
||||
ZT_ADDR = process.env.ZT_ADDR || 'localhost:9993';
|
||||
|
||||
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(ZT_ADDR + '/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(ZT_ADDR + '/controller/network', options);
|
||||
nwids = response.body;
|
||||
} catch(err) {
|
||||
throw(err);
|
||||
}
|
||||
|
||||
for (let nwid of nwids) {
|
||||
try {
|
||||
const response = await got(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/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(ZT_ADDR + '/controller/network/'
|
||||
+ nwid, options);
|
||||
return response.body;
|
||||
} catch(err) {
|
||||
throw(err);
|
||||
}
|
||||
}
|
||||
1
src/etc/default.passwd
Normal file
1
src/etc/default.passwd
Normal file
@@ -0,0 +1 @@
|
||||
{"admin":{"name":"admin","pass_set":false,"hash":"$argon2i$v=19$m=4096,t=3,p=1$/VYxjWHBzbkuCEO6Hh0AUw$nJaTJtth57vCAyYvg+UbtnscilR0UcE02AfLOhERe3A"}}
|
||||
2
src/etc/tls/.gitignore
vendored
Normal file
2
src/etc/tls/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
3525
src/package-lock.json
generated
Normal file
3525
src/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
src/package.json
Normal file
38
src/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "ztncui",
|
||||
"version": "0.3.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",
|
||||
"dotenv": "^4.0.0",
|
||||
"express": "~4.16.2",
|
||||
"express-session": "^1.15.6",
|
||||
"express-validator": "^4.3.0",
|
||||
"got": "^7.1.0",
|
||||
"helmet": "^3.9.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"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": [
|
||||
"views/*",
|
||||
"public/**/*",
|
||||
"etc/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
src/public/.well-known/acme-challenge/.gitignore
vendored
Normal file
2
src/public/.well-known/acme-challenge/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
BIN
src/public/favicon.ico
Normal file
BIN
src/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 B |
66
src/public/images/key-logo.svg
Normal file
66
src/public/images/key-logo.svg
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="200mm"
|
||||
height="200mm"
|
||||
viewBox="0 0 200 200"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="logo-vector-scanned-2.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.49497475"
|
||||
inkscape:cx="702.50685"
|
||||
inkscape:cy="481.47538"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="718"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-global="false" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-97)">
|
||||
<path
|
||||
style="fill:#ff0000;stroke-width:12.5;fill-opacity:1"
|
||||
d="m 12.5,209.5 c 0,-33.02363 -0.01942,-35.71328 -0.02608,-55.50705 -3.43e-4,-1.01896 0.188283,-4.2036 1.659648,-5.58767 1.471365,-1.38407 4.96429,-1.34748 4.96429,-1.34748 0,0 17.09385,-0.12474 38.636981,-0.12474 23.625291,0 64.605881,0.12862 76.434461,0.12862 1.60198,-8.10702 5.29415,-15.0635 9.43137,-20.72149 20.62554,-28.207112 46.03749,-10.62708 46.03749,33.15969 0,43.78677 -25.41196,61.36681 -46.03749,33.15969 -3.8105,-5.21118 -5.48312,-9.8996 -9.14695,-20.24734 C 123.36856,172.36387 99.404928,172 82.996967,172 c -20.050677,0 -34.03699,0.0515 -45.579233,0.0515 0,9.86746 0.08227,31.22419 0.08227,41.71342 0,23.33645 5.681817,9.33305 20.288576,-9.60113 13.537069,-17.54759 23.5314,-6.9188 13.086147,10.04556 -7.225806,11.73558 -20.035038,16.21681 5.440173,40.625 C 96.104161,273.79475 62.923641,278.5136 52.5,257 c -9.066,-18.71153 -15.082266,-10.25203 -15.082266,1.41463 0,1.17661 0.0074,4.3189 0,6.26873 -0.01053,2.77282 -0.484336,3.10831 -1.663828,4.29432 -1.179492,1.18601 -4.854353,1.18601 -4.854353,1.18601 h -8.624585 -3.754135 c 0,0 -3.492127,0.2914 -4.599177,-0.7684 -1.107049,-1.0598 -1.446946,-1.40674 -1.444746,-4.9013 C 12.484325,253.12341 12.5,231.23723 12.5,209.5 Z m 162.5,-50 c 0,-13.75 -5.625,-25 -12.5,-25 -6.875,0 -12.5,11.25 -12.5,25 0,13.75 5.625,25 12.5,25 6.875,0 12.5,-11.25 12.5,-25 z"
|
||||
id="path8033"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sczcscssscscssssscczccczcssssss" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
107
src/public/stylesheets/style.css
Normal file
107
src/public/stylesheets/style.css
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
54
src/routes/index.js
Normal file
54
src/routes/index.js
Normal file
@@ -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;
|
||||
34
src/routes/users.js
Normal file
34
src/routes/users.js
Normal file
@@ -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;
|
||||
90
src/routes/zt_controller.js
Normal file
90
src/routes/zt_controller.js
Normal file
@@ -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;
|
||||
31
src/views/authorized.pug
Normal file
31
src/views/authorized.pug
Normal file
@@ -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
|
||||
38
src/views/controller_layout.pug
Normal file
38
src/views/controller_layout.pug
Normal file
@@ -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')
|
||||
img(src='/images/key-logo.svg' alt='Key Networks logo' height='25px' width='25px' style='display: inline')
|
||||
| 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
|
||||
6
src/views/error.pug
Normal file
6
src/views/error.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extends controller_layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
||||
13
src/views/front_door.pug
Normal file
13
src/views/front_door.pug
Normal file
@@ -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
|
||||
17
src/views/head_layout.pug
Normal file
17
src/views/head_layout.pug
Normal file
@@ -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
|
||||
22
src/views/index.pug
Normal file
22
src/views/index.pug
Normal file
@@ -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 <b>#{zt_address}</b>
|
||||
|
||||
a(href='/controller/networks') List all networks on this network controller
|
||||
52
src/views/ipAssignmentPools.pug
Normal file
52
src/views/ipAssignmentPools.pug
Normal file
@@ -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
|
||||
44
src/views/login.pug
Normal file
44
src/views/login.pug
Normal file
@@ -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
|
||||
29
src/views/login_layout.pug
Normal file
29
src/views/login_layout.pug
Normal file
@@ -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 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')
|
||||
img(src='/images/key-logo.svg' alt='Key Networks logo' height='25px' width='25px' style='display: inline')
|
||||
| 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
|
||||
28
src/views/member_delete.pug
Normal file
28
src/views/member_delete.pug
Normal file
@@ -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
|
||||
33
src/views/member_detail.pug
Normal file
33
src/views/member_detail.pug
Normal file
@@ -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
|
||||
64
src/views/members.pug
Normal file
64
src/views/members.pug
Normal file
@@ -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
|
||||
28
src/views/name.pug
Normal file
28
src/views/name.pug
Normal file
@@ -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
|
||||
33
src/views/network_create.pug
Normal file
33
src/views/network_create.pug
Normal file
@@ -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
|
||||
24
src/views/network_delete.pug
Normal file
24
src/views/network_delete.pug
Normal file
@@ -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
|
||||
47
src/views/network_detail.pug
Normal file
47
src/views/network_detail.pug
Normal file
@@ -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
|
||||
88
src/views/network_easy.pug
Normal file
88
src/views/network_easy.pug
Normal file
@@ -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
|
||||
19
src/views/network_layout.pug
Normal file
19
src/views/network_layout.pug
Normal file
@@ -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
|
||||
47
src/views/networks.pug
Normal file
47
src/views/networks.pug
Normal file
@@ -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
|
||||
th(width='3%')
|
||||
= ''
|
||||
th(width='20%')
|
||||
| Network name
|
||||
th(width='10%')
|
||||
| Network ID
|
||||
th(width='7%')
|
||||
= ''
|
||||
th(width='10%')
|
||||
= ''
|
||||
th(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
|
||||
.alert.alert-info
|
||||
strong There are no networks on this network controller - click "Add network" above to create a new network.
|
||||
14
src/views/not_implemented.pug
Normal file
14
src/views/not_implemented.pug
Normal file
@@ -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
|
||||
60
src/views/password.pug
Normal file
60
src/views/password.pug
Normal file
@@ -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
|
||||
52
src/views/routes.pug
Normal file
52
src/views/routes.pug
Normal file
@@ -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
|
||||
32
src/views/user_delete.pug
Normal file
32
src/views/user_delete.pug
Normal file
@@ -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
|
||||
22
src/views/users.pug
Normal file
22
src/views/users.pug
Normal file
@@ -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
|
||||
.alert.alert-info
|
||||
strong There are no users on this system
|
||||
40
src/views/users_layout.pug
Normal file
40
src/views/users_layout.pug
Normal file
@@ -0,0 +1,40 @@
|
||||
//-
|
||||
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')
|
||||
img(src='/images/key-logo.svg' alt='Key Networks logo' height='25px' width='25px' style='display: inline')
|
||||
| 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
|
||||
29
src/views/v4AssignMode.pug
Normal file
29
src/views/v4AssignMode.pug
Normal file
@@ -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
|
||||
54
src/views/v6AssignMode.pug
Normal file
54
src/views/v6AssignMode.pug
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user