diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index ea85045b5..7b57180e6 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -6,7 +6,7 @@ import Sidebar from './sidebar.js';
import Status from './status.js';
import Topologies from './topologies.js';
import TopologyOptions from './topology-options.js';
-import { getApiDetails, getTopologies } from '../utils/web-api-utils';
+import { getApiDetails, getTopologies, basePathSlash } from '../utils/web-api-utils';
import { hitEsc } from '../actions/app-actions';
import Details from './details';
import Nodes from './nodes';
@@ -51,7 +51,7 @@ export default class App extends React.Component {
AppStore.addListener(this.onChange);
window.addEventListener('keyup', this.onKeyPress);
- getRouter(this.props.base).start({hashbang: true});
+ getRouter().start({hashbang: true});
if (!AppStore.isRouteSet()) {
// dont request topologies when already done via router
getTopologies(AppStore.getActiveTopologyOptions());
@@ -75,9 +75,9 @@ export default class App extends React.Component {
// width of details panel blocking a view
const detailsWidth = showingDetails ? 450 : 0;
const topMargin = 100;
- const contrastMode = this.props.base.indexOf('contrast') > -1;
+ const contrastMode = window.location.pathname.indexOf('contrast') > -1;
// link url to switch contrast with current UI state
- const otherContrastModeUrl = contrastMode ? '/' : '/contrast.html';
+ const otherContrastModeUrl = contrastMode ? basePathSlash(window.location.pathname) : 'contrast.html';
const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast';
return (
diff --git a/client/app/scripts/contrast-main.js b/client/app/scripts/contrast-main.js
index 29b3d735d..18de464be 100644
--- a/client/app/scripts/contrast-main.js
+++ b/client/app/scripts/contrast-main.js
@@ -6,4 +6,4 @@ import ReactDOM from 'react-dom';
import App from './components/app.js';
-ReactDOM.render(, document.getElementById('app'));
+ReactDOM.render(, document.getElementById('app'));
diff --git a/client/app/scripts/main.js b/client/app/scripts/main.js
index 10cbcc26a..129e1d6ab 100644
--- a/client/app/scripts/main.js
+++ b/client/app/scripts/main.js
@@ -6,4 +6,4 @@ import ReactDOM from 'react-dom';
import App from './components/app.js';
-ReactDOM.render(, document.getElementById('app'));
+ReactDOM.render(, document.getElementById('app'));
diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js
index f55a9af2d..b2a4e3208 100644
--- a/client/app/scripts/utils/router-utils.js
+++ b/client/app/scripts/utils/router-utils.js
@@ -38,7 +38,8 @@ page('/state/:state', function(ctx) {
route(state);
});
-export function getRouter(base) {
- page.base(base);
+export function getRouter() {
+ // strip any trailing '/'s.
+ page.base(window.location.pathname.replace(/\/$/, ''));
return page;
}
diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js
index aab8056b4..f9a2f977d 100644
--- a/client/app/scripts/utils/web-api-utils.js
+++ b/client/app/scripts/utils/web-api-utils.js
@@ -45,6 +45,16 @@ export function basePath(urlPath) {
return parts.join('/').replace(/\/$/, '');
}
+export function basePathSlash(urlPath) {
+ //
+ // "/scope/terminal.html" -> "/scope/"
+ // "/scope/" -> "/scope/"
+ // "/scope" -> "/scope/"
+ // "/" -> "/"
+ //
+ return basePath(urlPath) + '/';
+}
+
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = wsProto + '://' + location.host + basePath(location.pathname);
diff --git a/client/package.json b/client/package.json
index bdb70ca42..824502f96 100644
--- a/client/package.json
+++ b/client/package.json
@@ -42,6 +42,7 @@
"eslint-plugin-jasmine": "1.6.0",
"eslint-plugin-react": "3.8.0",
"file-loader": "0.8.4",
+ "http-proxy-rules": "^1.0.1",
"jest-cli": "~0.7.1",
"json-loader": "0.5.3",
"less": "~2.5.1",
diff --git a/client/server.js b/client/server.js
index 6efba3ec9..62fb9d7c8 100644
--- a/client/server.js
+++ b/client/server.js
@@ -1,5 +1,7 @@
var express = require('express');
+var http = require('http');
var httpProxy = require('http-proxy');
+var HttpProxyRules = require('http-proxy-rules');
var url = require('url');
var app = express();
@@ -90,3 +92,39 @@ var server = app.listen(port, function () {
});
server.on('upgrade', proxy.ws.bind(proxy));
+
+
+/*************************************************************
+ *
+ * path proxy server
+ *
+ *************************************************************/
+
+var proxyRules = new HttpProxyRules({
+ rules: {
+ '/scoped/': 'http://localhost:' + port
+ }
+});
+
+var pathProxy = httpProxy.createProxy({ws: true});
+var pathProxyPort = port + 1;
+const proxyPathServer = http.createServer(function(req, res) {
+ var target = proxyRules.match(req);
+ if (!target) {
+ res.writeHead(500, {'Content-Type': 'text/plain'});
+ res.end('No rules matched! Check out /scoped/');
+ return;
+ }
+ return pathProxy.web(req, res, {target: target});
+}).listen(pathProxyPort, function() {
+ var pathProxyHost = proxyPathServer.address().address;
+ console.log('Scope Proxy Path UI listening at http://%s:%s/scoped/',
+ pathProxyHost, pathProxyPort);
+});
+
+proxyPathServer.on('upgrade', function(req, socket, head) {
+ var target = proxyRules.match(req);
+ if (target) {
+ return pathProxy.ws(req, socket, head, {target: target});
+ }
+});