feat: 新增日志中心、功能优化与代码重构
- 新增 log_datasource.go / log_proxy.go / user_api_token.go 后端控制器 - 新增 log_datasource.go model - 新增 monitor/log-center.ts API 模块 - 新增 LogCenter 前端组件及页面 - 重构 deployment 相关组件 (HPDA/UpdateImageModal/bundle-yaml 等) - 新增 ResetPasswordDialog / code-snippet-block / copy-button 等 UI 组件 - 重构多个 k8s 相关组件 (TopologyView/ResourceVisualEditor/PodFileBrowser 等) - 更新多个 API 和工具函数 (auth.ts/monitor/index.ts/request.ts 等) - 更新静态资源 (index.html + 新 JS/CSS bundle) - 删除废弃静态资源文件
This commit is contained in:
1 parent
5ca0e6d83a
commit
8d46e8f352
197 files changed
+5014
-815
No files matched your search
@@ -9,6 +9,7 @@ export const NODE_TYPE_LABELS: Record<string, string> = {
|
||||
loadbalancer: 'Ingress',
|
||||
service: 'Service',
|
||||
app: 'Deployment',
|
||||
pod: 'Pod',
|
||||
server: 'Node',
|
||||
database: '数据库',
|
||||
external: '外部服务',
|
||||
@@ -33,6 +34,7 @@ export function getNodeIcon(type: string, className = 'h-5 w-5') {
|
||||
case 'cluster': return <Cloud className={className} />;
|
||||
case 'app': return <Boxes className={className} />;
|
||||
case 'service': return <Network className={className} />;
|
||||
case 'pod': return <Box className={className} />;
|
||||
case 'database': return <Database className={className} />;
|
||||
case 'external': return <Zap className={className} />;
|
||||
case 'custom': return <Box className={className} />;
|
||||
@@ -48,6 +50,7 @@ export function getNodeColor(type: string) {
|
||||
case 'cluster': return 'bg-cyan-500/20 text-cyan-400 border-cyan-500/30';
|
||||
case 'app': return 'bg-rose-500/20 text-rose-400 border-rose-500/30';
|
||||
case 'service': return 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30';
|
||||
case 'pod': return 'bg-teal-500/20 text-teal-400 border-teal-500/30';
|
||||
case 'database': return 'bg-amber-500/20 text-amber-400 border-amber-500/30';
|
||||
case 'external': return 'bg-orange-500/20 text-orange-400 border-orange-500/30';
|
||||
case 'custom': return 'bg-slate-500/20 text-slate-400 border-slate-500/30';
|
||||
@@ -56,18 +59,19 @@ export function getNodeColor(type: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* buildTopology — 从真实 K8s 资源构建分层拓扑图
|
||||
* 层级: Cluster → Ingress → Service → Deployment → Node
|
||||
* buildTopology — 从真实 K8s 资源构建分层拓扑图(自动关联)
|
||||
* 推荐层级: Cluster → Ingress(域名+路径) → Service → Deployment → Pod
|
||||
* 支持基于 Ingress rules、Service selector、Pod 标签的自动连线
|
||||
*/
|
||||
export function buildTopology( cluster: any, ingresses: any[], services: any[], deployments: any[], nodes: any[],
|
||||
export function buildTopology( cluster: any, ingresses: any[], services: any[], deployments: any[], nodes: any[], pods: any[] = [],
|
||||
): { nodes: TopologyNode[]; edges: TopologyEdge[] } {
|
||||
const tNodes: TopologyNode[] = [];
|
||||
const tEdges: TopologyEdge[] = [];
|
||||
const MAX_PER_LAYER = 12;
|
||||
|
||||
const maxLayerCount = Math.max(1, ingresses.length, services.length, deployments.length, nodes.length);
|
||||
const canvasW = Math.max(900, Math.min(maxLayerCount * 130, 2400));
|
||||
const layerY = [60, 180, 320, 480, 640];
|
||||
const maxLayerCount = Math.max(1, ingresses.length, services.length, deployments.length, pods.length || nodes.length);
|
||||
const canvasW = Math.max(900, Math.min(maxLayerCount * 140, 2600));
|
||||
const layerY = [50, 160, 280, 410, 540, 680]; // cluster, ingress, svc, deploy, pod, node (可选)
|
||||
|
||||
const distributeX = (count: number): number[] => {
|
||||
const cap = Math.min(count, MAX_PER_LAYER);
|
||||
@@ -85,12 +89,24 @@ export function buildTopology( cluster: any, ingresses: any[], services: any[],
|
||||
data: { kind: 'cluster', version: cluster.version, status: cluster.status, api_server: cluster.api_server, node_count: cluster.node_count, pod_count: cluster.pod_count },
|
||||
});
|
||||
|
||||
// Layer 1: Ingress
|
||||
// Layer 1: Ingress (优先显示 域名 + 路径)
|
||||
const ingCap = ingresses.slice(0, MAX_PER_LAYER);
|
||||
const ingXs = distributeX(ingCap.length);
|
||||
ingCap.forEach((ing, i) => {
|
||||
const id = `ingress-${ing.id}`;
|
||||
tNodes.push({ id, name: ing.name, type: 'loadbalancer', x: ingXs[i], y: layerY[1], data: { kind: 'ingress', namespace: ing.namespace, backend: ing.backend, tls: ing.tls, hosts: ing.hosts } });
|
||||
const hosts = Array.isArray(ing.hosts) ? ing.hosts : (ing.hosts ? JSON.parse(ing.hosts) : []);
|
||||
const hostLabel = hosts.length ? hosts.join(',') : (ing.name || '');
|
||||
// 尝试显示路径 (如果数据中有 paths 或 backend)
|
||||
const pathLabel = ing.paths ? ing.paths : (ing.backend ? `→${ing.backend}` : '');
|
||||
const displayName = pathLabel ? `${hostLabel}\n${pathLabel}` : hostLabel;
|
||||
tNodes.push({
|
||||
id,
|
||||
name: displayName,
|
||||
type: 'loadbalancer',
|
||||
x: ingXs[i],
|
||||
y: layerY[1],
|
||||
data: { kind: 'ingress', namespace: ing.namespace, backend: ing.backend, tls: ing.tls, hosts }
|
||||
});
|
||||
tEdges.push({ id: `e-${clusterId}-${id}`, source: clusterId, target: id, label: 'route' });
|
||||
});
|
||||
|
||||
@@ -124,7 +140,41 @@ export function buildTopology( cluster: any, ingresses: any[], services: any[],
|
||||
tNodes.push({ id, name: dep.name, type: 'app', x: depXs[i], y: layerY[3], data: { kind: 'deployment', namespace: dep.namespace, replicas: dep.replicas, ready_replicas: dep.ready_replicas, available_replicas: dep.available_replicas, image: dep.image } });
|
||||
});
|
||||
|
||||
// Service → Deployment edges
|
||||
// Layer 4: Pod (自动关联 Deployment)
|
||||
const podCap = (pods || []).slice(0, MAX_PER_LAYER);
|
||||
const podXs = distributeX(podCap.length);
|
||||
podCap.forEach((pod, i) => {
|
||||
const id = `pod-${pod.id || pod.name}`;
|
||||
tNodes.push({
|
||||
id,
|
||||
name: pod.name || 'pod',
|
||||
type: 'pod', // 需要在类型中支持
|
||||
x: podXs[i],
|
||||
y: layerY[4],
|
||||
data: { kind: 'pod', namespace: pod.namespace, node: pod.node_name || pod.node, status: pod.status, ip: pod.ip }
|
||||
});
|
||||
});
|
||||
|
||||
// Deployment → Pod 连线 (基于 deployment 名称匹配或 namespace)
|
||||
if (depCap.length > 0 && podCap.length > 0) {
|
||||
depCap.forEach((dep) => {
|
||||
const depNodeId = `deploy-${dep.id}`;
|
||||
let matchedPods = podCap.filter((p) => p.namespace === dep.namespace);
|
||||
// 简单匹配:pod 名称包含 deploy 名称 或 labels
|
||||
matchedPods = matchedPods.filter((p) =>
|
||||
(p.name || '').includes(dep.name) ||
|
||||
(dep.name && (p.name || '').startsWith(dep.name))
|
||||
);
|
||||
if (matchedPods.length === 0) matchedPods = podCap.filter((p) => p.namespace === dep.namespace).slice(0, 3);
|
||||
if (matchedPods.length === 0) matchedPods = podCap.slice(0, 2);
|
||||
matchedPods.slice(0, 3).forEach((p) => {
|
||||
const pid = `pod-${p.id || p.name}`;
|
||||
tEdges.push({ id: `e-${depNodeId}-${pid}`, source: depNodeId, target: pid, label: 'owns' });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Service → Deployment edges (基于 selector 自动匹配)
|
||||
if (svcCap.length > 0 && depCap.length > 0) {
|
||||
svcCap.forEach((svc) => {
|
||||
const svcNodeId = `service-${svc.id}`;
|
||||
@@ -147,18 +197,23 @@ export function buildTopology( cluster: any, ingresses: any[], services: any[],
|
||||
});
|
||||
}
|
||||
|
||||
// Layer 4: Node
|
||||
// Layer 5: Node (宿主机,可选)
|
||||
const nodeCap = nodes.slice(0, MAX_PER_LAYER);
|
||||
const nodeXs = distributeX(nodeCap.length);
|
||||
nodeCap.forEach((n, i) => {
|
||||
const id = `k8snode-${n.id}`;
|
||||
tNodes.push({ id, name: n.name, type: 'server', x: nodeXs[i], y: layerY[4], data: { kind: 'node', ip: n.ip, role: n.role, status: n.status, os: n.os, cpu: n.cpu, memory: n.memory, cpu_usage: n.cpu_usage, memory_usage: n.memory_usage, pod_count: n.pod_count } });
|
||||
tNodes.push({ id, name: n.name, type: 'server', x: nodeXs[i], y: layerY[5], data: { kind: 'node', ip: n.ip, role: n.role, status: n.status, os: n.os, cpu: n.cpu, memory: n.memory, cpu_usage: n.cpu_usage, memory_usage: n.memory_usage, pod_count: n.pod_count } });
|
||||
});
|
||||
|
||||
// Deployment → Node edges
|
||||
// Deployment / Pod → Node (宿主机) 连线
|
||||
const workerNodes = nodeCap.filter((n) => (n.role || '').toLowerCase() === 'worker');
|
||||
const schedulableNodes = workerNodes.length > 0 ? workerNodes : nodeCap;
|
||||
if (depCap.length > 0 && schedulableNodes.length > 0) {
|
||||
if (podCap.length > 0 && schedulableNodes.length > 0) {
|
||||
podCap.forEach((p) => {
|
||||
const pid = `pod-${p.id || p.name}`;
|
||||
schedulableNodes.slice(0, 2).forEach((n) => tEdges.push({ id: `e-${pid}-k8snode-${n.id}`, source: pid, target: `k8snode-${n.id}`, label: 'on' }));
|
||||
});
|
||||
} else if (depCap.length > 0 && schedulableNodes.length > 0) {
|
||||
depCap.forEach((dep) => {
|
||||
const depNodeId = `deploy-${dep.id}`;
|
||||
schedulableNodes.slice(0, 3).forEach((n) => tEdges.push({ id: `e-${depNodeId}-k8snode-${n.id}`, source: depNodeId, target: `k8snode-${n.id}`, label: 'schedule' }));
|
||||
|
||||
Reference in new issue
Block a user