From abcb94b1f1bc2bad76dc6aefc1f635ef250c4fdc Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 3 Nov 2015 10:30:00 +0000 Subject: [PATCH] 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 --- client/.eslintrc | 3 + client/app/scripts/actions/app-actions.js | 38 +++++++- client/app/scripts/charts/node.js | 9 +- client/app/scripts/charts/nodes-chart.js | 4 +- .../components/__tests__/node-details-test.js | 2 + client/app/scripts/components/app.js | 4 + client/app/scripts/components/details.js | 14 +-- .../scripts/components/node-control-button.js | 23 +++++ .../components/node-details-controls.js | 25 ++++++ client/app/scripts/components/node-details.js | 3 + client/app/scripts/components/nodes.js | 6 -- client/app/scripts/constants/action-types.js | 4 + .../app/scripts/dispatcher/app-dispatcher.js | 2 +- client/app/scripts/local.js | 8 -- client/app/scripts/stores/app-store.js | 40 ++++++++- client/app/scripts/utils/web-api-utils.js | 30 ++++++- client/app/styles/main.less | 88 ++++++++++++++----- client/server.js | 6 +- client/webpack.local.config.js | 10 ++- client/webpack.production.config.js | 17 ++-- 20 files changed, 267 insertions(+), 69 deletions(-) create mode 100644 client/app/scripts/components/node-control-button.js create mode 100644 client/app/scripts/components/node-details-controls.js delete mode 100644 client/app/scripts/local.js diff --git a/client/.eslintrc b/client/.eslintrc index 4dce480aff..d5928ee0c6 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -26,6 +26,9 @@ "templateStrings": true, "jsx": true }, + "globals": { + __WS_URL__: false + }, "rules": { /** * Strict mode diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 46931a6d3c..3950ec5253 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -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 }); }, diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index 6fd29dfca3..a0dc57af97 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -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 ( + onClick={onMouseClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}> {props.highlighted && } @@ -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); }, diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index 1d0245e28e..f2d3a2dd94 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -218,7 +218,7 @@ const NodesChart = React.createClass({
{errorEmpty} {errorMaxNodesExceeded} - + {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 diff --git a/client/app/scripts/components/__tests__/node-details-test.js b/client/app/scripts/components/__tests__/node-details-test.js index 655751aa4b..d4b17f41d9 100644 --- a/client/app/scripts/components/__tests__/node-details-test.js +++ b/client/app/scripts/components/__tests__/node-details-test.js @@ -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; diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 1b30c91d0c..aaeabc08e1 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -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 (
{showingDetails &&
} diff --git a/client/app/scripts/components/details.js b/client/app/scripts/components/details.js index 811b7b6426..459e6a66fb 100644 --- a/client/app/scripts/components/details.js +++ b/client/app/scripts/components/details.js @@ -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 (
-
-
- -
-
- +
); - }, - - handleClickClose: function(ev) { - ev.preventDefault(); - AppActions.clickCloseDetails(); } }); diff --git a/client/app/scripts/components/node-control-button.js b/client/app/scripts/components/node-control-button.js new file mode 100644 index 0000000000..70f8f10a50 --- /dev/null +++ b/client/app/scripts/components/node-control-button.js @@ -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 ( + + ); + }, + + handleClick: function(ev) { + ev.preventDefault(); + AppActions.doControl(this.props.control.probeId, this.props.control.nodeId, this.props.control.id); + } + +}); + +module.exports = NodeControlButton; diff --git a/client/app/scripts/components/node-details-controls.js b/client/app/scripts/components/node-details-controls.js new file mode 100644 index 0000000000..7123a7b5a2 --- /dev/null +++ b/client/app/scripts/components/node-details-controls.js @@ -0,0 +1,25 @@ +const React = require('react'); + +const NodeControlButton = require('./node-control-button'); + +const NodeDetailsControls = React.createClass({ + + render: function() { + return ( +
+ {this.props.error &&
+ + {this.props.error} +
} + {this.props.controls && this.props.controls.map(control => { + return ( + + ); + })} +
+ ); + } + +}); + +module.exports = NodeDetailsControls; diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js index 235a4f34d8..2ee7ec1678 100644 --- a/client/app/scripts/components/node-details.js +++ b/client/app/scripts/components/node-details.js @@ -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 (
+

{details.label_major}

diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index c6737d4e54..3e913aae74 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -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 (