layout of sub-topologies

* uses injected static topology, see web-api-utils.js
This commit is contained in:
David Kaltschmidt
2015-06-12 11:37:49 +02:00
parent 3f27086fd0
commit 816e1e9e99
6 changed files with 124 additions and 62 deletions

View File

@@ -2,7 +2,6 @@ const React = require('react');
const Logo = require('./logo');
const AppStore = require('../stores/app-store');
const Groupings = require('./groupings.js');
const Status = require('./status.js');
const Topologies = require('./topologies.js');
const WebapiUtils = require('../utils/web-api-utils');
@@ -67,7 +66,6 @@ const App = React.createClass({
<div className="header">
<Logo />
<Topologies topologies={this.state.topologies} currentTopology={this.state.currentTopology} />
<Groupings active={this.state.currentGrouping} currentTopology={this.state.currentTopology} />
<Status connectionState={this.state.connectionState} />
</div>

View File

@@ -11,29 +11,46 @@ const Topologies = React.createClass({
AppActions.clickTopology(ev.currentTarget.getAttribute('rel'));
},
renderSubTopology: function(subTopology) {
const isActive = subTopology.name === this.props.currentTopology.name;
const topologyId = AppStore.getTopologyIdForUrl(subTopology.url);
const className = isActive ? 'topologies-sub-item topologies-sub-item-active' : 'topologies-sub-item';
return (
<div className={className} key={topologyId} rel={topologyId} onClick={this.onTopologyClick}>
<div className="topologies-sub-item-label">
{subTopology.name}
</div>
</div>
);
},
renderTopology: function(topology) {
const isActive = topology.name === this.props.currentTopology.name;
const className = isActive ? 'topologies-item topologies-item-active' : 'topologies-item';
const className = isActive ? 'topologies-item-main topologies-item-main-active' : 'topologies-item-main';
const topologyId = AppStore.getTopologyIdForUrl(topology.url);
const title = ['Topology: ' + topology.name,
'Nodes: ' + topology.stats.node_count,
'Connections: ' + topology.stats.node_count].join('\n');
return (
<div className={className} key={topologyId} rel={topologyId} onClick={this.onTopologyClick}>
<div title={title}>
<div className="topologies-item" key={topologyId}>
<div className={className} title={title} rel={topologyId} onClick={this.onTopologyClick}>
<div className="topologies-item-label">
{topology.name}
</div>
</div>
<div className="topologies-sub">
{topology.sub_topologies && topology.sub_topologies.map(this.renderSubTopology)}
</div>
</div>
);
},
render: function() {
const topologies = _.sortBy(this.props.topologies, function(topology) {
return topology.name;
});
return topology.name;
});
return (
<div className="topologies">

View File

@@ -16,6 +16,11 @@ describe('AppStore', function() {
nodeId: 'n1'
};
const ClickSubTopologyAction = {
type: ActionTypes.CLICK_TOPOLOGY,
topologyId: 'topo1-grouped'
};
const ClickTopologyAction = {
type: ActionTypes.CLICK_TOPOLOGY,
topologyId: 'topo1'
@@ -45,8 +50,11 @@ describe('AppStore', function() {
type: ActionTypes.RECEIVE_TOPOLOGIES,
topologies: [{
url: '/topo1',
grouped_url: '/topo1grouped',
name: 'Topo1'
name: 'Topo1',
sub_topologies: [{
url: '/topo1-grouped',
name: 'topo 1 grouped'
}]
}]
};
@@ -77,14 +85,13 @@ describe('AppStore', function() {
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1');
});
it('get grouped topology', function() {
registeredCallback(ClickTopologyAction);
it('get sub-topology', function() {
registeredCallback(ReceiveTopologiesAction);
registeredCallback(ClickGroupingAction);
registeredCallback(ClickSubTopologyAction);
expect(AppStore.getTopologies().length).toBe(1);
expect(AppStore.getCurrentTopology().name).toBe('Topo1');
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1grouped');
expect(AppStore.getCurrentTopology().name).toBe('topo 1 grouped');
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1-grouped');
});
// browsing
@@ -110,14 +117,14 @@ describe('AppStore', function() {
registeredCallback(ReceiveNodesDeltaAction);
// TODO clear AppStore cache
expect(AppStore.getAppState())
.toEqual({"topologyId":"topo1","grouping":"grouped","selectedNodeId": null});
.toEqual({"topologyId":"topo1-grouped","grouping":"none","selectedNodeId": null});
registeredCallback(ClickNodeAction);
expect(AppStore.getAppState())
.toEqual({"topologyId":"topo1","grouping":"grouped","selectedNodeId": 'n1'});
.toEqual({"topologyId":"topo1-grouped","grouping":"none","selectedNodeId": 'n1'});
// go back in browsing
RouteAction.state = {"topologyId":"topo1","grouping":"grouped","selectedNodeId": null};
RouteAction.state = {"topologyId":"topo1-grouped","grouping":"none","selectedNodeId": null};
registeredCallback(RouteAction);
expect(AppStore.getSelectedNodeId()).toBe(null);
expect(AppStore.getNodes()).toEqual(NODE_SET);

View File

@@ -9,8 +9,22 @@ const Naming = require('../constants/naming');
// Helpers
function isUrlForTopologyId(url, topologyId) {
return _.endsWith(url, topologyId);
function findCurrentTopology(subTree, topologyId) {
let foundTopology;
_.each(subTree, function(topology) {
if (_.endsWith(topology.url, topologyId)) {
foundTopology = topology;
}
if (!foundTopology) {
foundTopology = findCurrentTopology(topology.sub_topologies, topologyId);
}
if (foundTopology) {
return false;
}
});
return foundTopology;
}
// Initial values
@@ -45,16 +59,14 @@ const AppStore = assign({}, EventEmitter.prototype, {
},
getCurrentTopology: function() {
return _.find(topologies, function(topology) {
return isUrlForTopologyId(topology.url, currentTopologyId);
});
return findCurrentTopology(topologies, currentTopologyId);
},
getCurrentTopologyUrl: function() {
const topology = this.getCurrentTopology();
if (topology) {
return topology.grouped_url && currentGrouping === 'grouped' ? topology.grouped_url : topology.url;
return topology.url;
}
},

View File

@@ -39,10 +39,50 @@ function createWebsocket(topologyUrl) {
currentUrl = topologyUrl;
}
const TOPOLOGIES = [
{
'name': 'Applications',
'url': '/api/topology/applications',
'stats': {
'node_count': 12,
'nonpseudo_node_count': 10,
'edge_count': 13
},
'sub_topologies': [
{
'name': 'by name',
'url': '/api/topology/applications-grouped'
}
]
},
{
'name': 'Containers',
'url': '/api/topology/containers',
'grouped_url': '/api/topology/containers-grouped',
'stats': {
'node_count': 2,
'nonpseudo_node_count': 1,
'edge_count': 2
}
},
{
'name': 'Hosts',
'url': '/api/topology/hosts',
'stats': {
'node_count': 2,
'nonpseudo_node_count': 1,
'edge_count': 2
}
}
];
function getTopologies() {
clearTimeout(topologyTimer);
reqwest('/api/topology', function(res) {
AppActions.receiveTopologies(res);
reqwest('/api/topology', function() {
// injecting static topos
AppActions.receiveTopologies(TOPOLOGIES);
topologyTimer = setTimeout(getTopologies, 10000);
});
}

View File

@@ -81,59 +81,47 @@ body {
float: left;
}
.topologies,
.groupings {
float: left;
margin-top: 7px;
margin-left: 48px;
}
.topologies {
&-icon {
font-size: 12px;
color: @text-secondary-color;
margin-right: 16px;
position: relative;
top: -1px;
}
float: left;
margin: 4px 64px;
.topologies-item {
margin: 8px 16px 6px 0;
cursor: pointer;
display: inline-block;
margin: 0px 16px;
float: left;
&-label {
color: @text-secondary-color;
font-size: 15px;
font-size: 16px;
text-transform: uppercase;
}
}
.topologies-sub {
margin-top: 4px;
&-item {
&-label {
color: @text-secondary-color;
font-size: 12px;
text-transform: uppercase;
}
}
}
.topologies-item-main,
.topologies-sub-item {
cursor: pointer;
&-active, &:hover {
.topologies-sub-item-label,
.topologies-item-label {
color: @text-color;
}
}
}
}
.groupings {
&-item {
font-size: 15px;
margin: 8px 12px 6px 0;
cursor: pointer;
display: inline-block;
color: @text-tertiary-color;
&-disabled {
color: @text-tertiary-color;
cursor: default;
}
&-default:hover,
&-active {
color: @text-color;
}
}
}
.status {