Files
HellreigN/frontend/src/pages/graphs.page.tsx
T
nikita 915aa7018a
ci-front / build (push) Successful in 3m39s
feat: graph 2
2026-04-05 09:19:39 +03:00

151 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useMemo } from "react";
import {
Graph,
type GraphData,
type GraphNode,
type GraphLink,
} from "@/modules/graph";
import { agentApiService } from "@/modules/agent/api/agent.api.service";
import { FaSpinner } from "react-icons/fa";
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
const buildGraphFromApi = (apiData: any, agents: any[]): GraphData => {
const nodes: GraphNode[] = [];
const links: GraphLink[] = [];
// Build a map of service statuses from agents
const serviceStatusMap = new Map<string, "up" | "down">();
agents.forEach((agent) => {
const services = agent.services || [];
services.forEach((svc: string) => {
// Parse "serviceName:up" or "serviceName:down"
const parts = svc.split(":");
const svcName = parts[0];
const status = parts[1] === "down" ? "down" : "up";
serviceStatusMap.set(`${agent.label}-${svcName}`, status);
});
});
if (!apiData?.nodes) return { nodes, links };
Object.entries(apiData.nodes).forEach(
([agentLabel, agentNode]: [string, any]) => {
// Агент как узел
nodes.push({
id: agentLabel,
name: agentLabel,
type: "agent",
val: 8,
description: `Агент: ${agentLabel}`,
});
// Сервисы агента
const services = agentNode?.services || {};
Object.entries(services).forEach(
([serviceName, serviceNode]: [string, any]) => {
const serviceId = `${agentLabel}-${serviceName}`;
const status = serviceStatusMap.get(serviceId) || "up";
nodes.push({
id: serviceId,
name: serviceName,
type: "service",
val: 12,
description: `Сервис: ${serviceName}`,
status,
});
// Связь агент → сервис
links.push({
source: agentLabel,
target: serviceId,
type: "hosts",
});
// Зависимости между сервисами
const dependencies = serviceNode?.dependencies || [];
dependencies.forEach((dep: any) => {
const targetServiceName = dep?.target?.name;
if (targetServiceName) {
const targetServiceId = `${agentLabel}-${targetServiceName}`;
links.push({
source: serviceId,
target: targetServiceId,
type: dep.condition || "dependency",
});
}
});
},
);
},
);
return { nodes, links };
};
export const GraphsPage = () => {
const agents = useAgentStore((s) => s.agents);
const [graphData, setGraphData] = useState<GraphData>({
nodes: [],
links: [],
});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchGraph = async () => {
setLoading(true);
setError(null);
try {
const apiData = await agentApiService.getGraph();
const data = buildGraphFromApi(apiData, agents);
setGraphData(data);
} catch (e) {
console.error("Failed to fetch graph:", e);
setError(e instanceof Error ? e.message : "Failed to load graph");
setGraphData({ nodes: [], links: [] });
} finally {
setLoading(false);
}
};
fetchGraph();
const interval = setInterval(fetchGraph, 30000);
return () => clearInterval(interval);
}, [agents]);
if (loading) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<FaSpinner
className="animate-spin mx-auto mb-4"
size={32}
style={{ color: "var(--accent)" }}
/>
<p style={{ color: "var(--text-secondary)" }}>Загрузка графа...</p>
</div>
</div>
);
}
if (error && graphData.nodes.length === 0) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<p style={{ color: "var(--error-text)", marginBottom: "12px" }}>
{error}
</p>
</div>
</div>
);
}
return (
<div className="h-full">
<Graph initialData={graphData} />
</div>
);
};