mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-16 03:49:52 +00:00
UI for controls.
- Make backend address configurable via env variable - `BACKEND_HOST=1.2.3.4:4040 npm start` points the frontend to the app on that server. Just for development - Render control icons - removed close x for details panel - added control icons in its space - closing of panel still works by clicking on same node, or background - Dont allow control while pending - Render and auto-dismiss control error - Make tests pass
This commit is contained in:
committed by
Tom Wilkie
parent
8f957c4f13
commit
abcb94b1f1
@@ -26,6 +26,9 @@
|
||||
"templateStrings": true,
|
||||
"jsx": true
|
||||
},
|
||||
"globals": {
|
||||
__WS_URL__: false
|
||||
},
|
||||
"rules": {
|
||||
/**
|
||||
* Strict mode
|
||||
|
||||
@@ -66,12 +66,29 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
clearControlError: function() {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CLEAR_CONTROL_ERROR
|
||||
});
|
||||
},
|
||||
|
||||
closeWebsocket: function() {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CLOSE_WEBSOCKET
|
||||
});
|
||||
},
|
||||
|
||||
doControl: function(probeId, nodeId, control) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.DO_CONTROL
|
||||
});
|
||||
WebapiUtils.doControl(
|
||||
probeId,
|
||||
nodeId,
|
||||
control,
|
||||
);
|
||||
},
|
||||
|
||||
enterEdge: function(edgeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.ENTER_EDGE,
|
||||
@@ -107,6 +124,19 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
receiveControlError: function(err) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.DO_CONTROL_ERROR,
|
||||
error: err
|
||||
});
|
||||
},
|
||||
|
||||
receiveControlSuccess: function() {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.DO_CONTROL_SUCCESS
|
||||
});
|
||||
},
|
||||
|
||||
receiveNodeDetails: function(details) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODE_DETAILS,
|
||||
@@ -139,15 +169,15 @@ module.exports = {
|
||||
|
||||
receiveApiDetails: function(apiDetails) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_API_DETAILS,
|
||||
version: apiDetails.version
|
||||
type: ActionTypes.RECEIVE_API_DETAILS,
|
||||
version: apiDetails.version
|
||||
});
|
||||
},
|
||||
|
||||
receiveError: function(errorUrl) {
|
||||
AppDispatcher.dispatch({
|
||||
errorUrl: errorUrl,
|
||||
type: ActionTypes.RECEIVE_ERROR
|
||||
errorUrl: errorUrl,
|
||||
type: ActionTypes.RECEIVE_ERROR
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ const Node = React.createClass({
|
||||
const subLabelOffsetY = labelOffsetY + 17;
|
||||
const isPseudo = !!this.props.pseudo;
|
||||
const color = isPseudo ? '' : this.getNodeColor(this.props.rank);
|
||||
const onClick = this.props.onClick;
|
||||
const onMouseEnter = this.handleMouseEnter;
|
||||
const onMouseLeave = this.handleMouseLeave;
|
||||
const onMouseClick = this.handleMouseClick;
|
||||
const classNames = ['node'];
|
||||
const animConfig = [80, 20]; // stiffness, bounce
|
||||
const label = this.ellipsis(props.label, 14, scale(4 * scaleFactor));
|
||||
@@ -51,7 +51,7 @@ const Node = React.createClass({
|
||||
const transform = `translate(${interpolated.x.val},${interpolated.y.val})`;
|
||||
return (
|
||||
<g className={classes} transform={transform} id={props.id}
|
||||
onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
onClick={onMouseClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
{props.highlighted && <circle r={scale(0.7 * interpolated.f.val)} className="highlighted"></circle>}
|
||||
<circle r={scale(0.5 * interpolated.f.val)} className="border" stroke={color}></circle>
|
||||
<circle r={scale(0.45 * interpolated.f.val)} className="shadow"></circle>
|
||||
@@ -79,6 +79,11 @@ const Node = React.createClass({
|
||||
return truncatedText;
|
||||
},
|
||||
|
||||
handleMouseClick: function(ev) {
|
||||
ev.stopPropagation();
|
||||
AppActions.clickNode(ev.currentTarget.id);
|
||||
},
|
||||
|
||||
handleMouseEnter: function(ev) {
|
||||
AppActions.enterNode(ev.currentTarget.id);
|
||||
},
|
||||
|
||||
@@ -218,7 +218,7 @@ const NodesChart = React.createClass({
|
||||
<div className="nodes-chart">
|
||||
{errorEmpty}
|
||||
{errorMaxNodesExceeded}
|
||||
<svg width="100%" height="100%" className={svgClassNames} onMouseUp={this.handleMouseUp}>
|
||||
<svg width="100%" height="100%" className={svgClassNames} onClick={this.handleMouseClick}>
|
||||
<Spring endValue={{val: translate, config: [80, 20]}}>
|
||||
{function(interpolated) {
|
||||
let interpolatedTranslate = wasShifted ? interpolated.val : panTranslate;
|
||||
@@ -402,7 +402,7 @@ const NodesChart = React.createClass({
|
||||
|
||||
isZooming: false, // distinguish pan/zoom from click
|
||||
|
||||
handleMouseUp: function() {
|
||||
handleMouseClick: function() {
|
||||
if (!this.isZooming) {
|
||||
AppActions.clickCloseDetails();
|
||||
// allow shifts again
|
||||
|
||||
@@ -2,6 +2,8 @@ jest.dontMock('../node-details.js');
|
||||
jest.dontMock('../../mixins/node-color-mixin');
|
||||
jest.dontMock('../../utils/title-utils');
|
||||
|
||||
__WS_URL__ = false
|
||||
|
||||
describe('NodeDetails', () => {
|
||||
let NodeDetails;
|
||||
let nodes;
|
||||
|
||||
@@ -20,6 +20,8 @@ const ESC_KEY_CODE = 27;
|
||||
function getStateFromStores() {
|
||||
return {
|
||||
activeTopologyOptions: AppStore.getActiveTopologyOptions(),
|
||||
controlError: AppStore.getControlError(),
|
||||
controlPending: AppStore.isControlPending(),
|
||||
currentTopology: AppStore.getCurrentTopology(),
|
||||
currentTopologyId: AppStore.getCurrentTopologyId(),
|
||||
currentTopologyOptions: AppStore.getCurrentTopologyOptions(),
|
||||
@@ -81,6 +83,8 @@ const App = React.createClass({
|
||||
return (
|
||||
<div>
|
||||
{showingDetails && <Details nodes={this.state.nodes}
|
||||
controlError={this.state.controlError}
|
||||
controlPending={this.state.controlPending}
|
||||
nodeId={this.state.selectedNodeId}
|
||||
details={this.state.nodeDetails} /> }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ const React = require('react');
|
||||
const mui = require('material-ui');
|
||||
const Paper = mui.Paper;
|
||||
|
||||
const AppActions = require('../actions/app-actions');
|
||||
const NodeDetails = require('./node-details');
|
||||
|
||||
const Details = React.createClass({
|
||||
@@ -11,21 +10,10 @@ const Details = React.createClass({
|
||||
return (
|
||||
<div id="details">
|
||||
<Paper zDepth={3} style={{height: '100%', paddingBottom: 8}}>
|
||||
<div className="details-tools-wrapper">
|
||||
<div className="details-tools">
|
||||
<span className="fa fa-close" onClick={this.handleClickClose} />
|
||||
</div>
|
||||
</div>
|
||||
<NodeDetails nodeId={this.props.nodeId} details={this.props.details}
|
||||
nodes={this.props.nodes} />
|
||||
<NodeDetails {...this.props} />
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
handleClickClose: function(ev) {
|
||||
ev.preventDefault();
|
||||
AppActions.clickCloseDetails();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
23
client/app/scripts/components/node-control-button.js
Normal file
23
client/app/scripts/components/node-control-button.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const React = require('react');
|
||||
const AppActions = require('../actions/app-actions');
|
||||
|
||||
const NodeControlButton = React.createClass({
|
||||
|
||||
render: function() {
|
||||
let className = `node-control-button fa ${this.props.control.icon}`;
|
||||
if (this.props.pending) {
|
||||
className += ' node-control-button-pending';
|
||||
}
|
||||
return (
|
||||
<span className={className} title={this.props.control.human} onClick={this.handleClick} />
|
||||
);
|
||||
},
|
||||
|
||||
handleClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
AppActions.doControl(this.props.control.probeId, this.props.control.nodeId, this.props.control.id);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = NodeControlButton;
|
||||
25
client/app/scripts/components/node-details-controls.js
Normal file
25
client/app/scripts/components/node-details-controls.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const React = require('react');
|
||||
|
||||
const NodeControlButton = require('./node-control-button');
|
||||
|
||||
const NodeDetailsControls = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="node-details-controls">
|
||||
{this.props.error && <div className="node-details-controls-error" title={this.props.error}>
|
||||
<span className="node-details-controls-error-icon fa fa-warning" />
|
||||
<span className="node-details-controls-error-messages">{this.props.error}</span>
|
||||
</div>}
|
||||
{this.props.controls && this.props.controls.map(control => {
|
||||
return (
|
||||
<NodeControlButton control={control} pending={this.props.pending} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = NodeDetailsControls;
|
||||
@@ -1,6 +1,7 @@
|
||||
const _ = require('lodash');
|
||||
const React = require('react');
|
||||
|
||||
const NodeDetailsControls = require('./node-details-controls');
|
||||
const NodeDetailsTable = require('./node-details-table');
|
||||
const NodeColorMixin = require('../mixins/node-color-mixin');
|
||||
const TitleUtils = require('../utils/title-utils');
|
||||
@@ -65,6 +66,8 @@ const NodeDetails = React.createClass({
|
||||
return (
|
||||
<div className="node-details">
|
||||
<div className="node-details-header" style={style}>
|
||||
<NodeDetailsControls controls={details.controls}
|
||||
pending={this.props.controlPending} error={this.props.controlError} />
|
||||
<h2 className="node-details-header-label truncate" title={details.label_major}>
|
||||
{details.label_major}
|
||||
</h2>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const React = require('react');
|
||||
|
||||
const NodesChart = require('../charts/nodes-chart');
|
||||
const AppActions = require('../actions/app-actions');
|
||||
|
||||
const navbarHeight = 160;
|
||||
const marginTop = 0;
|
||||
@@ -23,10 +22,6 @@ const Nodes = React.createClass({
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
},
|
||||
|
||||
onNodeClick: function(ev) {
|
||||
AppActions.clickNode(ev.currentTarget.id);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<NodesChart
|
||||
@@ -34,7 +29,6 @@ const Nodes = React.createClass({
|
||||
highlightedNodeIds={this.props.highlightedNodeIds}
|
||||
selectedNodeId={this.props.selectedNodeId}
|
||||
nodes={this.props.nodes}
|
||||
onNodeClick={this.onNodeClick}
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
topologyId={this.props.topologyId}
|
||||
|
||||
@@ -2,10 +2,14 @@ const keymirror = require('keymirror');
|
||||
|
||||
module.exports = keymirror({
|
||||
CHANGE_TOPOLOGY_OPTION: null,
|
||||
CLEAR_CONTROL_ERROR: null,
|
||||
CLICK_CLOSE_DETAILS: null,
|
||||
CLICK_NODE: null,
|
||||
CLICK_TOPOLOGY: null,
|
||||
CLOSE_WEBSOCKET: null,
|
||||
DO_CONTROL: null,
|
||||
DO_CONTROL_ERROR: null,
|
||||
DO_CONTROL_SUCCESS: null,
|
||||
ENTER_EDGE: null,
|
||||
ENTER_NODE: null,
|
||||
HIT_ESC_KEY: null,
|
||||
|
||||
@@ -5,7 +5,7 @@ const AppDispatcher = new flux.Dispatcher();
|
||||
|
||||
AppDispatcher.dispatch = _.wrap(flux.Dispatcher.prototype.dispatch, function(func) {
|
||||
const args = Array.prototype.slice.call(arguments, 1);
|
||||
// console.log(args[0]);
|
||||
console.log(args[0]);
|
||||
func.apply(this, args);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// This file is an entrypoint for development,
|
||||
// see main.js for the real entrypoint
|
||||
|
||||
// Inject websocket url to dev backend
|
||||
window.WS_PROTO = (location.protocol === 'https:' ? 'wss' : 'ws');
|
||||
window.WS_URL = window.WS_PROTO + '://' + location.hostname + ':4040';
|
||||
|
||||
require('./main');
|
||||
@@ -46,6 +46,8 @@ function makeNode(node) {
|
||||
|
||||
let topologyOptions = makeOrderedMap();
|
||||
let adjacentNodes = makeSet();
|
||||
let controlError = null;
|
||||
let controlPending = false;
|
||||
let currentTopology = null;
|
||||
let currentTopologyId = 'containers';
|
||||
let errorUrl = null;
|
||||
@@ -132,6 +134,10 @@ const AppStore = assign({}, EventEmitter.prototype, {
|
||||
return adjacentNodes;
|
||||
},
|
||||
|
||||
getControlError: function() {
|
||||
return controlError;
|
||||
},
|
||||
|
||||
getCurrentTopology: function() {
|
||||
if (!currentTopology) {
|
||||
currentTopology = setTopology(currentTopologyId);
|
||||
@@ -209,6 +215,10 @@ const AppStore = assign({}, EventEmitter.prototype, {
|
||||
return version;
|
||||
},
|
||||
|
||||
isControlPending: function() {
|
||||
return controlPending;
|
||||
},
|
||||
|
||||
isRouteSet: function() {
|
||||
return routeSet;
|
||||
},
|
||||
@@ -244,13 +254,23 @@ AppStore.registeredCallback = function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.CLEAR_CONTROL_ERROR:
|
||||
controlError = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_CLOSE_DETAILS:
|
||||
selectedNodeId = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_NODE:
|
||||
selectedNodeId = payload.nodeId;
|
||||
if (payload.nodeId === selectedNodeId) {
|
||||
// clicking same node twice unsets the selection
|
||||
selectedNodeId = null;
|
||||
} else {
|
||||
selectedNodeId = payload.nodeId;
|
||||
}
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
@@ -268,6 +288,12 @@ AppStore.registeredCallback = function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.DO_CONTROL:
|
||||
controlPending = true;
|
||||
controlError = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.ENTER_EDGE:
|
||||
mouseOverEdgeId = payload.edgeId;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
@@ -302,6 +328,18 @@ AppStore.registeredCallback = function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.DO_CONTROL_ERROR:
|
||||
controlPending = false;
|
||||
controlError = payload.error;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.DO_CONTROL_SUCCESS:
|
||||
controlPending = false;
|
||||
controlError = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_ERROR:
|
||||
errorUrl = payload.errorUrl;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
|
||||
const debug = require('debug')('scope:web-api-utils');
|
||||
const reqwest = require('reqwest');
|
||||
|
||||
const AppActions = require('../actions/app-actions');
|
||||
|
||||
const WS_PROTO = window.WS_PROTO || (location.protocol === 'https:' ? 'wss' : 'ws');
|
||||
const WS_URL = window.WS_URL || WS_PROTO + '://' + location.host + location.pathname.replace(/\/$/, '');
|
||||
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = __WS_URL__ || wsProto + '://' + location.host + location.pathname.replace(/\/$/, '');
|
||||
|
||||
const apiTimerInterval = 10000;
|
||||
const reconnectTimerInterval = 5000;
|
||||
@@ -17,7 +18,7 @@ let currentUrl = null;
|
||||
let currentOptions = null;
|
||||
let topologyTimer = 0;
|
||||
let apiDetailsTimer = 0;
|
||||
|
||||
let controlErrorTimer = 0;
|
||||
|
||||
function buildOptionsQuery(options) {
|
||||
if (options) {
|
||||
@@ -35,7 +36,7 @@ function createWebsocket(topologyUrl, optionsQuery) {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
socket = new WebSocket(WS_URL + topologyUrl
|
||||
socket = new WebSocket(wsUrl + topologyUrl
|
||||
+ '/ws?t=' + updateFrequency + '&' + optionsQuery);
|
||||
|
||||
socket.onopen = function() {
|
||||
@@ -135,7 +136,28 @@ function getApiDetails() {
|
||||
});
|
||||
}
|
||||
|
||||
function doControl(probeId, nodeId, control) {
|
||||
clearTimeout(controlErrorTimer);
|
||||
const url = `api/control/${encodeURIComponent(probeId)}/`
|
||||
+ `${encodeURIComponent(nodeId)}/${control}`;
|
||||
reqwest({
|
||||
method: 'POST',
|
||||
url: url,
|
||||
success: function() {
|
||||
AppActions.receiveControlSuccess();
|
||||
},
|
||||
error: function(err) {
|
||||
AppActions.receiveControlError(err.response);
|
||||
controlErrorTimer = setTimeout(function() {
|
||||
AppActions.clearControlError();
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
doControl: doControl,
|
||||
|
||||
getNodeDetails: getNodeDetails,
|
||||
|
||||
getTopologies: getTopologies,
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.palable {
|
||||
transition: opacity .2s ease-in-out;
|
||||
transition: border-color .2s ease-in-out;
|
||||
}
|
||||
|
||||
.hideable {
|
||||
transition: opacity .5s ease-in-out;
|
||||
}
|
||||
@@ -313,24 +318,6 @@ h2 {
|
||||
top: 24px;
|
||||
bottom: 48px;
|
||||
width: 420px;
|
||||
|
||||
.details-tools-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details-tools {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 24px;
|
||||
color: @white;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-details {
|
||||
@@ -342,26 +329,79 @@ h2 {
|
||||
|
||||
padding: 24px 36px 24px 36px;
|
||||
|
||||
&-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&-label {
|
||||
color: white;
|
||||
margin: 0;
|
||||
width: 348px;
|
||||
padding-top: 0;
|
||||
|
||||
&-minor {
|
||||
width: 348px;
|
||||
flex: 1;
|
||||
font-size: 120%;
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
.details-tools {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
&-notavailable {
|
||||
background-color: @background-dark-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-controls {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
margin: -8px -8px 0 0;
|
||||
|
||||
.node-control-button {
|
||||
.palable;
|
||||
padding: 6px;
|
||||
margin-left: 2px;
|
||||
font-size: 110%;
|
||||
color: @white;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
border: 1px solid rgba(255, 255, 255, 0);
|
||||
border-radius: 10%;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
&-pending, &-pending:hover {
|
||||
opacity: 0.2;
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
.truncate;
|
||||
float: left;
|
||||
width: 66%;
|
||||
padding-top: 6px;
|
||||
text-align: left;
|
||||
color: @white;
|
||||
|
||||
&-icon {
|
||||
margin-right: 0.5em;
|
||||
animation: blinking 2.0s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
position: absolute;
|
||||
top: 115px;
|
||||
top: 128px;
|
||||
bottom: 0;
|
||||
padding: 0 36px 0 36px;
|
||||
overflow-y: scroll;
|
||||
@@ -449,6 +489,14 @@ h2 {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blinking {
|
||||
0%, 100% {
|
||||
opacity: 1.0;
|
||||
} 50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes status-loading {
|
||||
0%, 100% {
|
||||
background-color: darken(@background-color, 4%);
|
||||
|
||||
@@ -28,9 +28,11 @@ app.get('/app.js', function(req, res) {
|
||||
|
||||
// Proxy to backend
|
||||
|
||||
var BACKEND_HOST = process.env.BACKEND_HOST || 'localhost:4040';
|
||||
|
||||
// HACK need express-http-proxy, because proxy-middleware does
|
||||
// not proxy to /api itself
|
||||
app.use(httpProxy('localhost:4040', {
|
||||
app.use(httpProxy(BACKEND_HOST, {
|
||||
filter: function(req) {
|
||||
return url.parse(req.url).path === '/api';
|
||||
},
|
||||
@@ -39,7 +41,7 @@ app.use(httpProxy('localhost:4040', {
|
||||
}
|
||||
}));
|
||||
|
||||
app.use('/api', proxy('http://localhost:4040/api/'));
|
||||
app.use('/api', proxy('http://' + BACKEND_HOST + '/api/'));
|
||||
|
||||
// Serve index page
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ var path = require('path');
|
||||
*
|
||||
* For more information, see: http://webpack.github.io/docs/configuration.html
|
||||
*/
|
||||
|
||||
// Inject websocket url to dev backend
|
||||
var BACKEND_HOST = process.env.BACKEND_HOST || 'localhost:4040';
|
||||
var GLOBALS = {
|
||||
__WS_URL__: JSON.stringify('ws://' + BACKEND_HOST)
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
// Efficiently evaluate modules with source maps
|
||||
@@ -21,7 +28,7 @@ module.exports = {
|
||||
entry: [
|
||||
'webpack-dev-server/client?http://localhost:4041',
|
||||
'webpack/hot/only-dev-server',
|
||||
'./app/scripts/local'
|
||||
'./app/scripts/main'
|
||||
],
|
||||
|
||||
// This will not actually create a app.js file in ./build. It is used
|
||||
@@ -34,6 +41,7 @@ module.exports = {
|
||||
|
||||
// Necessary plugins for hot load
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(GLOBALS),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
],
|
||||
|
||||
@@ -2,6 +2,10 @@ var webpack = require('webpack');
|
||||
var autoprefixer = require('autoprefixer-core');
|
||||
var path = require('path');
|
||||
|
||||
var GLOBALS = {
|
||||
__WS_URL__: 'false'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the Webpack configuration file for production.
|
||||
*/
|
||||
@@ -56,9 +60,12 @@ module.exports = {
|
||||
extensions: ['', '.js', '.jsx']
|
||||
},
|
||||
|
||||
plugins: [new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})]
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(GLOBALS),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user