Merge pull request #797 from weaveworks/758-independent-node-controls

Fix node controls so they behave independently across nodes
This commit is contained in:
Simon
2016-01-19 14:41:37 +01:00
7 changed files with 46 additions and 43 deletions

View File

@@ -3,7 +3,7 @@ import debug from 'debug';
import AppDispatcher from '../dispatcher/app-dispatcher';
import ActionTypes from '../constants/action-types';
import { updateRoute } from '../utils/router-utils';
import { doControl as doControlRequest, getNodesDelta, getNodeDetails,
import { doControlRequest, getNodesDelta, getNodeDetails,
getTopologies, deletePipe } from '../utils/web-api-utils';
import AppStore from '../stores/app-store';
@@ -79,9 +79,10 @@ export function openWebsocket() {
});
}
export function clearControlError() {
export function clearControlError(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.CLEAR_CONTROL_ERROR
type: ActionTypes.CLEAR_CONTROL_ERROR,
nodeId: nodeId
});
}
@@ -91,15 +92,12 @@ export function closeWebsocket() {
});
}
export function doControl(probeId, nodeId, control) {
export function doControl(nodeId, control) {
AppDispatcher.dispatch({
type: ActionTypes.DO_CONTROL
type: ActionTypes.DO_CONTROL,
nodeId: nodeId
});
doControlRequest(
probeId,
nodeId,
control
);
doControlRequest(nodeId, control);
}
export function enterEdge(edgeId) {
@@ -144,16 +142,18 @@ export function leaveNode(nodeId) {
});
}
export function receiveControlError(err) {
export function receiveControlError(nodeId, err) {
AppDispatcher.dispatch({
type: ActionTypes.DO_CONTROL_ERROR,
nodeId: nodeId,
error: err
});
}
export function receiveControlSuccess() {
export function receiveControlSuccess(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.DO_CONTROL_SUCCESS
type: ActionTypes.DO_CONTROL_SUCCESS,
nodeId: nodeId
});
}
@@ -203,7 +203,7 @@ export function receiveControlPipeFromParams(pipeId, rawTty) {
}
export function receiveControlPipe(pipeId, nodeId, rawTty) {
if (nodeId.split(';').pop() !== AppStore.getSelectedNodeId()) {
if (nodeId !== AppStore.getSelectedNodeId()) {
log('Node was deselected before we could set up control!');
deletePipe(pipeId);
return;

View File

@@ -19,8 +19,7 @@ const ESC_KEY_CODE = 27;
function getStateFromStores() {
return {
activeTopologyOptions: AppStore.getActiveTopologyOptions(),
controlError: AppStore.getControlError(),
controlPending: AppStore.isControlPending(),
controlStatus: AppStore.getControlStatus(),
controlPipe: AppStore.getControlPipe(),
currentTopology: AppStore.getCurrentTopology(),
currentTopologyId: AppStore.getCurrentTopologyId(),
@@ -82,9 +81,8 @@ export default class App extends React.Component {
<div className="app">
{showingDebugToolbar() && <DebugToolbar />}
{showingDetails && <Details nodes={this.state.nodes}
controlError={this.state.controlError}
controlPending={this.state.controlPending}
nodeId={this.state.selectedNodeId}
controlStatus={this.state.controlStatus[this.state.selectedNodeId]}
details={this.state.nodeDetails} />}
{showingTerminal && <EmbeddedTerminal

View File

@@ -86,6 +86,7 @@ export default class NodeDetails extends React.Component {
renderDetails() {
const details = this.props.details;
const nodeColor = getNodeColorDark(details.rank, details.label_major);
const {error, pending} = (this.props.controlStatus || {});
const styles = {
controls: {
'backgroundColor': brightenColor(nodeColor)
@@ -109,8 +110,10 @@ export default class NodeDetails extends React.Component {
</div>
{details.controls && details.controls.length > 0 && <div className="node-details-controls-wrapper" style={styles.controls}>
<NodeDetailsControls controls={details.controls}
pending={this.props.controlPending} error={this.props.controlError} />
<NodeDetailsControls nodeId={this.props.nodeId}
controls={details.controls}
pending={pending}
error={error} />
</div>}
<div className="node-details-content">

View File

@@ -20,6 +20,6 @@ export default class NodeDetailsControlButton extends React.Component {
handleClick(ev) {
ev.preventDefault();
doControl(this.props.control.probeId, this.props.control.nodeId, this.props.control.id);
doControl(this.props.nodeId, this.props.control);
}
}

View File

@@ -16,7 +16,7 @@ export default class NodeDetailsControls extends React.Component {
<span className="node-details-controls-buttons">
{this.props.controls && this.props.controls.map(control => {
return (
<NodeDetailsControlButton control={control}
<NodeDetailsControlButton nodeId={this.props.nodeId} control={control}
pending={this.props.pending} key={control.id} />
);
})}

View File

@@ -7,6 +7,7 @@ import AppDispatcher from '../dispatcher/app-dispatcher';
import ActionTypes from '../constants/action-types';
import { EDGE_ID_SEPARATOR } from '../constants/naming';
const makeMap = Immutable.Map;
const makeOrderedMap = Immutable.OrderedMap;
const makeSet = Immutable.Set;
const log = debug('scope:app-store');
@@ -48,8 +49,7 @@ function makeNode(node) {
let topologyOptions = makeOrderedMap(); // topologyId -> options
let adjacentNodes = makeSet();
let controlError = null;
let controlPending = false;
let controlStatus = makeMap();
let currentTopology = null;
let currentTopologyId = 'containers';
let errorUrl = null;
@@ -143,8 +143,8 @@ export class AppStore extends Store {
return adjacentNodes;
}
getControlError() {
return controlError;
getControlStatus() {
return controlStatus.toJS();
}
getControlPipe() {
@@ -230,10 +230,6 @@ export class AppStore extends Store {
return version;
}
isControlPending() {
return controlPending;
}
isRouteSet() {
return routeSet;
}
@@ -269,7 +265,7 @@ export class AppStore extends Store {
break;
case ActionTypes.CLEAR_CONTROL_ERROR:
controlError = null;
controlStatus = controlStatus.removeIn([payload.nodeId, 'error']);
this.__emitChange();
break;
@@ -307,8 +303,10 @@ export class AppStore extends Store {
break;
case ActionTypes.DO_CONTROL:
controlPending = true;
controlError = null;
controlStatus = controlStatus.set(payload.nodeId, makeMap({
pending: true,
error: null
}));
this.__emitChange();
break;
@@ -346,14 +344,18 @@ export class AppStore extends Store {
break;
case ActionTypes.DO_CONTROL_ERROR:
controlPending = false;
controlError = payload.error;
controlStatus = controlStatus.set(payload.nodeId, makeMap({
pending: false,
error: payload.error
}));
this.__emitChange();
break;
case ActionTypes.DO_CONTROL_SUCCESS:
controlPending = false;
controlError = null;
controlStatus = controlStatus.set(payload.nodeId, makeMap({
pending: false,
error: null
}));
this.__emitChange();
break;

View File

@@ -155,23 +155,23 @@ export function getApiDetails() {
});
}
export function doControl(probeId, nodeId, control) {
export function doControlRequest(nodeId, control) {
clearTimeout(controlErrorTimer);
const url = `api/control/${encodeURIComponent(probeId)}/`
+ `${encodeURIComponent(nodeId)}/${control}`;
const url = `api/control/${encodeURIComponent(control.probeId)}/`
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
reqwest({
method: 'POST',
url: url,
success: function(res) {
receiveControlSuccess();
receiveControlSuccess(nodeId);
if (res && res.pipe) {
receiveControlPipe(res.pipe, nodeId, res.raw_tty, true);
}
},
error: function(err) {
receiveControlError(err.response);
receiveControlError(nodeId, err.response);
controlErrorTimer = setTimeout(function() {
clearControlError();
clearControlError(nodeId);
}, 10000);
}
});