diff --git a/.gitignore b/.gitignore
index b5c8ca2..11aa4da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
*.swp
Release/
Staging/
+
+node_modules/
diff --git a/src/app.js b/src/app.js
index 5bbde10..fecca9e 100644
--- a/src/app.js
+++ b/src/app.js
@@ -22,6 +22,18 @@ const zt_controller = require('./routes/zt_controller');
const app = express();
+// Base path support for reverse proxy subpaths (e.g., Nginx location)
+// Example: BASE_PATH=/ztncui -> app is served under http(s)://host/ztncui
+const rawBasePath = process.env.BASE_PATH || '';
+const basePath = (function normalizeBasePath(p) {
+ if (!p) return '';
+ if (!p.startsWith('/')) p = '/' + p;
+ // trim trailing slash except root
+ if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);
+ return p;
+})(rawBasePath);
+app.locals.basePath = basePath;
+
const session_secret = Math.random().toString(36).substring(2,12);
// view engine setup
@@ -29,7 +41,8 @@ app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(helmet());
-app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+// Mount favicon and static assets under base path
+app.use(basePath, favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
@@ -40,15 +53,23 @@ app.use(session({
}));
app.use(expressValidator());
app.use(cookieParser());
-app.use(express.static(path.join(__dirname, 'public')));
-app.use('/fonts', express.static(path.join(__dirname, 'node_modules/bootstrap/fonts')));
-app.use('/bscss', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css')));
-app.use('/jqjs', express.static(path.join(__dirname, 'node_modules/jquery/dist')));
-app.use('/bsjs', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js')));
+// Inject basePath into templates
+app.use(function(req, res, next) {
+ res.locals.basePath = basePath;
+ next();
+});
-app.use('/', index);
-app.use('/users', users);
-app.use('/controller', zt_controller);
+// Static mounts under base path
+app.use(basePath, express.static(path.join(__dirname, 'public')));
+app.use(basePath + '/fonts', express.static(path.join(__dirname, 'node_modules/bootstrap/fonts')));
+app.use(basePath + '/bscss', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css')));
+app.use(basePath + '/jqjs', express.static(path.join(__dirname, 'node_modules/jquery/dist')));
+app.use(basePath + '/bsjs', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js')));
+
+// Route mounts under base path
+app.use(basePath + '/', index);
+app.use(basePath + '/users', users);
+app.use(basePath + '/controller', zt_controller);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
diff --git a/src/controllers/auth.js b/src/controllers/auth.js
index 8e11ecb..5453869 100644
--- a/src/controllers/auth.js
+++ b/src/controllers/auth.js
@@ -42,7 +42,8 @@ exports.restrict = function(req, res, next) {
if (req.session.user) {
next();
} else {
+ const basePath = req.app && req.app.locals ? (req.app.locals.basePath || '') : '';
req.session.error = 'Access denied!';
- res.redirect('/login?redirect=' + encodeURIComponent(req.originalUrl));
+ res.redirect(basePath + '/login?redirect=' + encodeURIComponent(req.originalUrl));
}
}
diff --git a/src/controllers/networkController.js b/src/controllers/networkController.js
index 50c10ee..3d5b947 100644
--- a/src/controllers/networkController.js
+++ b/src/controllers/networkController.js
@@ -12,6 +12,11 @@ const util = require('util');
storage.initSync({dir: 'etc/storage'});
+// Unified helper to get basePath consistently across the controller
+function getBasePath(req) {
+ return (req.app && req.app.locals ? (req.app.locals.basePath || '') : '');
+}
+
async function get_network_with_members(nwid) {
const [network, peers, members] = await Promise.all([
zt.network_detail(nwid),
@@ -92,10 +97,11 @@ exports.network_list = async function(req, res) {
// Display detail page for specific network
exports.network_detail = async function(req, res) {
+ const basePath = getBasePath(req);
const navigate =
{
active: 'networks',
- whence: '/controller/networks'
+ whence: basePath + '/controller/networks'
}
try {
@@ -144,7 +150,8 @@ exports.network_create_post = async function(req, res) {
} else {
try {
const network = await zt.network_create(name);
- res.redirect('/controller/network/' + network.nwid);
+ const basePath = getBasePath(req);
+ res.redirect(basePath + '/controller/network/' + network.nwid);
} catch (err) {
res.render('network_detail', {title: 'Create Network - error', navigate: navigate, error: 'Error creating network ' + name.name});
}
@@ -153,10 +160,11 @@ exports.network_create_post = async function(req, res) {
// Display Network delete form on GET
exports.network_delete_get = async function(req, res) {
+ const basePath = getBasePath(req);
const navigate =
{
active: 'networks',
- whence: '/controller/networks'
+ whence: basePath + '/controller/networks'
}
try {
@@ -170,10 +178,11 @@ exports.network_delete_get = async function(req, res) {
// Handle Network delete on POST
exports.network_delete_post = async function(req, res) {
+ const basePath = getBasePath(req);
const navigate =
{
active: 'networks',
- whence: '/controller/networks'
+ whence: basePath + '/controller/networks'
}
try {
@@ -194,7 +203,8 @@ exports.network_object = async function(req, res) {
try {
const network = await zt.network_detail(req.params.nwid);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render(req.params.object, {title: req.params.object, navigate: navigate, network: network}, function(err, html) {
if (err) {
if (err.message.indexOf('Failed to lookup view') !== -1 ) {
@@ -211,10 +221,11 @@ exports.network_object = async function(req, res) {
// Handle Network rename form on POST
exports.name = async function(req, res) {
+ const basePath = getBasePath(req);
const navigate =
{
active: 'networks',
- whence: '/controller/networks'
+ whence: basePath + '/controller/networks'
}
req.checkBody('name', 'Network name required').notEmpty();
@@ -234,7 +245,7 @@ exports.name = async function(req, res) {
console.error("Error renaming network " + req.params.nwid, err);
}
}
- res.redirect('/controller/network/' + req.params.nwid);
+ res.redirect(basePath + '/controller/network/' + req.params.nwid);
};
// ipAssignmentPools POST
@@ -265,7 +276,8 @@ exports.ipAssignmentPools = async function(req, res) {
if (errors) {
try {
const network = await zt.network_detail(req.params.nwid);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, ipAssignmentPool: ipAssignmentPool, network: network, errors: errors});
} catch (err) {
res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, error: 'Error resolving network detail for network ' + req.params.nwid + ': ' + err});
@@ -273,7 +285,8 @@ exports.ipAssignmentPools = async function(req, res) {
} else {
try {
const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'add');
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, ipAssignmentPool: ipAssignmentPool, network: network});
} catch (err) {
res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, error: 'Error applying IP Assignment Pools for network ' + req.params.nwid + ': ' + err});
@@ -332,7 +345,8 @@ exports.routes = async function (req, res) {
if (errors) {
try {
const network = await zt.network_detail(req.params.nwid);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('routes', {title: 'routes', navigate: navigate, route: route, network: network, errors: errors});
} catch (err) {
res.render('routes', {title: 'routes', navigate: navigate, error: 'Error resolving network detail'});
@@ -340,7 +354,8 @@ exports.routes = async function (req, res) {
} else {
try {
const network = await zt.routes(req.params.nwid, route, 'add');
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('routes', {title: 'routes', navigate: navigate, route: route, network: network});
} catch (err) {
res.render('routes', {title: 'routes', navigate: navigate, error: 'Error adding route for network ' + req.params.nwid + ': ' + err});
@@ -366,7 +381,8 @@ exports.route_delete = async function (req, res) {
try {
const network = await zt.routes(req.params.nwid, route, 'delete');
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('routes', {title: 'routes', navigate: navigate, route: route, network: network});
} catch (err) {
res.render('routes', {title: 'routes', navigate: navigate, error: 'Error deleting route for network ' + req.params.nwid + ': ' + err});
@@ -390,7 +406,8 @@ exports.ipAssignmentPool_delete = async function (req, res) {
try {
const network = await zt.ipAssignmentPools(req.params.nwid, ipAssignmentPool, 'delete');
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, ipAssignmentPool: ipAssignmentPool, network: network});
} catch (err) {
res.render('ipAssignmentPools', {title: 'ipAssignmentPools', navigate: navigate, error: 'Error deleting IP Assignment Pool for network ' + req.params.nwid + ': ' + err});
@@ -412,7 +429,8 @@ exports.private = async function (req, res) {
try {
const network = await zt.network_object(req.params.nwid, private);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('private', {title: 'private', navigate: navigate, network: network});
} catch (err) {
res.render('private', {title: 'private', navigate: navigate, error: 'Error applying private for network ' + req.params.nwid + ': ' + err});
@@ -434,7 +452,8 @@ exports.v4AssignMode = async function (req, res) {
try {
const network = await zt.network_object(req.params.nwid, v4AssignMode);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('v4AssignMode', {title: 'v4AssignMode', navigate: navigate, network: network});
} catch (err) {
res.render('v4AssignMode', {title: 'v4AssignMode', navigate: navigate, error: 'Error applying v4AssignMode for network ' + req.params.nwid + ': ' + err});
@@ -461,7 +480,8 @@ exports.v6AssignMode = async function (req, res) {
try {
const network = await zt.network_object(req.params.nwid, v6AssignMode);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('v6AssignMode', {title: 'v6AssignMode', navigate: navigate, network: network});
} catch (err) {
res.render('v6AssignMode', {title: 'v6AssignMode', navigate: navigate, error: 'Error applying v6AssignMode for network ' + req.params.nwid + ': ' + err});
@@ -490,7 +510,8 @@ exports.dns = async function (req, res) {
try {
const network = await zt.network_object(req.params.nwid, dns);
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/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});
@@ -507,7 +528,8 @@ exports.member_detail = async function(req, res) {
try {
const {network, member} = await get_network_member(req.params.nwid, req.params.id);
- navigate.whence = '/controller/network/' + network.nwid + '#members';
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid + '#members';
res.render('member_detail', {title: 'Network member detail', navigate: navigate, network: network, member: member});
} catch (err) {
console.error(err);
@@ -525,7 +547,8 @@ exports.member_object = async function(req, res) {
try {
const {network, member} = await get_network_member(req.params.nwid, req.params.id);
- navigate.whence = '/controller/network/' + network.nwid + '#members';
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/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 ) {
@@ -542,10 +565,11 @@ exports.member_object = async function(req, res) {
// Easy network setup GET
exports.easy_get = async function(req, res) {
+ const basePath = getBasePath(req);
const navigate =
{
active: 'networks',
- whence: '/controller/network/' + req.params.nwid
+ whence: basePath + '/controller/network/' + req.params.nwid
}
try {
@@ -558,10 +582,11 @@ exports.easy_get = async function(req, res) {
// Easy network setup POST
exports.easy_post = async function(req, res) {
+ const basePath = getBasePath(req);
const navigate =
{
active: 'networks',
- whence: '/controller/networks'
+ whence: basePath + '/controller/networks'
}
req.checkBody('networkCIDR', 'Network address is required').notEmpty();
@@ -631,7 +656,7 @@ exports.members = async function(req, res) {
const navigate =
{
active: 'networks',
- whence: '/controller/networks'
+ whence: getBasePath(req) + '/controller/networks'
}
let errors = null;
@@ -694,8 +719,17 @@ exports.members = async function(req, res) {
}
}
}
+
+ // Respond based on validation result
+ if (errors) {
+ return res.status(400).json({ errors });
+ }
+
+ // No content needed for AJAX updates
+ return res.sendStatus(204);
} else { // GET
- res.redirect("/controller/network/" + req.params.nwid + "#members");
+ const basePath = getBasePath(req);
+ res.redirect(basePath + "/controller/network/" + req.params.nwid + "#members");
}
}
@@ -722,7 +756,8 @@ exports.member_delete = async function(req, res) {
}
member.name = name || '';
- navigate.whence = '/controller/network/' + network.nwid;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
res.render('member_delete', {title: 'Delete member from ' + network.name,
navigate: navigate, network: network, member: member});
} catch (err) {
@@ -743,12 +778,13 @@ 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;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/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);
- res.redirect('/controller/network/' + network.nwid + '/member/' +
+ res.redirect(basePath + '/controller/network/' + network.nwid + '/member/' +
member.id + '/ipAssignments');
}
res.render('ipAssignments', {title: 'ipAssignments ' + network.name,
@@ -802,7 +838,8 @@ 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;
+ const basePath = getBasePath(req);
+ navigate.whence = basePath + '/controller/network/' + network.nwid;
if (!errors) {
member = await zt.ipAssignmentAdd(network.nwid, member.id, ipAssignment);
diff --git a/src/controllers/usersController.js b/src/controllers/usersController.js
index 4ac89fb..cd70922 100644
--- a/src/controllers/usersController.js
+++ b/src/controllers/usersController.js
@@ -150,7 +150,8 @@ exports.user_create_post = async function(req, res) {
active: 'create_user',
}
- res.redirect(307, '/users/' + req.body.username + '/password');
+ const basePath = (res && res.locals && res.locals.basePath) ? res.locals.basePath : '';
+ res.redirect(307, basePath + '/users/' + req.body.username + '/password');
}
exports.user_delete = async function(req, res) {
diff --git a/src/routes/index.js b/src/routes/index.js
index cc255fb..6c45e06 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -13,7 +13,8 @@ const router = express.Router();
/** Redirect logged user to controler page */
function guest_only(req, res, next) {
if (req.session.user) {
- res.redirect('/controller');
+ const basePath = req.app && req.app.locals ? (req.app.locals.basePath || '') : '';
+ res.redirect(basePath + '/controller');
} else {
next();
}
@@ -26,7 +27,8 @@ router.get('/', guest_only, function(req, res, next) {
router.get('/logout', function(req, res) {
req.session.destroy(function() {
- res.redirect('/');
+ const basePath = req.app && req.app.locals ? (req.app.locals.basePath || '') : '';
+ res.redirect(basePath + '/');
});
});
@@ -49,14 +51,18 @@ router.post('/login', async function(req, res) {
req.session.user = user;
req.session.success = 'Authenticated as ' + user.name;
if (user.pass_set) {
- res.redirect(req.query.redirect || '/controller');
+ const basePath = req.app && req.app.locals ? (req.app.locals.basePath || '') : '';
+ const redirectTarget = req.query.redirect || (basePath + '/controller');
+ res.redirect(redirectTarget);
} else {
- res.redirect('/users/' + user.name + '/password');
+ const basePath = req.app && req.app.locals ? (req.app.locals.basePath || '') : '';
+ res.redirect(basePath + '/users/' + user.name + '/password');
}
});
} else {
req.session.error = 'Authentication failed, please check your username and password.'
- res.redirect('/login');
+ const basePath = req.app && req.app.locals ? (req.app.locals.basePath || '') : '';
+ res.redirect(basePath + '/login');
}
});
});
diff --git a/src/views/controller_layout.pug b/src/views/controller_layout.pug
index b0f72ee..57c59e6 100644
--- a/src/views/controller_layout.pug
+++ b/src/views/controller_layout.pug
@@ -6,10 +6,10 @@
extends head_layout
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')
+ +nav_item('controller_home', 'Home', basePath + '/controller')
+ +nav_item('users', 'Users', basePath + '/users')
+ +nav_item('networks', 'Networks', basePath + '/controller/networks')
+ +nav_item('add_network', 'Add network', basePath + '/controller/network/create')
block body_content
.container(style='margin: 50px auto 20px')
diff --git a/src/views/dns.pug b/src/views/dns.pug
index 87b08a4..19f008a 100644
--- a/src/views/dns.pug
+++ b/src/views/dns.pug
@@ -47,7 +47,7 @@ block net_content
.col-sm-12
button.btn.btn-primary(type='submit') Submit
= ' '
- a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/controller/network/' + network.nwid) name='cancel' role='button') Cancel
if errors
.row
diff --git a/src/views/head_layout.pug b/src/views/head_layout.pug
index c2be79c..56da6f2 100644
--- a/src/views/head_layout.pug
+++ b/src/views/head_layout.pug
@@ -19,10 +19,12 @@ html(lang='en')
title= title
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
- link(rel='stylesheet', href='/bscss/bootstrap.min.css')
- link(rel='stylesheet', href='/stylesheets/style.css')
- script(src='/jqjs/jquery.min.js')
- script(src='/bsjs/bootstrap.min.js')
+ link(rel='stylesheet', href=(basePath + '/bscss/bootstrap.min.css'))
+ link(rel='stylesheet', href=(basePath + '/stylesheets/style.css'))
+ link(rel='icon', type='image/x-icon', href=(basePath + '/favicon.ico'))
+ link(rel='shortcut icon', type='image/x-icon', href=(basePath + '/favicon.ico'))
+ script(src=(basePath + '/jqjs/jquery.min.js'))
+ script(src=(basePath + '/bsjs/bootstrap.min.js'))
body
nav.navbar.navbar-inverse.navbar-fixed-top
.container-fluid
@@ -32,7 +34,7 @@ html(lang='en')
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')
+ img(src=(basePath + '/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
@@ -40,7 +42,7 @@ html(lang='en')
ul.nav.navbar-nav.navbar-right
li
block nav_login
- a(href='/logout')
+ a(href=(basePath + '/logout'))
span.glyphicon.glyphicon-log-out
| Logout
block body_content
diff --git a/src/views/index.pug b/src/views/index.pug
index 6a77369..aae3f9f 100644
--- a/src/views/index.pug
+++ b/src/views/index.pug
@@ -19,4 +19,4 @@ block content
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
+ a(href=(basePath + '/controller/networks')) List all networks on this network controller
diff --git a/src/views/ipAssignmentPools.pug b/src/views/ipAssignmentPools.pug
index 7af58bf..6487358 100644
--- a/src/views/ipAssignmentPools.pug
+++ b/src/views/ipAssignmentPools.pug
@@ -16,7 +16,7 @@ block net_content
each ipAssignmentPool in network.ipAssignmentPools
tr
td(width='3%')
- a(href='/controller/network/' + network.nwid + '/ipAssignmentPools/' + ipAssignmentPool.ipRangeStart + '/' + ipAssignmentPool.ipRangeEnd + '/delete')
+ a(href=(basePath + '/controller/network/' + network.nwid + '/ipAssignmentPools/' + ipAssignmentPool.ipRangeStart + '/' + ipAssignmentPool.ipRangeEnd + '/delete'))
i.glyphicon.glyphicon-trash
td= ipAssignmentPool.ipRangeStart
td= ipAssignmentPool.ipRangeEnd
@@ -25,7 +25,7 @@ block net_content
.col-sm-12
h3 Add new IP Assignment Pool:
- form(method='POST' action='/controller/network/' + network.nwid + '/ipAssignmentPools')
+ form(method='POST' action=(basePath + '/controller/network/' + network.nwid + '/ipAssignmentPools'))
.form-group.row
.col-sm-2
label(for='ipRangeStart') IP range start:
@@ -42,7 +42,7 @@ block net_content
.col-sm-12
button.btn.btn-primary(type='submit') Submit
= ' '
- a.btn.btn-default(href='/controller/network/' + network.nwid name='cancel' role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/controller/network/' + network.nwid) name='cancel' role='button') Cancel
if errors
.row
diff --git a/src/views/ipAssignments.pug b/src/views/ipAssignments.pug
index 26c3ee1..f2a7d90 100644
--- a/src/views/ipAssignments.pug
+++ b/src/views/ipAssignments.pug
@@ -32,7 +32,7 @@ block net_content
each ipAssignment, index in member.ipAssignments
tr
td(width='3%')
- a.btn.btn-link(role='button' href='/controller/network/' + network.nwid + '/member/' + member.id + '/ipAssignments/' + index + '/delete')
+ a.btn.btn-link(role='button' href=(basePath + '/controller/network/' + network.nwid + '/member/' + member.id + '/ipAssignments/' + index + '/delete'))
i.glyphicon.glyphicon-trash
td
each digit in ipAssignment
@@ -47,7 +47,7 @@ block net_content
.row
.col-sm-12
- a(href='/controller/network/' + network.nwid + '/routes')
+ a(href=(basePath + '/controller/network/' + network.nwid + '/routes'))
h3 Managed routes
table.table.table-responsive.table-striped.table-hover
tr
@@ -57,7 +57,7 @@ block net_content
each route in network.routes
tr
td(width='3%')
- a.btn.btn-link(role='button' href='/controller/network/' + network.nwid + '/routes/' + route.target + '/delete')
+ a.btn.btn-link(role='button' href=(basePath + '/controller/network/' + network.nwid + '/routes/' + route.target + '/delete'))
i.glyphicon.glyphicon-trash
td= route.target
td= route.via
diff --git a/src/views/login.pug b/src/views/login.pug
index 9a131ab..392fdd3 100644
--- a/src/views/login.pug
+++ b/src/views/login.pug
@@ -42,4 +42,4 @@ block login_content
.col-sm-10
button.btn.btn-primary(type='submit') Login
= ' '
- a.btn.btn-default(href='/' name='cancel' role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/') name='cancel' role='button') Cancel
diff --git a/src/views/login_layout.pug b/src/views/login_layout.pug
index a9fcb5c..8cce467 100644
--- a/src/views/login_layout.pug
+++ b/src/views/login_layout.pug
@@ -6,7 +6,7 @@
extends head_layout
block nav_login
- a(href='/login')
+ a(href=(basePath + '/login'))
span.glyphicon.glyphicon-log-in
| Login
diff --git a/src/views/member_delete.pug b/src/views/member_delete.pug
index e6001ea..c623162 100644
--- a/src/views/member_delete.pug
+++ b/src/views/member_delete.pug
@@ -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=('/controller/network/' + network.nwid + '#members') name='networks' role='button') Members
+ a.btn.btn-default(href=(basePath + '/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=(basePath + '/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 8d1369a..03a681b 100644
--- a/src/views/member_detail.pug
+++ b/src/views/member_detail.pug
@@ -12,9 +12,9 @@ block net_content
each value, key in member
.row
.col-sm-2
- a(href=('/controller/network/' + member.nwid + '/member/' + member.address + '/' + key)) #{key}:
+ a(href=(basePath + '/controller/network/' + member.nwid + '/member/' + member.address + '/' + key)) #{key}:
.col-sm-10
+json_value(value)
- a.btn.btn-default(href=('/controller/network/' + member.nwid + "#members") name='networks' role='button' style='margin-top: 10px;')
+ a.btn.btn-default(href=(basePath + '/controller/network/' + member.nwid + "#members") name='networks' role='button' style='margin-top: 10px;')
| Members
diff --git a/src/views/network_delete.pug b/src/views/network_delete.pug
index 00bba12..9a20733 100644
--- a/src/views/network_delete.pug
+++ b/src/views/network_delete.pug
@@ -16,7 +16,7 @@ block net_content
form(method='POST' action='')
button.btn.btn-danger(type='submit', name='delete') Delete #{network.name} (#{network.nwid})
= ' '
- a.btn.btn-default(href='/controller/networks', name='cancel', role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/controller/networks'), name='cancel', role='button') Cancel
if errors
ul
diff --git a/src/views/network_detail.pug b/src/views/network_detail.pug
index 127dd6a..0005fb9 100644
--- a/src/views/network_detail.pug
+++ b/src/views/network_detail.pug
@@ -19,7 +19,7 @@ block network_title
| (#{network.nwid}):
script.
$(function() {
- var nwurl = '/controller/network/#{network.nwid}';
+ var nwurl = '#{basePath}/controller/network/#{network.nwid}';
var name = !{JSON.stringify(network.name)};
function toggleNameEditor(show) {
@@ -51,7 +51,7 @@ block network_title
});
block net_content
- - const nwurl = '/controller/network/' + network.nwid;
+ - const nwurl = basePath + '/controller/network/' + network.nwid;
a.btn.btn-primary(style="margin: 5px" href=(nwurl + '/private') role='button')
= network.private ? "Private" : "Public"
@@ -75,6 +75,18 @@ block net_content
$('.text').on('change', function() {
$.post(url, {'id': this.name, 'name': this.value});
});
+ // Prevent form submit on Enter which would POST to the page URL
+ $('form').on('submit', function(e) {
+ e.preventDefault();
+ return false;
+ });
+ // If Enter is pressed inside the name input, prevent submit and trigger change
+ $('.text').on('keypress', function(e) {
+ if (e.which == 13) {
+ e.preventDefault();
+ $(this).blur();
+ }
+ });
});
h3#members Members (#{members.length})
form(method='POST' action='')
@@ -154,8 +166,8 @@ block net_content
each value, key in network
.row(style='margin: 5px 0;')
.col-sm-2
- a(href= network.nwid + '/' + key) #{key}:
+ a(href= nwurl + '/' + key) #{key}:
.col-sm-10
+json_value(value)
- a.btn.btn-default(href='/controller/networks' name='networks' role='button' style='margin-top: 10px;') Networks
+ a.btn.btn-default(href=(basePath + '/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 209428e..8a1aec8 100644
--- a/src/views/network_easy.pug
+++ b/src/views/network_easy.pug
@@ -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/network/' + network.nwid) name='cancel' role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/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 2004c85..ff4c839 100644
--- a/src/views/network_layout.pug
+++ b/src/views/network_layout.pug
@@ -14,7 +14,7 @@ block content
block network_title
h2
| Network
- a(href='/controller/network/' + network.nwid) #{network.name}
+ a(href=(basePath + '/controller/network/' + network.nwid)) #{network.name}
| (#{network.nwid}):
block title
if title
diff --git a/src/views/networks.pug b/src/views/networks.pug
index 6a9e5d4..7b11ee3 100644
--- a/src/views/networks.pug
+++ b/src/views/networks.pug
@@ -26,7 +26,7 @@ block content
th(width='37%')
= ''
each network in networks
- - const nwurl = '/controller/network/' + network.nwid;
+ - const nwurl = basePath + '/controller/network/' + network.nwid;
tr
td
a(href=nwurl + '/delete')
diff --git a/src/views/not_implemented.pug b/src/views/not_implemented.pug
index 8e61eca..85ee1b3 100644
--- a/src/views/not_implemented.pug
+++ b/src/views/not_implemented.pug
@@ -16,5 +16,5 @@ block net_content
h4
| Note that you may be able to edit some properties on the
strong
- a(href='/controller/network/' + network.nwid + '#members') Members
+ a(href=(basePath + '/controller/network/' + network.nwid + '#members')) Members
| page.
diff --git a/src/views/password.pug b/src/views/password.pug
index 09b9369..cdef7ca 100644
--- a/src/views/password.pug
+++ b/src/views/password.pug
@@ -51,7 +51,7 @@ block users_content
.col-sm-10
button.btn.btn-primary(type='submit') Set password
= ' '
- a.btn.btn-default(href='/users' name='cancel' role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/users') name='cancel' role='button') Cancel
if errors
.form-group.row
diff --git a/src/views/routes.pug b/src/views/routes.pug
index d80c5e4..390579f 100644
--- a/src/views/routes.pug
+++ b/src/views/routes.pug
@@ -16,7 +16,7 @@ block net_content
each route in network.routes
tr
td(width='3%')
- a(href='/controller/network/' + network.nwid + '/routes/' + route.target + '/delete')
+ a(href=(basePath + '/controller/network/' + network.nwid + '/routes/' + route.target + '/delete'))
i.glyphicon.glyphicon-trash
td= route.target
td= route.via
@@ -25,7 +25,7 @@ block net_content
.col-sm-12
h3 Add new route:
- form(method='POST' action='/controller/network/' + network.nwid + '/routes')
+ form(method='POST' action=(basePath + '/controller/network/' + network.nwid + '/routes'))
.form-group.row
.col-sm-12
label(for='target') Target:
@@ -42,7 +42,7 @@ block net_content
.col-sm-12
button.btn.btn-primary(type='submit') Submit
= ' '
- a.btn.btn-default(href=('/controller/network/' + network.nwid) name='cancel' role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/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 d9d74b6..458eda3 100644
--- a/src/views/user_delete.pug
+++ b/src/views/user_delete.pug
@@ -24,7 +24,7 @@ block users_content
form(method='POST' action='')
button.btn.btn-danger(type='submit', name='delete' value='delete') Delete #{user.name}
= ' '
- a.btn.btn-default(href='/users', name='cancel', role='button') Cancel
+ a.btn.btn-default(href=(basePath + '/users'), name='cancel', role='button') Cancel
if errors
ul
diff --git a/src/views/users.pug b/src/views/users.pug
index 5cef684..b8451cb 100644
--- a/src/views/users.pug
+++ b/src/views/users.pug
@@ -10,12 +10,12 @@ block users_content
each user in users
tr
td(width='3%')
- a(href='/users/' + user.name + '/delete')
+ a(href=(basePath + '/users/' + user.name + '/delete'))
i.glyphicon.glyphicon-trash
td(width='15%')
- a(href='/users/' + user.name + '/password') #{user.name}
+ a(href=(basePath + '/users/' + user.name + '/password')) #{user.name}
td(width='82%')
- a(href='/users/' + user.name + '/password') set password
+ a(href=(basePath + '/users/' + user.name + '/password')) set password
else
.alert.alert-info
diff --git a/src/views/users_layout.pug b/src/views/users_layout.pug
index bf5e73d..c0633e4 100644
--- a/src/views/users_layout.pug
+++ b/src/views/users_layout.pug
@@ -6,10 +6,10 @@
extends head_layout
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')
+ +nav_item('controller_home', 'Home', basePath + '/controller')
+ +nav_item('users', 'Users', basePath + '/users')
+ +nav_item('networks', 'Networks', basePath + '/controller/networks')
+ +nav_item('create_user', 'Create user', basePath + '/users/create')
block body_content
.container(style='margin-top:50px')