mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 10:11:03 +00:00
layout of sub-topologies
* uses injected static topology, see web-api-utils.js
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user