diff --git a/README.md b/README.md index f707a8a..baf04f1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See [github.com/key-networks/ztncui-containerized](https://github.com/key-networ Relative directory references below are relative to the cloned ztncui directory. ### Prerequisites -* ztncui is a [node.js](https://nodejs.org) [Express](https://expressjs.com) application that requires [node.js](https://nodejs.org) v8 or higher. +* ztncui is a [node.js](https://nodejs.org) [Express](https://expressjs.com) application that requires [node.js](https://nodejs.org) v14. * ztncui uses argon2 for password hashing. Argon2 needs the following: 1. g++ @@ -29,7 +29,7 @@ sudo npm install -g node-gyp * ztncui requires [ZeroTier One](https://www.zerotier.com/download.shtml) to be installed on the same machine. This will run as the network controller to establish ZeroTier networks. -* ztncui has been developed on a Linux platform and expects the ZT home directory to be in `/var/lib/zerotier-one`. It should be easy to modify for other platforms - please feed back if this is required. (Edit: it should be easier to run on any platform now using a `.env` file - see below). +* ztncui has been developed on a Linux platform and expects the ZT home directory to be in `/var/lib/zerotier-one`. ### Installing ##### 1. Clone the repository on a machine running ZeroTier One: @@ -79,13 +79,20 @@ chmod 600 .env The `.env` file should make it easier to run ztncui on a non-Linux platform. -##### 4. Copy the default passwd file +##### 4. Run in production mode +To run the server in production mode, add the following to the `.env` file (see 3B above): +``` +NODE_ENV=production +``` +Without this, the template engine always re-compiles the pug file when rendering (taking ~200 ms!) + +##### 5. Copy the default passwd file To prevent git from over-writing your password file every time you pull updates from the repository, the etc/passwd file has been added to .gitignore. So you need to copy the default file after the first time you do a git clone. All these things ideally need to be done with a package installer script: ```shell cp -v etc/default.passwd etc/passwd ``` -##### 5. Start the app manually: +##### 6. Start the app manually: ```shell npm start ``` @@ -94,7 +101,7 @@ This will run the app on TCP port 3000 by default. If port 3000 is already in u HTTP_PORT=3456 ``` -##### 6. Start the app automatically +##### 7. Start the app automatically To start the app automatically, something like [PM2](http://pm2.keymetrics.io) can be used. Install it with: ```shell sudo npm install -g pm2 @@ -117,27 +124,27 @@ Save the current PM2 process list so that ztncui will restart across reboots: pm2 save ``` -##### 7. Test access on http://localhost:3000 +##### 8. Test access on http://localhost:3000 If the machine has a GUI and GUI web browser, then use it to access the app, otherwise use a text web browser like Lynx or a CLI web browser like curl: ```shell curl http://localhost:3000 ``` You should see the front page of the app (or the raw HTML with curl). -##### 8. Remote access via HTTPS +##### 9. Remote access via HTTPS This app listens for HTTP requests on the looback interface (default port 3000). It can be reverse proxied by Nginx (which can proxy the HTTP as HTTPS), or accessed over an SSH tunnel as described below. -The app can be made to listen on all interfaces for HTTP requests by setting HTTP_ALL_INTERFACES in the .env file, e.g.: +The app can be made to listen on all interfaces for HTTP requests by setting HTTP_ALL_INTERFACES in the `.env` file, e.g.: ``` HTTP_ALL_INTERFACES=yes ``` Note that HTTP traffic is unencrypted, so this should only be done on a secure network, otherwise usernames and passwords will be exposed in plain text over the network. -The app can be made to listen on all interfaces for HTTPS requests by specifying HTTPS_PORT in the .env file, e.g.: +The app can be made to listen on all interfaces for HTTPS requests by specifying HTTPS_PORT in the `.env` file, e.g.: ``` HTTPS_PORT=3443 ``` -The app can be made to listen on a specific interface for HTTPS requests by specifying HTTPS_HOST (the host name or IP address of the interface) in the .env file, e.g.: +The app can be made to listen on a specific interface for HTTPS requests by specifying HTTPS_HOST (the host name or IP address of the interface) in the `.env` file, e.g.: ``` HTTPS_HOST=12.34.56.78 ``` @@ -156,7 +163,9 @@ If HTTPS_HOST is not specified, but HTTPS_PORT is specified, then the app will l ###### TLS Certificate For HTTPS you obviously need a TLS (SSL) certificate and private key pair. There are a few options: -1. Generate a self-signed certificate as follows: +1. By default, if there is no existing TLS certificate and private key pair, the RPM and DEB packages automatically generate a self-signed certificate / private key pair. + +2. If you are running directly from source, then generate a self-signed certificate as follows: ```shell cd etc/tls openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout privkey.pem -out fullchain.pem @@ -165,11 +174,11 @@ For HTTPS you obviously need a TLS (SSL) certificate and private key pair. Ther The advantage of this option is that it is quick and easy to generate the certificate / private key pair. The disadvantage is that your web browser will give you a warning that it cannot verify the certificate. You can override this warning and make a temporary exception. -2. Buy a certificate: +3. Buy a certificate: You will need to store the private key as `etc/tls/privkey.pem` and the full certificate chain as `etc/tls/fullchain.pem`. They need to be in PEM format. -3. Get a free certificate from Letsencrypt.org: +4. Get a free certificate from Letsencrypt.org: a. Install certbot by following the instructions at certbot.eff.org: @@ -201,7 +210,7 @@ For HTTPS you obviously need a TLS (SSL) certificate and private key pair. Ther Once you have a certificate at `etc/tls/fullchain.pem` and private key at `etc/tls/privkey.pem`, you should be able to access ztncui over HTTPS on the port specified by HTTPS_PORT. -##### 9. Remote access via SSH +##### 10. Remote access via SSH ###### SSH tunnel from Linux / Unix / macOS client An SSH tunnel can be established with: ```shell @@ -304,3 +313,6 @@ Problems with ztncui can be reported using the GitHub issue tracking system. Pl ## License The ztncui code is open source code, licensed under the GNU GPLv3, and is free to use on those terms. If you are interested in commercial licensing, please contact us via the contact form at [key-networks.com](https://key-networks.com) . + +## Thanks +@lideming for a rework and improvement of the network details page, adding DNS support, peer status/address/latency and other improvements. diff --git a/build/build.sh b/build/build.sh index 4dc24cb..03c5492 100755 --- a/build/build.sh +++ b/build/build.sh @@ -24,7 +24,7 @@ LICENSE='GPLv3' BINDINGGYP='node_modules/argon2/binding.gyp' -NODE_VER='v8' +NODE_VER='v14' if [ ! -f /usr/lib/gcc/x86_64-redhat-linux/10/libstdc++.a ]; then echo "You must install libstdc++-static" @@ -75,7 +75,7 @@ if [ $? -ne 0 ]; then fi popd -pkg -c ./package.json -t node8-linux-x64 bin/www -o $BUILD_DIR/ztncui +pkg -c ./package.json -t node14-linux-x64 bin/www -o $BUILD_DIR/ztncui popd diff --git a/src/app.js b/src/app.js index 6145cba..5bbde10 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ diff --git a/src/bin/www b/src/bin/www index 56fe4da..837bedd 100755 --- a/src/bin/www +++ b/src/bin/www @@ -10,11 +10,6 @@ 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. */ @@ -50,6 +45,11 @@ if (http_all_interfaces) { app.listen(http_port, 'localhost'); } +const options = !https_port ? {} : { + cert: fs.readFileSync('etc/tls/fullchain.pem'), + key: fs.readFileSync('etc/tls/privkey.pem') +}; + const server = https.createServer(options, app); if (https_port) { diff --git a/src/controllers/auth.js b/src/controllers/auth.js index a5099c2..8e11ecb 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -1,13 +1,13 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 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) { +const hash_check = async function(user, password) { let verified = false; try { var users = await usersController.get_users(); @@ -43,6 +43,6 @@ exports.restrict = function(req, res, next) { next(); } else { req.session.error = 'Access denied!'; - res.redirect('/login'); + res.redirect('/login?redirect=' + encodeURIComponent(req.originalUrl)); } } diff --git a/src/controllers/networkController.js b/src/controllers/networkController.js index 009802a..c13c299 100644 --- a/src/controllers/networkController.js +++ b/src/controllers/networkController.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ @@ -12,6 +12,42 @@ const util = require('util'); storage.initSync({dir: 'etc/storage'}); +async function get_network_with_members(nwid) { + const [network, peers, members] = await Promise.all([ + zt.network_detail(nwid), + zt.peers(), + zt.members(nwid) + .then(member_ids => + Promise.all( + Object.keys(member_ids) + .map(id => Promise.all([ + zt.member_detail(nwid, id), + storage.getItem(id) + ])) + ) + ).then(results => results.map(([member, name]) => { + member.name = name || ''; + return member; + })) + ]); + for (const member of members) { + member.peer = peers.find(x => x.address === member.address); + } + return {network, members}; +} + +async function get_network_member(nwid, memberid) { + const [network, member, peer, name] = await Promise.all([ + zt.network_detail(nwid), + zt.member_detail(nwid, memberid), + zt.peer(memberid), + storage.getItem(memberid) + ]); + member.name = name || ''; + member.peer = peer; + return {network, member}; +} + // ZT network controller home page exports.index = async function(req, res) { const navigate = @@ -20,11 +56,11 @@ exports.index = async function(req, res) { } try { - zt_address = await zt.get_zt_address(); - res.render('index', {title: 'ztncui', navigate: navigate, zt_address: zt_address}); + const zt_status = await zt.get_zt_status(); + res.render('index', {title: 'ztncui', navigate: navigate, zt_status}); } catch (err) { res.render('index', {title: 'ztncui', - navigate: navigate, error: 'ERROR resolving ZT address: ' + err}); + navigate: navigate, error: 'ERROR getting ZT status: ' + err}); } }; @@ -51,13 +87,15 @@ exports.network_detail = async function(req, res) { whence: '/controller/networks' } - console.log('NAVIGATE = ' + navigate.toString()); - console.log(util.inspect(navigate, false, null, true /* enable colors */)) - 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', navigate: navigate, network: network, members: members}); + const [ + {network, members}, + zt_address + ] = await Promise.all([ + get_network_with_members(req.params.nwid), + zt.get_zt_address() + ]); + res.render('network_detail', {title: 'Network ' + network.name, navigate: navigate, network: network, members: members, zt_address: zt_address}); } catch (err) { res.render('network_detail', {title: 'Detail for network', navigate: navigate, error: 'Error resolving detail for network ' + req.params.nwid + ': ' + err}); } @@ -95,7 +133,7 @@ exports.network_create_post = async function(req, res) { } else { try { const network = await zt.network_create(name); - res.redirect('/controller/networks'); + res.redirect('/controller/network/' + network.nwid); } catch (err) { res.render('network_detail', {title: 'Create Network - error', navigate: navigate, error: 'Error creating network ' + name.name}); } @@ -177,21 +215,15 @@ exports.name = async function(req, res) { let name = { name: req.body.name }; if (errors) { - try { - const network = await zt.network_detail(req.params.nwid); - res.render('name', {title: 'Rename network', navigate: navigate, network: network, name: name, errors: errors}); - } catch (err) { - res.render('name', {title: 'Rename network', navigate: navigate, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err}); - } + console.error("network name validation errors", errors); } else { try { const network = await zt.network_object(req.params.nwid, name); - res.redirect('/controller/networks'); } catch ( err) { - res.render('name', {title: 'Rename network', navigate: navigate, error: 'Error renaming network ' + req.params.nwid + ': ' + err}); + console.error("Error renaming network " + req.params.nwid, err); } } - + res.redirect('/controller/network/' + req.params.nwid); }; // ipAssignmentPools POST @@ -425,6 +457,35 @@ exports.v6AssignMode = async function (req, res) { } } +// dns POST +exports.dns = async function (req, res) { + const navigate = { + active: 'networks', + whence: '' + }; + + const dns = { + dns: { + domain: req.body.domain, + servers: req.body.servers + .split('\n') + .map(x => x.trim()) + .filter(ip => + new ipaddr.Address4(ip).isValid() || + new ipaddr.Address6(ip).isValid() + ) + } + }; + + try { + const network = await zt.network_object(req.params.nwid, dns); + navigate.whence = '/controller/network/' + network.nwid; + res.render('dns', {title: 'dns', navigate: navigate, network: network}); + } catch (err) { + res.render('dns', {title: 'dns', navigate: navigate, error: 'Error updating dns for network ' + req.params.nwid + ': ' + err}); + } +} + // Display detail page for specific member exports.member_detail = async function(req, res) { const navigate = @@ -434,15 +495,12 @@ exports.member_detail = async function(req, res) { } try { - const network = await zt.network_detail(req.params.nwid); - const member = await zt.member_detail(req.params.nwid, req.params.id); - let name = await storage.getItem(member.id); - if (!name) name = ''; - member.name = name; - navigate.whence = '/controller/network/' + network.nwid + '/members'; + const {network, member} = await get_network_member(req.params.nwid, req.params.id); + navigate.whence = '/controller/network/' + network.nwid + '#members'; res.render('member_detail', {title: 'Network member detail', navigate: navigate, network: network, member: member}); } catch (err) { - res.render(req.params.object, {title: req.params.object, navigate: navigate, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err}); + console.error(err); + res.render('error', {title: req.params.object, navigate: navigate, error: 'Error resolving detail for member ' + req.params.id + ' of network ' + req.params.nwid + ': ' + err}); } }; @@ -455,12 +513,8 @@ exports.member_object = async function(req, res) { } try { - const network = await zt.network_detail(req.params.nwid); - const member = await zt.member_detail(req.params.nwid, req.params.id); - let name = await storage.getItem(member.id); - if (!name) name = ''; - member.name = name; - navigate.whence = '/controller/network/' + network.nwid + '/members'; + const {network, member} = await get_network_member(req.params.nwid, req.params.id); + navigate.whence = '/controller/network/' + network.nwid + '#members'; res.render(req.params.object, {title: req.params.object, navigate: navigate, network: network, member: member}, function(err, html) { if (err) { if (err.message.indexOf('Failed to lookup view') !== -1 ) { @@ -480,7 +534,7 @@ exports.easy_get = async function(req, res) { const navigate = { active: 'networks', - whence: '/controller/networks' + whence: '/controller/network/' + req.params.nwid } try { @@ -561,7 +615,7 @@ exports.easy_post = async function(req, res) { } } -// Easy members auth GET or POST +// Easy members auth POST exports.members = async function(req, res) { const navigate = { @@ -630,26 +684,6 @@ exports.members = async function(req, res) { } } } - - 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', navigate: navigate, - network: network, members: members, errors: errors}); - } catch (err) { - res.render('members', {title: 'Members of this network', navigate: navigate, - error: 'Error resolving detail for network ' + req.params.nwid - + ': ' + err}); - } } // Member delete GET or POST @@ -673,10 +707,9 @@ exports.member_delete = async function(req, res) { member = await zt.member_detail(req.params.nwid, req.params.id); name = await storage.getItem(member.id); } - if (!name) name = ''; - member.name = name; + member.name = name || ''; - navigate.whence = '/controller/network/' + network.nwid + '/members'; + navigate.whence = '/controller/network/' + network.nwid; res.render('member_delete', {title: 'Delete member from ' + network.name, navigate: navigate, network: network, member: member}); } catch (err) { @@ -697,10 +730,8 @@ exports.delete_ip = async function(req, res) { try { const network = await zt.network_detail(req.params.nwid); let member = await zt.member_detail(req.params.nwid, req.params.id); - navigate.whence = '/controller/network/' + network.nwid + '/members'; - let name = await storage.getItem(member.id); - if (!name) name = ''; - member.name = name; + navigate.whence = '/controller/network/' + network.nwid; + member.name = await storage.getItem(member.id) | ''; if (req.params.index) { member = await zt.ipAssignmentDelete(network.nwid, member.id, req.params.index); @@ -758,15 +789,13 @@ exports.assign_ip = async function(req, res) { try { let member = await zt.member_detail(req.params.nwid, req.params.id); - navigate.whence = '/controller/network/' + network.nwid + '/members'; + navigate.whence = '/controller/network/' + network.nwid; if (!errors) { member = await zt.ipAssignmentAdd(network.nwid, member.id, ipAssignment); } - let name = await storage.getItem(member.id); - if (!name) name = ''; - member.name = name; + member.name = await storage.getItem(member.id) | ''; res.render('ipAssignments', {title: 'ipAssignments', navigate: navigate, ipAssignment: ipAssignment, network: network, member: member, diff --git a/src/controllers/token.js b/src/controllers/token.js index 0bbdbe1..4a5f8da 100644 --- a/src/controllers/token.js +++ b/src/controllers/token.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ diff --git a/src/controllers/usersController.js b/src/controllers/usersController.js index 780dc48..4ac89fb 100644 --- a/src/controllers/usersController.js +++ b/src/controllers/usersController.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ @@ -17,7 +17,7 @@ const chmod = util.promisify(fs.chmod); let _users = null; -get_users = async function() { +const get_users = async function() { if (_users) { return _users; } else { @@ -31,7 +31,7 @@ get_users = async function() { } exports.get_users = get_users; -update_users = async function(users) { +const update_users = async function(users) { try { await writeFile(passwd_file, JSON.stringify(users), 'utf8'); await chmod(passwd_file, 0600); diff --git a/src/controllers/zt.js b/src/controllers/zt.js index cf770ff..d01f415 100644 --- a/src/controllers/zt.js +++ b/src/controllers/zt.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ @@ -8,9 +8,9 @@ const got = require('got'); const ipaddr = require('ip-address'); const token = require('./token'); -ZT_ADDR = process.env.ZT_ADDR || 'localhost:9993'; +const ZT_ADDR = process.env.ZT_ADDR || 'localhost:9993'; -init_options = async function() { +const init_options = async function() { let tok = null; try { @@ -29,16 +29,21 @@ init_options = async function() { return options; } -get_zt_address = async function() { +const get_zt_status = async function() { const options = await init_options(); try { const response = await got(ZT_ADDR + '/status', options); - return response.body.address; + return response.body; } catch(err) { throw(err); } } +exports.get_zt_status = get_zt_status; + +const get_zt_address = async function() { + return (await get_zt_status()).address; +} exports.get_zt_address = get_zt_address; exports.network_list = async function() { @@ -68,7 +73,7 @@ exports.network_list = async function() { return networks; } -network_detail = async function(nwid) { +const network_detail = async function(nwid) { const options = await init_options(); try { @@ -241,7 +246,7 @@ exports.members = async function(nwid) { } } -member_detail = async function(nwid, id) { +const member_detail = async function(nwid, id) { const options = await init_options(); try { @@ -303,3 +308,22 @@ exports.network_easy_setup = async function(nwid, throw(err); } } + +exports.peers = async function() { + const options = await init_options(); + const response = await got(ZT_ADDR + '/peer', options); + return response.body; +} + +exports.peer = async function(id) { + const options = await init_options(); + try { + const response = await got(ZT_ADDR + '/peer/' + id, options); + return response.body; + } catch (error) { + if (error instanceof got.HTTPError && error.statusCode == 404) { + return null; + } + throw error; + } +} diff --git a/src/package.json b/src/package.json index bf03fd0..4af3b61 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "ztncui", - "version": "0.6.6", + "version": "0.7.0", "private": true, "scripts": { "start": "node ./bin/www", @@ -19,7 +19,7 @@ "got": "^7.1.0", "helmet": "^3.23.0", "ip-address": "^5.8.9", - "jquery": "^3.5.1", + "jquery": "~3.4.1", "morgan": "~1.9.1", "node-persist": "^2.1.0", "pug": "^2.0.4", @@ -32,7 +32,8 @@ "assets": [ "views/*", "public/**/*", - "etc/**/*" + "etc/**/*", + "node_modules/jquery/dist/jquery.min.js" ] } } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index a4c5c7b..8932fcc 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -1,5 +1,4 @@ html, body { - height: 100%; font: 16px "Lucida Grande", Helvetica, Arial, sans-serif; } diff --git a/src/routes/index.js b/src/routes/index.js index b178ab1..cc255fb 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ @@ -10,8 +10,17 @@ const authenticate = auth.authenticate; const restrict = auth.restrict; const router = express.Router(); +/** Redirect logged user to controler page */ +function guest_only(req, res, next) { + if (req.session.user) { + res.redirect('/controller'); + } else { + next(); + } +} + /* GET home page. */ -router.get('/', function(req, res, next) { +router.get('/', guest_only, function(req, res, next) { res.render('front_door', {title: 'ztncui'}); }); @@ -21,7 +30,7 @@ router.get('/logout', function(req, res) { }); }); -router.get('/login', function(req, res) { +router.get('/login', guest_only, function(req, res) { let message = null; if (req.session.error) { if (req.session.error !== 'Access denied!') { @@ -40,7 +49,7 @@ router.post('/login', async function(req, res) { req.session.user = user; req.session.success = 'Authenticated as ' + user.name; if (user.pass_set) { - res.redirect('/controller'); + res.redirect(req.query.redirect || '/controller'); } else { res.redirect('/users/' + user.name + '/password'); } diff --git a/src/routes/users.js b/src/routes/users.js index 11abd03..7fb18fe 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ diff --git a/src/routes/zt_controller.js b/src/routes/zt_controller.js index 44603da..99ae096 100644 --- a/src/routes/zt_controller.js +++ b/src/routes/zt_controller.js @@ -1,6 +1,6 @@ /* ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. */ @@ -43,6 +43,9 @@ router.get('/network/:nwid/routes/:target_ip/:target_prefix/delete', restrict, n // POST request for routes router.post('/network/:nwid/routes', restrict, networkController.routes); +// POST request for dns +router.post('/network/:nwid/dns', restrict, networkController.dns); + // POST request for private router.post('/network/:nwid/private', restrict, networkController.private); diff --git a/src/views/controller_layout.pug b/src/views/controller_layout.pug index 0bd051a..b0f72ee 100644 --- a/src/views/controller_layout.pug +++ b/src/views/controller_layout.pug @@ -1,38 +1,18 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 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=(navigate.active === 'controller_home'? 'active' : '')) - a(href='/controller') Home - li(class=(navigate.active === 'users'? 'active' : '')) - a(href='/users') Users - li(class=(navigate.active === 'networks'? 'active' : '')) - a(href='/controller/networks') Networks - li(class=(navigate.active === '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 +block nav_items + +nav_item('controller_home', 'Home', '/controller') + +nav_item('users', 'Users', '/users') + +nav_item('networks', 'Networks', '/controller/networks') + +nav_item('add_network', 'Add network', '/controller/network/create') - .container(style='margin-top:50px') +block body_content + .container(style='margin: 50px auto 20px') .row .col-sm-12 block content diff --git a/src/views/dns.pug b/src/views/dns.pug new file mode 100644 index 0000000..87b08a4 --- /dev/null +++ b/src/views/dns.pug @@ -0,0 +1,57 @@ +//- + ztncui - ZeroTier network controller UI + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) + Licensed under GPLv3 - see LICENSE for details. + +extends network_layout + +block net_content + - const dns = network.dns || {}; + if (!dns.domain && !(dns.servers && dns.servers.length > 0)) + .row + .col-sm-12 + b No DNS configuration on this network. + else + .row + .col-sm-2 + b Domain: + .col-sm-10 + p= dns.domain + .row + .col-sm-2 + b Servers: + .col-sm-10 + .row + each server in dns.servers + .col-sm-12= server + + .row + .col-sm-12 + h3 Change DNS configuration: + + form(method='POST' action='') + .form-group.row + .col-sm-12 + label(for='domain') Domain: + .col-sm-12 + input#domain.form-control(type='text' name='domain' value=dns.domain) + + .form-group.row + .col-sm-12 + label(for='servers') Servers: + .col-sm-12 + textarea#servers.form-control(type='text' name='servers' placeholder='(one IP address per line)') + = !dns.servers ? '' : dns.servers.join('\n') + + .form-group.row + .col-sm-12 + button.btn.btn-primary(type='submit') Submit + = ' ' + a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel + + if errors + .row + .col-sm-12 + ul + for err in errors + li!= err.msg diff --git a/src/views/front_door.pug b/src/views/front_door.pug index e0f3a5b..eeebc37 100644 --- a/src/views/front_door.pug +++ b/src/views/front_door.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends login_layout diff --git a/src/views/head_layout.pug b/src/views/head_layout.pug index 635ed4c..c2be79c 100644 --- a/src/views/head_layout.pug +++ b/src/views/head_layout.pug @@ -1,8 +1,18 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. +mixin nav_item(name, displayName, href) + li(class=((navigate && navigate.active === name) ? 'active' : '')) + a(href=href) #{displayName} + +mixin json_value(value) + - if ((!!value ) && (value.constructor == Object || value.constructor == Array)) + code(style='white-space: pre;')= JSON.stringify(value, null, 2) + - else + code= value + doctype html html(lang='en') head @@ -11,7 +21,26 @@ html(lang='en') 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='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js') + script(src='/jqjs/jquery.min.js') script(src='/bsjs/bootstrap.min.js') body + 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 + block nav_items + ul.nav.navbar-nav.navbar-right + li + block nav_login + a(href='/logout') + span.glyphicon.glyphicon-log-out + | Logout block body_content diff --git a/src/views/index.pug b/src/views/index.pug index e87c6de..6a77369 100644 --- a/src/views/index.pug +++ b/src/views/index.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends controller_layout @@ -10,14 +10,13 @@ block content h2 a(href='https://zerotier.com' target='_blank') ZeroTier - | network controller UI - - h3 Network controller details + | network controller UI by + a(href='https://key-networks.com' target='_blank') Key Networks if error b #{error} else - h4 This network controller has a ZeroTier address of #{zt_address} - + h4 This network controller has a ZeroTier address of #{zt_status.address} + h4 ZeroTier version #{zt_status.version} h4 a(href='/controller/networks') List all networks on this network controller diff --git a/src/views/ipAssignmentPools.pug b/src/views/ipAssignmentPools.pug index da45c21..7af58bf 100644 --- a/src/views/ipAssignmentPools.pug +++ b/src/views/ipAssignmentPools.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout diff --git a/src/views/ipAssignments.pug b/src/views/ipAssignments.pug index 519ca02..26c3ee1 100644 --- a/src/views/ipAssignments.pug +++ b/src/views/ipAssignments.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout diff --git a/src/views/login.pug b/src/views/login.pug index 378a885..9a131ab 100644 --- a/src/views/login.pug +++ b/src/views/login.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends login_layout diff --git a/src/views/login_layout.pug b/src/views/login_layout.pug index 9e4782c..a9fcb5c 100644 --- a/src/views/login_layout.pug +++ b/src/views/login_layout.pug @@ -1,28 +1,16 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 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 +block nav_login + a(href='/login') + span.glyphicon.glyphicon-log-in + | Login +block body_content .container(style='margin-top:50px') .row .col-sm-12 diff --git a/src/views/member_delete.pug b/src/views/member_delete.pug index ac187d1..e6001ea 100644 --- a/src/views/member_delete.pug +++ b/src/views/member_delete.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout @@ -9,7 +9,7 @@ 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 + a.btn.btn-default(href=('/controller/network/' + network.nwid + '#members') name='networks' role='button') Members else .alert.alert-info @@ -21,7 +21,7 @@ block net_content 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', + a.btn.btn-default(href='/controller/network/' + network.nwid + '#members', name='cancel', role='button') Cancel if errors diff --git a/src/views/member_detail.pug b/src/views/member_detail.pug index 4acfba2..8d1369a 100644 --- a/src/views/member_detail.pug +++ b/src/views/member_detail.pug @@ -1,32 +1,20 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout block net_content - h4 for member #{member.name} (#{member.address}) + h4 + | for member #{member.name} (#{member.address}) in network each value, key in member .row .col-sm-2 - a(href= member.address + '/' + key) #{key}: + a(href=('/controller/network/' + member.nwid + '/member/' + 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} + +json_value(value) - a.btn.btn-default(href='../members' name='networks' role='button') Members + a.btn.btn-default(href=('/controller/network/' + member.nwid + "#members") name='networks' role='button' style='margin-top: 10px;') + | Members diff --git a/src/views/members.pug b/src/views/members.pug deleted file mode 100644 index 0a56c20..0000000 --- a/src/views/members.pug +++ /dev/null @@ -1,76 +0,0 @@ -//- - ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) - Licensed under GPLv3 - see LICENSE for details. - -extends network_layout - -block net_content - script. - $(function() { - $('.authCheck').on('click', function() { - $.post('', {'id': this.value, 'auth': this.checked}); - }); - }); - - $(function() { - $('.bridgeCheck').on('click', function() { - $.post('', {'id': this.value, 'activeBridge': 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='10%') - | Active bridge - td(width='47%') - | 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.authCheck(type='checkbox' value=member.id checked=(member.authorized? true : false)) - td - input.bridgeCheck(type='checkbox' value=member.id checked=(member.activeBridge? true : false)) - td - each ipAssignment in member.ipAssignments - a(href='/controller/network/' + network.nwid + '/member/' + member.id + '/ipAssignments') - each digit in ipAssignment - = digit - = ' ' - else - a(href='/controller/network/' + network.nwid + '/member/' + member.id + '/ipAssignments') - | IP assignment - - else - .alert.alert-info - strong There are no members on this network - invite users to join #{network.nwid} - - a.btn.btn-default(href='' name='refresh' role='button') Refresh - - if errors - ul - for err in errors - li!= err.msg diff --git a/src/views/name.pug b/src/views/name.pug deleted file mode 100644 index 8ce23e4..0000000 --- a/src/views/name.pug +++ /dev/null @@ -1,28 +0,0 @@ -//- - ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) - Licensed under GPLv3 - see LICENSE for details. - -extends network_layout - -block net_content - form(method='POST' action='') - - .form-group.row - .col-sm-2 - label(for='name') Network name: - .col-sm-10 - input#name.form-control(type='text' name='name' placeholder='New network name' value=(undefined===network.name? '' : network.name)) - - .form-group.row - .col-sm-12 - button.btn.btn-primary(type='submit') Submit - = ' ' - a.btn.btn-default(href='/controller/networks' name='cancel' role='button') Cancel - - if errors - .row - .col-sm-12 - ul - for err in errors - li!= err.msg diff --git a/src/views/network_create.pug b/src/views/network_create.pug index c5fb323..2246548 100644 --- a/src/views/network_create.pug +++ b/src/views/network_create.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends controller_layout diff --git a/src/views/network_delete.pug b/src/views/network_delete.pug index 5d8766d..00bba12 100644 --- a/src/views/network_delete.pug +++ b/src/views/network_delete.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout diff --git a/src/views/network_detail.pug b/src/views/network_detail.pug index 7a4bc5b..2517a32 100644 --- a/src/views/network_detail.pug +++ b/src/views/network_detail.pug @@ -1,44 +1,159 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout +//- Don't display that title +block title + +//- Replace the network title with the editable one +block network_title + h2 + | Network + a#change-name(href='#') + span#name= network.name + i.glyphicon.glyphicon-pencil(style='font-size: 20px;') + input#name-input.form-control(type='text' style='width: 200px; display: none;') + | (#{network.nwid}): + script. + $(function() { + var nwurl = '/controller/network/#{network.nwid}'; + var name = !{JSON.stringify(network.name)}; + + function toggleNameEditor(show) { + $('#change-name').css('display', !show ? '' : 'none'); + $('#name-input').css('display', show ? 'inline-block' : 'none'); + } + + function submit() { + var newName = $('#name-input').val(); + if (newName != name) { + name = newName; + $.post(nwurl + '/name', {'name': name}) + .done(function () { + $('#name').text(newName); + }); + } + toggleNameEditor(false); + } + + $('#change-name').on('click', function() { + toggleNameEditor(true); + $('#name-input').val(name); + $('#name-input').focus(); + }); + $('#name-input').on('focusout', submit); + $('#name-input').keypress(function (e) { + if (e.which == 13) submit(); + }); + }); + block net_content - if error - b #{error} - else - - 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} + - const nwurl = '/controller/network/' + network.nwid; - 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 { - - if (!!elem) - each v2, k2 in elem - p #{k2}: #{v2}, - p }, - p ] - - else - | #{value} + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/private') role='button') + = network.private ? "Private" : "Public" + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/easy') role='button') Easy setup + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/routes') role='button') Routes + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/ipAssignmentPools') role='button') Assignment Pools + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/v4AssignMode') role='button') IPv4 Assign Mode + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/v6AssignMode') role='button') IPv6 Assign Mode + a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/dns') role='button') DNS - a.btn.btn-default(href='/controller/networks' name='networks' role='button') Networks + if (members !== undefined) + script. + $(function() { + const url = "#{nwurl}/members"; + $('.authCheck').on('click', function() { + $.post(url, {'id': this.value, 'auth': this.checked}); + }); + $('.bridgeCheck').on('click', function() { + $.post(url, {'id': this.value, 'activeBridge': this.checked}); + }); + $('.text').on('change', function() { + $.post(url, {'id': this.name, 'name': this.value}); + }); + }); + h3#members Members (#{members.length}) + 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='10%') + | Active bridge + td(width='17%') + | IP assignment + td(width='17%') + | Peer status + td(width='13%') + | Peer address / latency + each member in members + - const peer = member.peer; + tr + - const url = nwurl + '/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.authCheck(type='checkbox' value=member.id checked=(member.authorized? true : false)) + td + input.bridgeCheck(type='checkbox' value=member.id checked=(member.activeBridge? true : false)) + td + each ipAssignment in member.ipAssignments + a(href=nwurl + '/member/' + member.id + '/ipAssignments') + each digit in ipAssignment + = digit + = ' ' + else + a(href=nwurl + '/member/' + member.id + '/ipAssignments') + | IP assignment + td + if (peer && peer.latency != -1 && peer.versionMajor != -1) + if (peer.latency != -1) + span(style='color: green;') + | ONLINE (v#{peer.version}) + else + span(style='color: orange;') + | RELAY (v#{peer.version}) + else if (member.id == zt_address) + span(style='color: green;') CONTROLLER + else + span(style='color: red;') OFFLINE + td + if (peer) + each path in peer.paths + - const [ip, port] = path.address.split('/'); + = ip + span(style='color: gray;') /#{port} + = ' ' + if (peer.latency != -1) + br + | (#{peer.latency} ms) + else + .alert.alert-info + strong There are no members on this network - invite users to join #{network.nwid} + + a.btn.btn-default(href='' name='refresh' role='button') Refresh + + h3#detail Detail for network + each value, key in network + .row(style='margin: 5px 0;') + .col-sm-2 + a(href= network.nwid + '/' + key) #{key}: + .col-sm-10 + +json_value(value) + + a.btn.btn-default(href='/controller/networks' name='networks' role='button' style='margin-top: 10px;') Networks diff --git a/src/views/network_easy.pug b/src/views/network_easy.pug index ebe0151..209428e 100644 --- a/src/views/network_easy.pug +++ b/src/views/network_easy.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout @@ -80,7 +80,7 @@ block net_content .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 + a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel if errors ul diff --git a/src/views/network_layout.pug b/src/views/network_layout.pug index 60f32a5..2004c85 100644 --- a/src/views/network_layout.pug +++ b/src/views/network_layout.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends controller_layout @@ -11,10 +11,14 @@ block content else .row .col-sm-10 - h2 - a(href='/controller/network/' + network.nwid) #{network.name} - | (#{network.nwid}): - h3= title + block network_title + h2 + | Network + a(href='/controller/network/' + network.nwid) #{network.name} + | (#{network.nwid}): + block title + if title + h3= title .col-sm-2 h2.right diff --git a/src/views/networks.pug b/src/views/networks.pug index e4cc173..6a9e5d4 100644 --- a/src/views/networks.pug +++ b/src/views/networks.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends controller_layout @@ -11,36 +11,36 @@ block content 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%') + th(width='20%') | Network ID - th(width='7%') + th(width='8%') = '' - th(width='10%') + th(width='12%') = '' - th(width='50%') + th(width='37%') = '' each network in networks + - const nwurl = '/controller/network/' + network.nwid; tr td - a(href='/controller/network/' + network.nwid + '/delete') + a(href=nwurl + '/delete') i.glyphicon.glyphicon-trash td - a(href='/controller/network/' + network.nwid + '/name') #{network.name} + a(href=nwurl) #{network.name} td = network.nwid td - a(href='/controller/network/' + network.nwid) detail + a(href=nwurl) detail td - a(href='/controller/network/' + network.nwid + '/easy') easy setup + a(href=nwurl + '/easy') easy setup td - a(href='/controller/network/' + network.nwid + '/members') members + a(href=nwurl + "#members") members else .alert.alert-info diff --git a/src/views/not_implemented.pug b/src/views/not_implemented.pug index e6997c7..6023c63 100644 --- a/src/views/not_implemented.pug +++ b/src/views/not_implemented.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout diff --git a/src/views/password.pug b/src/views/password.pug index 1eb850c..09b9369 100644 --- a/src/views/password.pug +++ b/src/views/password.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends users_layout diff --git a/src/views/private.pug b/src/views/private.pug index 0b87bfb..e490209 100644 --- a/src/views/private.pug +++ b/src/views/private.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout diff --git a/src/views/routes.pug b/src/views/routes.pug index 686909f..d80c5e4 100644 --- a/src/views/routes.pug +++ b/src/views/routes.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout @@ -27,22 +27,22 @@ block net_content form(method='POST' action='/controller/network/' + network.nwid + '/routes') .form-group.row - .col-sm-2 + .col-sm-12 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 + .col-sm-12 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 + .col-sm-12 button.btn.btn-primary(type='submit') Submit = ' ' - a.btn.btn-default(href='/controller/networks' name='cancel' role='button') Cancel + a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel if errors .row diff --git a/src/views/user_delete.pug b/src/views/user_delete.pug index 14c1116..d9d74b6 100644 --- a/src/views/user_delete.pug +++ b/src/views/user_delete.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends users_layout diff --git a/src/views/users.pug b/src/views/users.pug index 5a6d247..5cef684 100644 --- a/src/views/users.pug +++ b/src/views/users.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends users_layout diff --git a/src/views/users_layout.pug b/src/views/users_layout.pug index e2508ba..bf5e73d 100644 --- a/src/views/users_layout.pug +++ b/src/views/users_layout.pug @@ -1,35 +1,17 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 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=(navigate.active === 'home'? 'active' : '')) - a(href='/controller') Home - li(class=(navigate.active === 'users'? 'active' : '')) - a(href='/users') Users - li(class=(navigate.active === '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 +block nav_items + +nav_item('controller_home', 'Home', '/controller') + +nav_item('users', 'Users', '/users') + +nav_item('networks', 'Networks', '/controller/networks') + +nav_item('create_user', 'Create user', '/users/create') +block body_content .container(style='margin-top:50px') .row .col-sm-12 diff --git a/src/views/v4AssignMode.pug b/src/views/v4AssignMode.pug index 41d2ee6..2aabf03 100644 --- a/src/views/v4AssignMode.pug +++ b/src/views/v4AssignMode.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout diff --git a/src/views/v6AssignMode.pug b/src/views/v6AssignMode.pug index 490e782..4833ac6 100644 --- a/src/views/v6AssignMode.pug +++ b/src/views/v6AssignMode.pug @@ -1,6 +1,6 @@ //- ztncui - ZeroTier network controller UI - Copyright (C) 2017-2018 Key Networks (https://key-networks.com) + Copyright (C) 2017-2021 Key Networks (https://key-networks.com) Licensed under GPLv3 - see LICENSE for details. extends network_layout