mirror of
https://github.com/yldio/copilot.git
synced 2024-12-30 22:00:06 +02:00
Add topology spike
This commit is contained in:
parent
0fc136c6ae
commit
32f784d461
15
spikes/graphs-topology/d3/.babelrc
Normal file
15
spikes/graphs-topology/d3/.babelrc
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"presets": [
|
||||
"react",
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
["transform-object-rest-spread", {
|
||||
"useBuiltIns": true
|
||||
}],
|
||||
"add-module-exports",
|
||||
"transform-es2015-modules-commonjs",
|
||||
"react-hot-loader/babel"
|
||||
],
|
||||
"sourceMaps": "both"
|
||||
}
|
3
spikes/graphs-topology/d3/.eslintignore
Normal file
3
spikes/graphs-topology/d3/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
coverage
|
||||
.nyc_output
|
29
spikes/graphs-topology/d3/.eslintrc
Normal file
29
spikes/graphs-topology/d3/.eslintrc
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "semistandard",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 7,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"babel",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"generator-star-spacing": 0,
|
||||
"babel/generator-star-spacing": 1,
|
||||
"space-before-function-paren": [2, "never"],
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/react-in-jsx-scope": 2,
|
||||
"object-curly-newline": ["error", {
|
||||
"minProperties": 1
|
||||
}],
|
||||
"sort-vars": ["error", {
|
||||
"ignoreCase": true
|
||||
}]
|
||||
}
|
||||
}
|
4
spikes/graphs-topology/d3/.gitignore
vendored
Normal file
4
spikes/graphs-topology/d3/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
coverage
|
||||
.nyc_output
|
||||
npm-debug.log
|
29
spikes/graphs-topology/d3/client/app.js
vendored
Normal file
29
spikes/graphs-topology/d3/client/app.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
const React = require('react');
|
||||
const Styled = require('styled-components');
|
||||
const ReactRouter = require('react-router');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const {
|
||||
Link
|
||||
} = ReactRouter;
|
||||
|
||||
const App = React.createClass({
|
||||
render: function() {
|
||||
const {
|
||||
children
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = App;
|
132
spikes/graphs-topology/d3/client/data.js
vendored
Normal file
132
spikes/graphs-topology/d3/client/data.js
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
module.exports = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'Nginx',
|
||||
attrs: {
|
||||
dcs: 1,
|
||||
instances: 2,
|
||||
healthy: true,
|
||||
},
|
||||
metrics: [
|
||||
{
|
||||
name: 'CPU',
|
||||
stat: '50%',
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
stat: '20%',
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
stat: '5.9KB/sec',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'WordPress',
|
||||
attrs: {
|
||||
dcs: 1,
|
||||
instances: 2,
|
||||
healthy: true,
|
||||
},
|
||||
metrics: [
|
||||
{
|
||||
name: 'CPU',
|
||||
stat: '50%',
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
stat: '20%',
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
stat: '5.9KB/sec',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'Memcached',
|
||||
attrs: {
|
||||
dcs: 1,
|
||||
instances: 2,
|
||||
healthy: true,
|
||||
},
|
||||
metrics: [
|
||||
{
|
||||
name: 'CPU',
|
||||
stat: '50%',
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
stat: '20%',
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
stat: '5.9KB/sec',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'Percona',
|
||||
attrs: {
|
||||
dcs: 1,
|
||||
instances: 2,
|
||||
healthy: true,
|
||||
},
|
||||
metrics: [
|
||||
{
|
||||
name: 'CPU',
|
||||
stat: '50%',
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
stat: '20%',
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
stat: '5.9KB/sec',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'NFS',
|
||||
attrs: {
|
||||
dcs: 1,
|
||||
instances: 2,
|
||||
healthy: true,
|
||||
},
|
||||
metrics: [
|
||||
{
|
||||
name: 'CPU',
|
||||
stat: '50%',
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
stat: '20%',
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
stat: '5.9KB/sec',
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
links: [
|
||||
{
|
||||
source: 'Nginx',
|
||||
target: 'WordPress',
|
||||
},
|
||||
{
|
||||
source: 'WordPress',
|
||||
target: 'Memcached',
|
||||
},
|
||||
{
|
||||
source: 'WordPress',
|
||||
target: 'NFS',
|
||||
},
|
||||
{
|
||||
source: 'WordPress',
|
||||
target: 'Percona',
|
||||
}
|
||||
]
|
||||
};
|
127
spikes/graphs-topology/d3/client/graph/graph-link.js
vendored
Normal file
127
spikes/graphs-topology/d3/client/graph/graph-link.js
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
const React = require('React');
|
||||
const Styled = require('styled-components');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const StyledLine = styled.line`
|
||||
stroke: #343434;
|
||||
stroke-width: 1.5;
|
||||
`;
|
||||
|
||||
const StyledCircle = styled.circle`
|
||||
stroke: #343434;
|
||||
fill: #464646;
|
||||
stroke-width: 1.5;
|
||||
`;
|
||||
|
||||
const StyledArrow = styled.line`
|
||||
stroke: white;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
`;
|
||||
|
||||
const getAngleFromPoints = (source, target) => {
|
||||
|
||||
const lineAngle = Math.atan2(target.y-source.y, target.x - source.x);
|
||||
const lineAngleDeg = lineAngle*180/Math.PI;
|
||||
const zeroToThreeSixty = lineAngleDeg < 0 ? 360 + lineAngleDeg : lineAngleDeg;
|
||||
|
||||
return zeroToThreeSixty;
|
||||
}
|
||||
|
||||
const getPosition = (angle, positions, position, noCorners=false) => {
|
||||
const positionIndex = noCorners ?
|
||||
Math.round(angle/90)*2 : Math.round(angle/45);
|
||||
const offsetPosition = positions[positionIndex];
|
||||
return {
|
||||
id: offsetPosition.id,
|
||||
x: position.x + offsetPosition.x,
|
||||
y: position.y + offsetPosition.y
|
||||
};
|
||||
}
|
||||
|
||||
class GraphLink extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
data,
|
||||
nodeSize,
|
||||
index
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
source,
|
||||
target
|
||||
} = data;
|
||||
|
||||
// actually, this will need to be got dynamically, in case them things are different sizes
|
||||
const {
|
||||
width,
|
||||
height
|
||||
} = nodeSize;
|
||||
|
||||
const halfWidth = width/2;
|
||||
const halfHeight = height/2;
|
||||
const halfCorner = 2;
|
||||
|
||||
const positions = getPositions(halfWidth, halfHeight, halfCorner);
|
||||
const sourceAngle = getAngleFromPoints(source, target);
|
||||
const sourcePosition = getPosition(sourceAngle, positions, source);
|
||||
const targetAngle = getAngleFromPoints(target, source);
|
||||
const targetPosition = getPosition(targetAngle, positions, target, true);
|
||||
const arrowAngle = getAngleFromPoints(sourcePosition, targetPosition);
|
||||
|
||||
return (
|
||||
<g>
|
||||
<StyledLine x1={sourcePosition.x} x2={targetPosition.x} y1={sourcePosition.y} y2={targetPosition.y} />
|
||||
<g transform={`translate(${targetPosition.x}, ${targetPosition.y}) rotate(${arrowAngle})`}>
|
||||
<StyledCircle cx={0} cy={0} r={9} />
|
||||
<StyledArrow x1={-1} x2={2} y1={-3} y2={0} />
|
||||
<StyledArrow x1={-1} x2={2} y1={3} y2={0} />
|
||||
</g>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GraphLink;
|
||||
|
||||
const getPositions = (halfWidth, halfHeight, halfCorner=0) => ([{
|
||||
id: 'r',
|
||||
x: halfWidth,
|
||||
y: 0
|
||||
}, {
|
||||
id: 'br',
|
||||
x: halfWidth - halfCorner,
|
||||
y: halfHeight - halfCorner
|
||||
}, {
|
||||
id: 'b',
|
||||
x: 0,
|
||||
y: halfHeight
|
||||
}, {
|
||||
id: 'bl',
|
||||
x: -halfWidth + halfCorner,
|
||||
y: halfHeight - halfCorner
|
||||
}, {
|
||||
id: 'l',
|
||||
x: -halfWidth,
|
||||
y: 0
|
||||
}, {
|
||||
id: 'tl',
|
||||
x: -halfWidth + halfCorner,
|
||||
y: -halfHeight + halfCorner
|
||||
}, {
|
||||
id: 't',
|
||||
x: 0,
|
||||
y: -halfHeight
|
||||
}, {
|
||||
id: 'tr',
|
||||
x: halfWidth - halfCorner,
|
||||
y: -halfHeight + halfCorner
|
||||
},{
|
||||
id: 'r',
|
||||
x: halfWidth,
|
||||
y: 0
|
||||
}]);
|
56
spikes/graphs-topology/d3/client/graph/graph-node-button.js
vendored
Normal file
56
spikes/graphs-topology/d3/client/graph/graph-node-button.js
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
const React = require('React');
|
||||
const Styled = require('styled-components');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const StyledButton = styled.rect`
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledButtonCircle = styled.circle`
|
||||
fill: white;
|
||||
`;
|
||||
|
||||
class GraphNodeButton extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
buttonRect,
|
||||
onButtonClick
|
||||
} = this.props;
|
||||
|
||||
const buttonCircleRadius = 2;
|
||||
const buttonCircleSpacing = 2;
|
||||
const buttonCircleY = (buttonRect.height - buttonCircleRadius*4 - buttonCircleSpacing*2)/2;
|
||||
const buttonCircles = [1,2,3].map((item, index) => (
|
||||
<StyledButtonCircle
|
||||
key={index}
|
||||
cx={buttonRect.width/2}
|
||||
cy={buttonCircleY + (buttonCircleRadius*2 + buttonCircleSpacing)*index}
|
||||
r={2}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<g transform={`translate(${buttonRect.x}, ${buttonRect.y})`}>
|
||||
<StyledButton onClick={onButtonClick} width={buttonRect.width} height={buttonRect.height}/>
|
||||
{buttonCircles}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GraphNodeButton.propTypes = {
|
||||
buttonRect: React.PropTypes.shape({
|
||||
x: React.PropTypes.number,
|
||||
y: React.PropTypes.number,
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number
|
||||
}).isRequired,
|
||||
onButtonClick: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
module.exports = GraphNodeButton;
|
47
spikes/graphs-topology/d3/client/graph/graph-node-metrics.js
vendored
Normal file
47
spikes/graphs-topology/d3/client/graph/graph-node-metrics.js
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
const React = require('React');
|
||||
const Styled = require('styled-components');
|
||||
const GraphNodeButton = require('./graph-node-button');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const StyledText = styled.text`
|
||||
fill: white;
|
||||
font-family: LibreFranklin;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
class GraphNodeMetrics extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
metrics,
|
||||
metricsPosition
|
||||
} = this.props;
|
||||
|
||||
const metricSpacing = 18;
|
||||
const metricsText = metrics.map((metric, index) => (
|
||||
<StyledText x={0} y={12 + metricSpacing*index}>{`${metric.name}: ${metric.stat}`}</StyledText>
|
||||
));
|
||||
|
||||
return (
|
||||
<g transform={`translate(${metricsPosition.x}, ${metricsPosition.y})`}>
|
||||
{metricsText}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GraphNodeMetrics.propTypes = {
|
||||
metrics: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
name: React.PropTypes.string,
|
||||
stat: React.PropTypes.string
|
||||
})),
|
||||
metricsPosition: React.PropTypes.shape({
|
||||
x: React.PropTypes.number,
|
||||
y: React.PropTypes.number
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = GraphNodeMetrics;
|
101
spikes/graphs-topology/d3/client/graph/graph-node.js
vendored
Normal file
101
spikes/graphs-topology/d3/client/graph/graph-node.js
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
const React = require('React');
|
||||
const Styled = require('styled-components');
|
||||
const GraphNodeButton = require('./graph-node-button');
|
||||
const GraphNodeMetrics = require('./graph-node-metrics');
|
||||
const HeartIcon =
|
||||
require(
|
||||
'!babel-loader!svg-react-loader!./icon-heart.svg?name=HeartIcon'
|
||||
);
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const StyledRect = styled.rect`
|
||||
stroke: #343434;
|
||||
fill: #464646;
|
||||
stroke-width: 1.5;
|
||||
rx: 4;
|
||||
ry: 4;
|
||||
`;
|
||||
|
||||
const StyledShadowRect = styled.rect`
|
||||
fill: #464646;
|
||||
opacity: 0.33;
|
||||
rx: 4;
|
||||
ry: 4;
|
||||
`;
|
||||
|
||||
const StyledLine = styled.line`
|
||||
stroke: #343434;
|
||||
stroke-width: 1.5;
|
||||
`;
|
||||
|
||||
const StyledText = styled.text`
|
||||
fill: white;
|
||||
font-family: LibreFranklin;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const StyledInfoText = styled.text`
|
||||
fill: white;
|
||||
font-family: LibreFranklin;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
class GraphNode extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
data,
|
||||
index,
|
||||
size
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
height
|
||||
} = size;
|
||||
|
||||
const halfWidth = width/2;
|
||||
const halfHeight = height/2;
|
||||
const lineY = 48 - halfHeight;
|
||||
const lineX = 140 - halfWidth;
|
||||
const buttonRect = {
|
||||
x: lineX,
|
||||
y: -halfHeight,
|
||||
width: width - 140,
|
||||
height: 48
|
||||
}
|
||||
|
||||
const onButtonClick = (evt) => {
|
||||
console.log('Rect clicked!!!');
|
||||
}
|
||||
|
||||
console.log('data = ', data);
|
||||
const paddingLeft = 18-halfWidth;
|
||||
const metricsPosition = {
|
||||
x: paddingLeft,
|
||||
y: 89 - halfHeight
|
||||
}
|
||||
|
||||
return (
|
||||
<g transform={`translate(${data.x}, ${data.y})`}>
|
||||
<StyledShadowRect x={-halfWidth} y={3-halfHeight} width={width} height={height} />
|
||||
<StyledRect x={-halfWidth} y={-halfHeight} width={width} height={height} />
|
||||
<StyledLine x1={-halfWidth} y1={lineY} x2={halfWidth} y2={lineY} />
|
||||
<StyledLine x1={lineX} y1={-halfHeight} x2={lineX} y2={lineY} />
|
||||
<StyledText x={paddingLeft} y={30 - halfHeight}>{data.id}</StyledText>
|
||||
<HeartIcon />
|
||||
<g>
|
||||
<path d='M0,10 C-5,-10 18,-10, 20,0 M20,0 C22,-10 45,-10, 40,10 L20,30 L0,10' />
|
||||
</g>
|
||||
<GraphNodeButton buttonRect={buttonRect} onButtonClick={onButtonClick} />
|
||||
<GraphNodeMetrics metrics={data.metrics} metricsPosition={metricsPosition} />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GraphNode;
|
20
spikes/graphs-topology/d3/client/graph/icon-data-centers.svg
Normal file
20
spikes/graphs-topology/d3/client/graph/icon-data-centers.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="9px" height="13px" viewBox="0 0 9 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon: data center</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Project:-topology-of-services" transform="translate(-575.000000, -487.000000)" fill="#FFFFFF">
|
||||
<g id="services-copy" transform="translate(213.000000, 426.000000)">
|
||||
<g id="service:-nginx" transform="translate(263.000000, 0.000000)">
|
||||
<g id="metric">
|
||||
<g id="data-centers-&-instanecs" transform="translate(18.000000, 59.000000)">
|
||||
<path d="M81,15 L90,15 L90,2 L81,2 L81,15 Z M83,13 L88,13 L88,4 L83,4 L83,13 Z M84,6 L87.001,6 L87.001,5 L84,5 L84,6 Z M84,8 L87.001,8 L87.001,7 L84,7 L84,8 Z M84,10 L87.001,10 L87.001,9 L84,9 L84,10 Z" id="icon:--data-center"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
18
spikes/graphs-topology/d3/client/graph/icon-heart.svg
Normal file
18
spikes/graphs-topology/d3/client/graph/icon-heart.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="80px" height="70px" viewBox="0 0 80 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon: health</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Project:-topology-of-services" transform="translate(0, 0)" fill="#FFFFFF">
|
||||
<g id="services-copy" transform="translate(0, 0)">
|
||||
<g id="service:-nginx" transform="translate(0, 0)">
|
||||
<g id="icon:-state" transform="translate(0, 0)">
|
||||
<path d="M9.47745233,6.60270759 L8.95496861,7.04565311 L8.51133742,6.60270759 C7.70841297,5.79909747 6.40563205,5.79909747 5.60270759,6.60270759 C4.79909747,7.40631772 4.79909747,8.70841297 5.60270759,9.5120231 L8.95496861,12.8642841 L12.3668833,9.5120231 C13.1698077,8.70841297 13.2301471,7.40631772 12.4265369,6.60270759 C11.6229268,5.79909747 10.2810625,5.79909747 9.47745233,6.60270759 Z" id="icon:-health"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
20
spikes/graphs-topology/d3/client/graph/icon-instances.svg
Normal file
20
spikes/graphs-topology/d3/client/graph/icon-instances.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="9px" viewBox="0 0 18 9" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon: instances</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Project:-topology-of-services" transform="translate(-494.000000, -489.000000)" fill="#FFFFFF">
|
||||
<g id="services-copy" transform="translate(213.000000, 426.000000)">
|
||||
<g id="service:-nginx" transform="translate(263.000000, 0.000000)">
|
||||
<g id="metric">
|
||||
<g id="data-centers-&-instanecs" transform="translate(18.000000, 59.000000)">
|
||||
<path d="M4.5,4 C2.015,4 0,6.015 0,8.5 C0,10.985 2.015,13 4.5,13 C6.985,13 9,10.985 9,8.5 C9,6.015 6.985,4 4.5,4 M13.0909091,4 C12.7145455,4 12.3512727,4.047 12,4.12 C14.184,4.576 15.8181818,6.359 15.8181818,8.5 C15.8181818,10.641 14.184,12.424 12,12.88 C12.3512727,12.953 12.7145455,13 13.0909091,13 C15.8018182,13 18,10.985 18,8.5 C18,6.015 15.8018182,4 13.0909091,4 M14,8.5 C14,10.985 11.8018182,13 9.09090909,13 C8.71454545,13 8.35127273,12.953 8,12.88 C10.184,12.424 11.8181818,10.641 11.8181818,8.5 C11.8181818,6.359 10.184,4.576 8,4.12 C8.35127273,4.047 8.71454545,4 9.09090909,4 C11.8018182,4 14,6.015 14,8.5" id="icon:-instances"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
137
spikes/graphs-topology/d3/client/graph/topology-graph.js
vendored
Normal file
137
spikes/graphs-topology/d3/client/graph/topology-graph.js
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
const React = require('react');
|
||||
const d3 = require('d3');
|
||||
const Styled = require('styled-components');
|
||||
const GraphNode = require('./graph-node');
|
||||
const GraphLink = require('./graph-link');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const StyledSvg = styled.svg`
|
||||
width: 1024px;
|
||||
height: 860px;
|
||||
border: 1px solid #ff0000;
|
||||
`;
|
||||
|
||||
const nodeSize = {
|
||||
width: 180,
|
||||
height: 156
|
||||
};
|
||||
|
||||
const mapData = (data, withIndex=false) => {
|
||||
return data.map((d, index) => {
|
||||
const r = {...d};
|
||||
if(withIndex) {
|
||||
r.index = index
|
||||
}
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
class TopologyGraph extends React.Component {
|
||||
|
||||
componentWillMount() {
|
||||
|
||||
const {
|
||||
data,
|
||||
nodeSize
|
||||
} = this.props;
|
||||
|
||||
this.setState(
|
||||
this.createSimulation(data, nodeSize)
|
||||
)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
data,
|
||||
nodeSize
|
||||
} = nextProps;
|
||||
|
||||
this.setState(
|
||||
this.createSimulation(data, nodeSize)
|
||||
)
|
||||
}
|
||||
|
||||
createSimulation(data, nodeSize) {
|
||||
|
||||
const dataNodes = mapData(data.nodes, true);
|
||||
const dataLinks = mapData(data.links);
|
||||
|
||||
const { width, height } = nodeSize;
|
||||
const nodeRadius = Math.round(Math.sqrt(width*width + height*height)/2);
|
||||
// const linkDistance = nodeRadius*2 + 20;
|
||||
console.log('nodeRadius = ', nodeRadius);
|
||||
// console.log('linkDistance = ', linkDistance);
|
||||
const simulation = d3.forceSimulation(dataNodes)
|
||||
.force('charge', d3.forceManyBody())
|
||||
.force('link', d3.forceLink(dataLinks)/*.distance(() => linkDistance)*/.id(d => d.id))
|
||||
.force('collide', d3.forceCollide(nodeRadius))
|
||||
.force('center', d3.forceCenter(1024/2, 860/2))
|
||||
.on('tick', () => {
|
||||
this.forceUpdate();
|
||||
})
|
||||
.on('end', () => {
|
||||
console.log('SIMULATION END');
|
||||
});
|
||||
|
||||
return {
|
||||
dataNodes,
|
||||
dataLinks,
|
||||
simulation
|
||||
};
|
||||
}
|
||||
|
||||
renderNodes(nodeSize) {
|
||||
return this.state.dataNodes.map((n, index) => (
|
||||
<GraphNode
|
||||
key={index}
|
||||
data={n}
|
||||
index={index}
|
||||
size={nodeSize}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
renderLinks(nodeSize) {
|
||||
return this.state.dataLinks.map((l, index) => (
|
||||
<GraphLink
|
||||
key={index}
|
||||
data={l}
|
||||
index={index}
|
||||
nodeSize={nodeSize}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
nodeSize
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<StyledSvg>
|
||||
<g>
|
||||
{this.renderNodes(nodeSize)}
|
||||
</g>
|
||||
<g>
|
||||
{this.renderLinks(nodeSize)}
|
||||
</g>
|
||||
</StyledSvg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
TopologyGraph.propTypes = {
|
||||
data: React.PropTypes.shape({
|
||||
nodes: React.PropTypes.array,
|
||||
links: React.PropTypes.array
|
||||
}),
|
||||
nodeSize: React.PropTypes.shape({
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = TopologyGraph;
|
46
spikes/graphs-topology/d3/client/index.js
vendored
Normal file
46
spikes/graphs-topology/d3/client/index.js
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
const ReactDOM = require('react-dom');
|
||||
const React = require('react');
|
||||
const store = require('./store')();
|
||||
const nes = require('nes/dist/client');
|
||||
|
||||
const {
|
||||
Client
|
||||
} = nes;
|
||||
|
||||
const client = new Client(`ws://${document.location.host}`);
|
||||
|
||||
client.connect((err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
console.log('connected');
|
||||
|
||||
client.subscribe('/stats/5', (update, flag) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_STATS',
|
||||
payload: update
|
||||
})
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
console.log('subscribed');
|
||||
});
|
||||
});
|
||||
|
||||
const render = () => {
|
||||
const Root = require('./root');
|
||||
|
||||
ReactDOM.render(
|
||||
<Root store={store} />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
};
|
||||
|
||||
render();
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./root', render);
|
||||
}
|
37
spikes/graphs-topology/d3/client/root.js
vendored
Normal file
37
spikes/graphs-topology/d3/client/root.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
const React = require('react');
|
||||
const ReactHotLoader = require('react-hot-loader');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactRouter = require('react-router');
|
||||
const App = require('./app');
|
||||
const Topology = require('./topology');
|
||||
|
||||
const {
|
||||
AppContainer
|
||||
} = ReactHotLoader;
|
||||
|
||||
const {
|
||||
Provider
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
Router,
|
||||
Route,
|
||||
IndexRoute,
|
||||
browserHistory
|
||||
} = ReactRouter;
|
||||
|
||||
module.exports = ({
|
||||
store
|
||||
}) => {
|
||||
return (
|
||||
<AppContainer>
|
||||
<Provider store={store}>
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/" component={App}>
|
||||
<IndexRoute component={Topology} />
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
26
spikes/graphs-topology/d3/client/store.js
vendored
Normal file
26
spikes/graphs-topology/d3/client/store.js
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
const takeRight = require('lodash.takeright');
|
||||
const redux = require('redux');
|
||||
|
||||
const {
|
||||
createStore,
|
||||
compose,
|
||||
combineReducers,
|
||||
applyMiddleware
|
||||
} = redux;
|
||||
|
||||
const reducer = (state, action) => {
|
||||
if (action.type !== 'UPDATE_STATS') {
|
||||
return state;
|
||||
}
|
||||
|
||||
const data = (state.data || []).concat([action.payload]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
data: takeRight(data, 50)
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = (state = Object.freeze({})) => {
|
||||
return createStore(reducer, state, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
|
||||
};
|
93
spikes/graphs-topology/d3/client/topology.js
vendored
Normal file
93
spikes/graphs-topology/d3/client/topology.js
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
const React = require('react');
|
||||
const Styled = require('styled-components');
|
||||
const TopologyGraph = require('./graph/topology-graph');
|
||||
const data = require('./data');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const StyledSvg = styled.svg`
|
||||
width: 1024px;
|
||||
height: 860px;
|
||||
`;
|
||||
|
||||
const StyledForm = styled.form`
|
||||
margin: 20px;
|
||||
`;
|
||||
|
||||
class Topology extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
//nasty
|
||||
this.state = {
|
||||
data: {
|
||||
nodes: [
|
||||
...data.nodes
|
||||
],
|
||||
links: [
|
||||
...data.links
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
} = this.props;
|
||||
|
||||
const nodeSize = {
|
||||
width: 180,
|
||||
height: 156
|
||||
};
|
||||
|
||||
const onSubmit = (evt) => {
|
||||
evt.preventDefault();
|
||||
console.log('submit ', evt.target.service.value);
|
||||
console.log('submit ', evt.target.link.value);
|
||||
const service = evt.target.service.value;
|
||||
const target = evt.target.link.value;
|
||||
const data = this.state.data;
|
||||
|
||||
this.setState({
|
||||
data: {
|
||||
nodes: [
|
||||
...data.nodes,
|
||||
{
|
||||
id: evt.target.service.value
|
||||
}
|
||||
],
|
||||
links: [
|
||||
...data.links,
|
||||
{
|
||||
source: service,
|
||||
target: target
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const options = data.nodes.map((n, index) => (
|
||||
<option key={index} value={n.id}>{n.id}</option>
|
||||
));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<StyledForm onSubmit={onSubmit}>
|
||||
<label>New service name</label>
|
||||
<input name='service' type='text' placeholder='Service name' />
|
||||
<label>Service to link to</label>
|
||||
<select name='link'>
|
||||
{ options }
|
||||
</select>
|
||||
<input type='submit' value='submit' />
|
||||
</StyledForm>
|
||||
<TopologyGraph data={this.state.data} nodeSize={nodeSize} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Topology;
|
59
spikes/graphs-topology/d3/package.json
Normal file
59
spikes/graphs-topology/d3/package.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "chartjs-graphing-spike",
|
||||
"private": true,
|
||||
"license": "private",
|
||||
"main": "server/index.js",
|
||||
"dependencies": {
|
||||
"autoprefixer": "^6.5.1",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.16.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.16.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-react-hmre": "^1.1.1",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"build-array": "^1.0.0",
|
||||
"component-emitter": "^1.2.1",
|
||||
"css-loader": "^0.25.0",
|
||||
"d3": "^4.5.0",
|
||||
"hapi": "^15.2.0",
|
||||
"hapi-webpack-dev-plugin": "^1.1.4",
|
||||
"inert": "^4.0.2",
|
||||
"lodash.takeright": "^4.1.1",
|
||||
"nes": "^6.3.1",
|
||||
"react": "^15.3.2",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-hot-loader": "^3.0.0-beta.6",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^3.0.0",
|
||||
"redux": "^3.6.0",
|
||||
"require-dir": "^0.3.1",
|
||||
"style-loader": "^0.13.1",
|
||||
"styled-components": "^1.2.1",
|
||||
"svg-react-loader": "^0.3.7",
|
||||
"transform-props-with": "^2.1.0",
|
||||
"validator": "^6.2.0",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-dev-server": "^1.16.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-register": "^6.16.3",
|
||||
"eslint": "^3.8.1",
|
||||
"eslint-config-semistandard": "^7.0.0",
|
||||
"eslint-config-standard": "^6.2.0",
|
||||
"eslint-plugin-babel": "^3.3.0",
|
||||
"eslint-plugin-promise": "^3.3.0",
|
||||
"eslint-plugin-react": "^6.4.1",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"json-loader": "^0.5.4"
|
||||
},
|
||||
"ava": {
|
||||
"require": [
|
||||
"babel-register"
|
||||
],
|
||||
"babel": "inherit"
|
||||
}
|
||||
}
|
16
spikes/graphs-topology/d3/readme.md
Normal file
16
spikes/graphs-topology/d3/readme.md
Normal file
@ -0,0 +1,16 @@
|
||||
# redux-form
|
||||
|
||||
## summary
|
||||
|
||||
- [x] form values in redux store
|
||||
- [x] clear / retain values in store
|
||||
- [x] pre-populate form
|
||||
- [x] validation on field / form level
|
||||
- [x] multi page form
|
||||
- [x] custom form components
|
||||
- [ ] requires updates to existing ui components as props to custom components are passed in the following format:
|
||||
|
||||
`"props": { "input": "value": "", "name": "", "onChange": "", "onFocus": "", ... }, "meta": { "valid": "", "error": "", ... }, "anyOtherPropsOnField": "", ... }`
|
||||
|
||||
- [ ] explore proxying props from Field to custom components from above shape to a flat form as expected by custom components
|
||||
- [ ] consider creating component that handles logic and display of label and error which would be reused by form components to avoid code duplication for this functionality
|
29
spikes/graphs-topology/d3/server/index.js
vendored
Normal file
29
spikes/graphs-topology/d3/server/index.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
const requireDir = require('require-dir');
|
||||
const plugins = require('./plugins');
|
||||
const routes = requireDir('./routes');
|
||||
const Hapi = require('hapi');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const server = new Hapi.Server();
|
||||
|
||||
server.connection({
|
||||
host: 'localhost',
|
||||
port: 8000
|
||||
});
|
||||
|
||||
server.register(plugins, (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
Object.keys(routes).forEach((name) => {
|
||||
routes[name](server);
|
||||
});
|
||||
|
||||
server.start((err) => {
|
||||
server.connections.forEach((conn) => {
|
||||
console.log(`started at: ${conn.info.uri}`);
|
||||
});
|
||||
});
|
||||
});
|
34
spikes/graphs-topology/d3/server/metric.js
vendored
Normal file
34
spikes/graphs-topology/d3/server/metric.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
const Emitter = require('component-emitter');
|
||||
|
||||
const cdm = {};
|
||||
|
||||
module.exports = (server) => ({
|
||||
on: (id) => {
|
||||
console.log('on', cdm[id]);
|
||||
if (cdm[id] && (cdm[id].sockets > 0)) {
|
||||
cdm[id].sockets +=1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let messageId = 0;
|
||||
const interval = setInterval(() => {
|
||||
console.log(`publishing /stats/${id}`);
|
||||
|
||||
server.publish(`/stats/${id}`, {
|
||||
when: new Date().getTime(),
|
||||
cpu: Math.random() * 100
|
||||
});
|
||||
}, 45);
|
||||
|
||||
cdm[id] = {
|
||||
interval,
|
||||
sockets: 1
|
||||
};
|
||||
},
|
||||
off: (id) => {
|
||||
if (!(cdm[id].sockets -= 1)) {
|
||||
clearInterval(cdm[id].interval);
|
||||
}
|
||||
}
|
||||
});
|
15
spikes/graphs-topology/d3/server/plugins.js
vendored
Normal file
15
spikes/graphs-topology/d3/server/plugins.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
|
||||
const cfg = require('../webpack.config.js');
|
||||
|
||||
module.exports = [
|
||||
require('inert'),
|
||||
require('nes'), {
|
||||
register: require('hapi-webpack-dev-plugin'),
|
||||
options: {
|
||||
compiler: webpack(cfg),
|
||||
devIndex: path.join(__dirname, '../static')
|
||||
}
|
||||
}
|
||||
];
|
11
spikes/graphs-topology/d3/server/routes/home.js
vendored
Normal file
11
spikes/graphs-topology/d3/server/routes/home.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (server) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/',
|
||||
handler: (request, reply) => {
|
||||
reply.file(path.join(__dirname, '../../static/index.html'));
|
||||
}
|
||||
});
|
||||
};
|
18
spikes/graphs-topology/d3/server/routes/metrics.js
vendored
Normal file
18
spikes/graphs-topology/d3/server/routes/metrics.js
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
const Metric = require('../metric');
|
||||
|
||||
module.exports = (server) => {
|
||||
const metric = Metric(server);
|
||||
|
||||
server.subscription('/stats/{id}', {
|
||||
onSubscribe: (socket, path, params, next) => {
|
||||
console.log('onSubscribe');
|
||||
metric.on(params.id);
|
||||
next();
|
||||
},
|
||||
onUnsubscribe: (socket, path, params, next) => {
|
||||
console.log('onUnsubscribe');
|
||||
metric.off(params.id);
|
||||
next();
|
||||
}
|
||||
});
|
||||
};
|
15
spikes/graphs-topology/d3/server/routes/static.js
vendored
Normal file
15
spikes/graphs-topology/d3/server/routes/static.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (server) => {
|
||||
// server.route({
|
||||
// method: 'GET',
|
||||
// path: '/{param*}',
|
||||
// handler: {
|
||||
// directory: {
|
||||
// path: path.join(__dirname, '../../static'),
|
||||
// redirectToSlash: true,
|
||||
// index: true
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
};
|
18
spikes/graphs-topology/d3/server/routes/version.js
vendored
Normal file
18
spikes/graphs-topology/d3/server/routes/version.js
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
const Pkg = require('../../package.json');
|
||||
|
||||
const internals = {
|
||||
response: {
|
||||
version: Pkg.version
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = (server) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/ops/version',
|
||||
config: {
|
||||
description: 'Returns the version of the server',
|
||||
handler: (request, reply) => reply(internals.response)
|
||||
}
|
||||
});
|
||||
};
|
12
spikes/graphs-topology/d3/static/index.html
Normal file
12
spikes/graphs-topology/d3/static/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang='en-US'>
|
||||
<head>
|
||||
<title>React Boilerplate</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://necolas.github.io/normalize.css/latest/normalize.css" />
|
||||
<link rel="stylesheet" type="text/css" href="https://rawgit.com/epochjs/epoch/master/dist/css/epoch.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
<script src='/static/bundle.js'></script>
|
||||
</body>
|
||||
</html>
|
55
spikes/graphs-topology/d3/webpack.config.js
Normal file
55
spikes/graphs-topology/d3/webpack.config.js
Normal file
@ -0,0 +1,55 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
|
||||
const config = {
|
||||
debug: true,
|
||||
devtool: 'source-map',
|
||||
context: path.join(__dirname, './client'),
|
||||
app: path.join(__dirname, './client/index.js'),
|
||||
entry: [
|
||||
'webpack-dev-server/client?http://localhost:8888',
|
||||
'webpack/hot/only-dev-server',
|
||||
'react-hot-loader/patch',
|
||||
'./index.js'
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, './static'),
|
||||
publicPath: '/static/',
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
],
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /js?$/,
|
||||
exclude: /node_modules/,
|
||||
include: [
|
||||
path.join(__dirname, './client')
|
||||
],
|
||||
loaders: ['babel']
|
||||
}, {
|
||||
test: /\.json?$/,
|
||||
exclude: /node_modules/,
|
||||
include: [
|
||||
path.join(__dirname, './client')
|
||||
],
|
||||
loaders: ['json']
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const devServer = {
|
||||
hot: true,
|
||||
compress: true,
|
||||
lazy: false,
|
||||
publicPath: config.output.publicPath,
|
||||
historyApiFallback: {
|
||||
index: './static/index.html'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Object.assign({
|
||||
devServer
|
||||
}, config);
|
4787
spikes/graphs-topology/d3/yarn.lock
Normal file
4787
spikes/graphs-topology/d3/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user