Some changes, including DNS support (#57)

* remove CDN

* Add a link to "members" in the network detail page

* Show object values as JSON

* remove TLS options

* Minor style improvements

* Show object values as JSON (member_detail)

* Add missing 'const'

* Navbar height fix

* Merge jQuery ready functions

* Change brand

* Merge network pages (name, members, detail) into single page and...

* Show ZT version on controller index page
* Show count of members
* Use <code> tag to display JSON data
* Fix error in some "error" pages caused by missing "navigate" when
  rendering nav items, use pug mixin to render nav items.
* Adjust column widths of network list

* Refactor: move duplicated nav code to `head_layout`

* Remove some debug logging code

* Get network members detail parallelly

* Add missing frontend script for members

* Revert "Change brand"

* Remove "members" and "name" pages which are merged into "detail"

* Add DNS support

* Trivial changes (table width etc.)

* Don't try to read TLS cert files when not using HTTPS

* Validate DNS IP

* Downgrade jquery to 3.4.1 to fix nav bar collapse

* Revert "Navbar height fix"

This reverts commit 8edaa9aa81, which
break the nav item height on mobile.

* Add missing margin for some buttons

* Display current DNS configuration above inputs

* Change network rename UI/UX

* Includes 'jquery.min.js' in pkg

* Improve JSON value rendering

* Get peer status of network members

* Display members with peer status

* Show controller itself as "CONTROLLER"

* Display peer address

* Improve login redirection

* pr57: Doc updates; version bump

* pr57: Year update

Co-authored-by: Key Networks <34238649+key-networks@users.noreply.github.com>
This commit is contained in:
lideming 2021-03-01 15:10:10 +08:00 committed by GitHub
parent ce4b0e6d79
commit 1295dd1d07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 503 additions and 388 deletions

View File

@ -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.

View File

@ -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

View File

@ -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.
*/

View File

@ -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) {

View File

@ -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));
}
}

View File

@ -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,

View File

@ -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.
*/

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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"
]
}
}

View File

@ -1,5 +1,4 @@
html, body {
height: 100%;
font: 16px "Lucida Grande", Helvetica, Arial, sans-serif;
}

View File

@ -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');
}

View File

@ -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.
*/

View File

@ -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);

View File

@ -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

57
src/views/dns.pug Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <b>#{zt_address}</b>
h4 This network controller has a ZeroTier address of <b>#{zt_status.address}</b>
h4 ZeroTier version <b>#{zt_status.version}</b>
h4
a(href='/controller/networks') List all networks on this network controller

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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