Skip to content

Commit c4b86fa

Browse files
authored
Merge pull request #13 from HEET-Group/relationalGraph
Relational graph
2 parents fcb2850 + d717835 commit c4b86fa

File tree

18 files changed

+1873
-27
lines changed

18 files changed

+1873
-27
lines changed

app/containers/EventContainer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ const EventContainer: React.FC<EventContainerProps> = React.memo(props => {
160160

161161
<div>
162162
{/* <div id="grafana" onClick={() => { setIsGrafana(!isGrafana) }}>Grafana</div> */}
163+
<button>Inspect</button>
163164
{service.includes('kafkametrics') || service.includes('kubernetesmetrics') ? currChunk : []}
164165
{eventChartsArr.length > chunkSize && (
165166
<>

app/containers/GraphsContainer.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import * as DashboardContext from '../context/DashboardContext';
2222
import lightAndDark from '../components/Styling';
2323

2424
import '../stylesheets/GraphsContainer.scss';
25+
import { Link } from 'react-router-dom';
26+
import Inspect from './Inspect';
2527

2628
interface Params {
2729
app: any;
@@ -44,6 +46,7 @@ const GraphsContainer: React.FC = React.memo(props => {
4446
const [chart, setChart] = useState<string>('all');
4547
const [prevRoute, setPrevRoute] = useState<string>('');
4648
const { mode } = useContext(DashboardContext.DashboardContext);
49+
let [inspect, setInspect] = useState<boolean>(false);
4750

4851
useEffect(() => {
4952
const serviceArray = service.split(' ');
@@ -194,8 +197,21 @@ const GraphsContainer: React.FC = React.memo(props => {
194197
>
195198
Modify Metrics
196199
</button>
200+
{/* <Link className="sidebar-link" to="/Inspect" id="Inspect" >
201+
<SettingsIcon
202+
style={{
203+
WebkitBoxSizing: 'content-box',
204+
boxShadow: 'none',
205+
width: '35px',
206+
height: '35px',
207+
}}
208+
/>
209+
&emsp;Inspect
210+
</Link> */}
211+
<button onClick={() => { setInspect(!inspect) }}>Inspect</button>
197212
</nav>
198213
<Header app={app} service={service} live={live} setLive={setLive} />
214+
{inspect && <Inspect />}
199215
<div className="graphs-container">
200216
{chart === 'communications' ? (
201217
<div className="graphs">

app/containers/Inspect.jsx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useEffect } from 'react';
2+
import * as d3 from 'd3'; // Import D3 libraries here
3+
import ForceGraph3D from '3d-force-graph'; // Import 3d-force-graph library here
4+
import dat from 'dat.gui'; // Import dat.gui library here
5+
import '../stylesheets/Inspect.scss';
6+
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
7+
8+
9+
10+
// currently, Insepect only works statically, but it should be possible to make it dynamic. To do so, we need to make get kubernetes resources information from the backend, and then pass it to the graph.
11+
// It will be great if future iteration group can make it dynamic.
12+
13+
const Inspect = () => {
14+
useEffect(() => {
15+
// controls
16+
const controls = { 'DAG Orientation': 'td' };
17+
const gui = new dat.GUI();
18+
gui.add(controls, 'DAG Orientation', ['td', 'bu', 'lr', 'rl', 'zout', 'zin', 'radialout', 'radialin', null])
19+
.onChange(orientation => graph && graph.dagMode(orientation));
20+
21+
// graph config
22+
const NODE_REL_SIZE = 7;
23+
const graph = ForceGraph3D({
24+
extraRenderers: [new CSS2DRenderer()]
25+
})
26+
.width(1000)
27+
.height(500)
28+
.dagMode('td')
29+
.dagLevelDistance(100)
30+
.backgroundColor('#101020')
31+
.linkColor(() => 'rgba(255,255,255,0.2)')
32+
.nodeRelSize(NODE_REL_SIZE)
33+
.nodeId('path')
34+
.nodeVal('size')
35+
.nodeLabel('path')
36+
.nodeAutoColorBy('module')
37+
.nodeOpacity(0.9)
38+
.nodeLabel('path')
39+
.linkDirectionalParticles(2)
40+
.linkDirectionalParticleWidth(1)
41+
.linkDirectionalParticleSpeed(0.006)
42+
.linkWidth(5)
43+
.d3Force('collision', d3.forceCollide(node => Math.cbrt(node.size) * NODE_REL_SIZE))
44+
.d3VelocityDecay(0.3);
45+
46+
// Decrease repel intensity
47+
graph.d3Force('charge').strength(-500);
48+
49+
50+
51+
fetch('http://localhost:1111/api/kuberData')
52+
.then(r => r.text())
53+
.then(d3.csvParse)
54+
.then(data => {
55+
console.log(data)
56+
const nodes = [], links = [];
57+
data.forEach(({ size, path }) => {
58+
const levels = path.split('/'),
59+
level = levels.length - 1,
60+
module = level > 0 ? levels[1] : null,
61+
leaf = levels.pop(),
62+
parent = levels.join('/');
63+
64+
const node = {
65+
path,
66+
leaf,
67+
module,
68+
size: +size || 20,
69+
level
70+
};
71+
72+
nodes.push(node);
73+
74+
if (parent) {
75+
links.push({ source: parent, target: path, targetNode: node });
76+
}
77+
});
78+
79+
80+
graph(document.getElementById('graph'))
81+
.graphData({ nodes, links })
82+
.onNodeDragEnd(node => {
83+
node.fx = node.x;
84+
node.fy = node.y;
85+
node.fz = node.z;
86+
})
87+
.onNodeClick(node => {
88+
// Aim at node from outside it
89+
const distance = 40;
90+
const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
91+
92+
const newPos = node.x || node.y || node.z
93+
? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }
94+
: { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0)
95+
96+
graph.cameraPosition(
97+
newPos, // new position
98+
node, // lookAt ({ x, y, z })
99+
3000 // ms transition duration
100+
);
101+
102+
// Show label
103+
const nodeEl = document.createElement('div');
104+
nodeEl.innerHTML = node.path;
105+
nodeEl.style.color = node.color;
106+
nodeEl.className = 'node-label';
107+
return new CSS2DObject(nodeEl);
108+
})
109+
.nodeThreeObject((node) => {
110+
const nodeEl = document.createElement('div');
111+
nodeEl.textContent = node.path.split('/').pop().replace(/:.*/, '');
112+
nodeEl.style.color = node.color;
113+
nodeEl.className = 'node-label';
114+
return new CSS2DObject(nodeEl);
115+
})
116+
.nodeThreeObjectExtend(true);
117+
118+
119+
});
120+
}, []);
121+
122+
123+
return <div id="Infrastructure">
124+
<h2>Infrastructure</h2>
125+
<div id="graph" />;
126+
</div>
127+
// <div id="graph" />;
128+
};
129+
130+
export default Inspect;

app/containers/Resource.html

Whitespace-only changes.

app/stylesheets/Inspect.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
h2 {
2+
display: flex;
3+
justify-content: center;
4+
align-items: center;
5+
}
6+
7+
#graph {
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
}
12+
13+
.node-label {
14+
font-size: 12px;
15+
padding: 1px 4px;
16+
border-radius: 4px;
17+
background-color: rgba(0, 0, 0, 0.5);
18+
user-select: none;
19+
}

chronos_npm_package/server/data.csv

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
size,path
2+
,default
3+
,default/service-backend
4+
,default/service-backend/pod
5+
,default/service-frontend
6+
,default/service-frontend/pod1
7+
,default/service-frontend/pod2
8+
,default/service-grafana
9+
,default/service-grafana/pod
10+
,default/service-kubernetes
11+
,default/service-kubernetes/pod
12+
,default/service-prometheus
13+
,default/service-prometheus/pod
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { Parser } = require('@json2csv/plainjs');
2+
const fs = require('fs');
3+
const { convertArrayToCSV } = require('convert-array-to-csv');
4+
const converter = require('convert-array-to-csv');
5+
6+
const fileControllers = {};
7+
8+
fileControllers.saveCSV = (req, res, next) => {
9+
console.log('saveCSV endpoint hit');
10+
const data = res.locals.data;
11+
12+
try {
13+
// const parser = new Parser();
14+
// const csv = parser.parse(data);
15+
const csv = convertArrayToCSV(data);
16+
console.log(csv);
17+
18+
fs.writeFile('./output.csv', csv, (err) => {
19+
if (err) {
20+
console.log('error: ', err);
21+
return res.status(500).send('Error');
22+
}
23+
console.log('data saved');
24+
next();
25+
26+
})
27+
} catch (err) {
28+
console.log('error: ', err);
29+
return next(err);
30+
};
31+
}
32+
33+
34+
35+
module.exports = fileControllers;

0 commit comments

Comments
 (0)