mirror of
https://github.com/Noratrieb/haesli.git
synced 2026-01-14 11:45:02 +01:00
graphing around
This commit is contained in:
parent
9855defeae
commit
881cabe2f0
6 changed files with 199 additions and 115 deletions
|
|
@ -17,5 +17,5 @@ td {
|
|||
}
|
||||
|
||||
.graph {
|
||||
height: 50vh;
|
||||
height: 70vh;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
import React from 'react';
|
||||
import { GraphView } from 'react-digraph';
|
||||
import { Binding, Data, Exchange } from '../types';
|
||||
|
||||
const sample = {
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Exchange A',
|
||||
type: 'empty',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Queue A',
|
||||
type: 'empty',
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
source: 1,
|
||||
target: 2,
|
||||
type: 'emptyEdge',
|
||||
},
|
||||
{
|
||||
source: 2,
|
||||
target: 4,
|
||||
type: 'emptyEdge',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const graphConfig = {
|
||||
nodeTypes: {
|
||||
exchange: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Exchange',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape: (
|
||||
<symbol viewBox="0 0 100 100" id="empty" key="0">
|
||||
<circle cx="50" cy="50" r="45" />
|
||||
</symbol>
|
||||
),
|
||||
},
|
||||
queue: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Queue',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape: (
|
||||
<symbol viewBox="0 0 100 100" id="empty" key="0">
|
||||
<circle cx="50" cy="50" r="45" />
|
||||
</symbol>
|
||||
),
|
||||
},
|
||||
},
|
||||
nodeSubtypes: {},
|
||||
edgeTypes: {},
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data: Data;
|
||||
};
|
||||
|
||||
const SPACE = 200;
|
||||
|
||||
const EntityGraph = ({ data }: Props) => {
|
||||
const queueTotal = (data.queues.length * SPACE) / 2;
|
||||
const queues = data.queues.map((q, i) => ({
|
||||
id: q.name,
|
||||
title: q.name,
|
||||
y: 300,
|
||||
x: i * SPACE - queueTotal,
|
||||
type: 'queue',
|
||||
}));
|
||||
const exchTotal = (data.exchanges.length * SPACE) / 2;
|
||||
const exchanges = data.exchanges.map((e, i) => ({
|
||||
id: e.name,
|
||||
title: e.name,
|
||||
y: 0,
|
||||
x: i * SPACE - exchTotal,
|
||||
type: 'exchange',
|
||||
}));
|
||||
|
||||
const nodes = [...queues, ...exchanges];
|
||||
|
||||
const edges = data.exchanges
|
||||
.flatMap((e) => e.bindings.map((b) => [b, e] as [Binding, Exchange]))
|
||||
.map(([b, e]) => ({
|
||||
source: b.queue,
|
||||
target: e.name,
|
||||
label_to: `'${b.routingKey}'`,
|
||||
type: 'emptyEdge',
|
||||
}));
|
||||
|
||||
const nodeTypes = graphConfig.nodeTypes;
|
||||
const nodeSubtypes = graphConfig.nodeSubtypes;
|
||||
const edgeTypes = graphConfig.edgeTypes;
|
||||
|
||||
return (
|
||||
<div className="graph">
|
||||
<GraphView
|
||||
nodeKey="id"
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
nodeSubtypes={nodeSubtypes}
|
||||
edgeTypes={edgeTypes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityGraph;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import Table from './table';
|
||||
import type { Data } from '../types';
|
||||
import EntityGraph from './EntityGraph';
|
||||
import EntityGraph from './entity-graph';
|
||||
|
||||
const fetchData = async (prefix: string): Promise<Data> => {
|
||||
const url = `${prefix}api/data`;
|
||||
|
|
@ -74,12 +74,19 @@ const DataPage: FC<Props> = ({ prefix }) => {
|
|||
<h2>Queues</h2>
|
||||
{data ? (
|
||||
<Table
|
||||
headers={['Queue ID', 'Name', 'Durable', 'Message Count']}
|
||||
headers={[
|
||||
'Queue ID',
|
||||
'Name',
|
||||
'Durable',
|
||||
'Message Count',
|
||||
'Consumer Count',
|
||||
]}
|
||||
rows={data.queues.map((queue) => [
|
||||
queue.id,
|
||||
queue.name,
|
||||
queue.durable ? 'Yes' : 'No',
|
||||
queue.messages,
|
||||
queue.consumers.length,
|
||||
])}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
167
haesli_dashboard/frontend/src/components/entity-graph.tsx
Normal file
167
haesli_dashboard/frontend/src/components/entity-graph.tsx
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import React from 'react';
|
||||
import { GraphView } from 'react-digraph';
|
||||
import { Binding, Data, Exchange } from '../types';
|
||||
|
||||
const shape = (
|
||||
<symbol viewBox="0 0 100 100" id="empty" key="0">
|
||||
<circle cx="50" cy="50" r="30" />
|
||||
</symbol>
|
||||
);
|
||||
|
||||
const graphConfig = {
|
||||
nodeTypes: {
|
||||
exchange: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Exchange',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape,
|
||||
},
|
||||
queue: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Queue',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape,
|
||||
},
|
||||
consumer: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Consumer',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape,
|
||||
},
|
||||
channel: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Channel',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape,
|
||||
},
|
||||
connection: {
|
||||
// required to show empty nodes
|
||||
typeText: 'Connection',
|
||||
shapeId: '#empty', // relates to the type property of a node
|
||||
shape,
|
||||
},
|
||||
},
|
||||
nodeSubtypes: {},
|
||||
edgeTypes: {},
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data: Data;
|
||||
};
|
||||
|
||||
const SPACE_H = 120;
|
||||
const SPACE_V = 150;
|
||||
|
||||
const EntityGraph = ({ data }: Props) => {
|
||||
const exchTotal = (data.exchanges.length * SPACE_H) / 2;
|
||||
const exchanges = data.exchanges.map((e, i) => ({
|
||||
id: e.name,
|
||||
title: e.name,
|
||||
y: 0,
|
||||
x: i * SPACE_H - exchTotal,
|
||||
type: 'exchange',
|
||||
}));
|
||||
|
||||
const queueTotal = (data.queues.length * SPACE_H) / 2;
|
||||
const queues = data.queues.map((q, i) => ({
|
||||
id: q.name,
|
||||
title: q.name,
|
||||
y: SPACE_V,
|
||||
x: i * SPACE_H - queueTotal,
|
||||
type: 'queue',
|
||||
}));
|
||||
|
||||
const consumersData = data.queues.flatMap((q) =>
|
||||
q.consumers.map((c) => [q, c] as const)
|
||||
);
|
||||
const consumerTotal = (consumersData.length * SPACE_H) / 2;
|
||||
const consumers = consumersData.map(([q, c], i) => ({
|
||||
id: c.tag,
|
||||
title: c.tag,
|
||||
y: SPACE_V * 2,
|
||||
x: i * SPACE_H - consumerTotal,
|
||||
type: 'consumer',
|
||||
}));
|
||||
|
||||
const channelsData = data.connections.flatMap((c) => c.channels);
|
||||
const channelTotal = (channelsData.length * SPACE_H) / 2;
|
||||
const channels = channelsData.map((c, i) => ({
|
||||
id: c.id,
|
||||
title: c.number,
|
||||
y: SPACE_V * 3,
|
||||
x: i * SPACE_H - channelTotal,
|
||||
type: 'channel',
|
||||
}));
|
||||
|
||||
const connectionTotal = (data.connections.length * SPACE_H) / 2;
|
||||
const connections = data.connections.map((c, i) => ({
|
||||
id: c.id,
|
||||
title: c.peerAddr,
|
||||
y: SPACE_V * 4,
|
||||
x: i * SPACE_H - connectionTotal,
|
||||
type: 'connection',
|
||||
}));
|
||||
|
||||
const nodes = [
|
||||
...queues,
|
||||
...exchanges,
|
||||
...consumers,
|
||||
...channels,
|
||||
...connections,
|
||||
];
|
||||
|
||||
const bindingEdges = data.exchanges
|
||||
.flatMap((e) => e.bindings.map((b) => [b, e] as const))
|
||||
.map(([b, e]) => ({
|
||||
source: b.queue,
|
||||
target: e.name,
|
||||
label_to: `'${b.routingKey}'`,
|
||||
type: 'emptyEdge',
|
||||
}));
|
||||
|
||||
const consumerEdges = consumersData.map(([q, c]) => ({
|
||||
source: c.tag,
|
||||
target: q.name,
|
||||
type: 'emptyEdge',
|
||||
}));
|
||||
|
||||
const channelConsumerEdges = consumersData.map(([, c]) => ({
|
||||
source: c.channel,
|
||||
target: c.tag,
|
||||
type: 'emptyEdge',
|
||||
}));
|
||||
|
||||
const connectionChannelEdges = data.connections.flatMap((c) =>
|
||||
c.channels.map((ch) => ({
|
||||
source: c.id,
|
||||
target: ch.id,
|
||||
type: 'emptyEdge',
|
||||
}))
|
||||
);
|
||||
|
||||
const edges = [
|
||||
...bindingEdges,
|
||||
...consumerEdges,
|
||||
...channelConsumerEdges,
|
||||
...connectionChannelEdges,
|
||||
];
|
||||
|
||||
const nodeTypes = graphConfig.nodeTypes;
|
||||
const nodeSubtypes = graphConfig.nodeSubtypes;
|
||||
const edgeTypes = graphConfig.edgeTypes;
|
||||
|
||||
return (
|
||||
<div className="graph">
|
||||
<GraphView
|
||||
nodeKey="id"
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
nodeSubtypes={nodeSubtypes}
|
||||
edgeTypes={edgeTypes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityGraph;
|
||||
|
|
@ -9,11 +9,17 @@ export type Connection = {
|
|||
channels: ReadonlyArray<Channel>;
|
||||
};
|
||||
|
||||
export type Consumer = {
|
||||
tag: string;
|
||||
channel: string;
|
||||
};
|
||||
|
||||
export type Queue = {
|
||||
id: string;
|
||||
name: string;
|
||||
durable: boolean;
|
||||
messages: number;
|
||||
consumers: ReadonlyArray<Consumer>;
|
||||
};
|
||||
|
||||
export type Binding = {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,13 @@ struct Queue {
|
|||
name: String,
|
||||
durable: bool,
|
||||
messages: usize,
|
||||
consumers: Vec<Consumer>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Consumer {
|
||||
tag: String,
|
||||
channel: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
@ -124,6 +131,15 @@ async fn get_data(global_data: GlobalData) -> impl IntoResponse {
|
|||
name: queue.name.to_string(),
|
||||
durable: queue.durable,
|
||||
messages: queue.messages.len(),
|
||||
consumers: queue
|
||||
.consumers
|
||||
.lock()
|
||||
.values()
|
||||
.map(|consumer| Consumer {
|
||||
tag: consumer.tag.to_string(),
|
||||
channel: consumer.channel.id.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue