"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { readBackendUnavailableMessage, useResolvedApiBase } from "../lib/api-base";
import { ConfirmDialog, EmptyPanel, ErrorBanner, NoticeBanner, PageStatus } from "./admin-feedback";
import { DetailKeyGrid, DetailSection } from "./admin-detail";
import { AdminFrame } from "./admin-frame";
import { ResourceCard, ResourceCardHeader, ResourceToolbar } from "./admin-resource";
import styles from "./platform-shell.module.css";

type TargetType =
  | "http"
  | "tcp"
  | "redis"
  | "rabbitmq"
  | "elasticsearch"
  | "java-app";
type TargetStatus = "enabled" | "disabled";
type RunStatus = "queued" | "running" | "success" | "failed" | "partial";
type LogSeverity = "info" | "warning" | "major" | "critical";

type MonitorTarget = {
  id: string;
  name: string;
  type: TargetType;
  serviceKey: string;
  cronExpr: string;
  status: TargetStatus;
  endpoint: string;
  configJson: string;
  secretJson: string | null;
  lastRunAt: string | null;
};

type MonitorRun = {
  id: string;
  traceId: string;
  status: RunStatus;
  errorTag: string | null;
  summary: string | null;
  failureReason: string | null;
  startedAt: string;
  endedAt: string | null;
  target?: MonitorTarget;
};

type RunFilters = {
  targetId: string;
  status: "" | RunStatus;
  latestPerTarget: boolean;
  recentOnly: boolean;
  limit: string;
};

type RunMonthGroup = {
  month: string;
  label: string;
  runs: MonitorRun[];
};

type MonitorRunDetail = {
  run: MonitorRun & { target: MonitorTarget };
  detail: {
    metrics: Record<string, unknown>;
    details: Record<string, unknown>;
    logExcerpt: string | null;
    analysis: {
      targetType: TargetType;
      source: {
        openobserve: "configured" | "fallback";
        drain3: "configured" | "fallback";
      };
      overview: string;
      severity: LogSeverity;
      templates: Array<{
        templateId: string;
        templateText: string;
        sampleCount: number;
        sampleMessages: string[];
        levels: string[];
        category: string;
        severity: LogSeverity;
      }>;
      issues: Array<{
        id: string;
        title: string;
        category: string;
        severity: LogSeverity;
        confidence: "low" | "medium" | "high";
        summary: string;
        evidence: string[];
        possibleCauses: string[];
        recommendedActions: string[];
        templateIds: string[];
      }>;
      stats: {
        totalLogs: number;
        errorCount: number;
        warnCount: number;
        infoCount: number;
        matchedTemplateCount: number;
      };
    };
    resultJsonUrl: string | null;
  };
};

type MonitorSummary = {
  targetCount: number;
  runCount: number;
  exceptionCount: number;
};

type MonitorAgentOption = {
  id: string;
  name: string;
  agentKey: string;
  host: string | null;
  platform: "linux" | "windows";
  status: "enabled" | "disabled";
};

type TargetForm = {
  name: string;
  type: TargetType;
  serviceKey: string;
  cronExpr: string;
  endpoint: string;
  configJson: string;
  secretJson: string;
};

type HostAgentVisualForm = {
  agentKey: string;
  processMatchMode: "pid" | "command";
  processMatchValue: string;
  nodeKey: string;
  host: string;
  jcmdPath: string;
  jstatPath: string;
  jcmdCommandsText: string;
  jstatOptionsText: string;
  logFilePath: string;
  logTailLines: string;
  logKeyword: string;
};

type RedisVisualForm = {
  collectionMode: "builtin" | "host-agent";
  agentKey: string;
  tcpHost: string;
  tcpPort: string;
  timeoutMs: string;
  authPassword: string;
  logFilePath: string;
  logTailLines: string;
  logKeyword: string;
};

type HttpVisualForm = {
  collectionMode: "blackbox" | "builtin" | "host-agent";
  agentKey: string;
  url: string;
  timeoutMs: string;
  expectedStatus: string;
  authUsername: string;
  authPassword: string;
  blackboxBaseUrl: string;
  blackboxModule: string;
};

type RabbitMqVisualForm = {
  collectionMode: "builtin" | "host-agent";
  agentKey: string;
  baseUrl: string;
  authUsername: string;
  authPassword: string;
  overviewPath: string;
  queuesPath: string;
  nodesPath: string;
};

type ElasticsearchVisualForm = {
  collectionMode: "builtin" | "host-agent";
  agentKey: string;
  baseUrl: string;
  authMode: "none" | "basic" | "bearer";
  authUsername: string;
  authPassword: string;
  bearerToken: string;
  healthPath: string;
  nodesPath: string;
};

type TargetFilters = {
  keyword: string;
  status: "" | TargetStatus;
  type: "" | TargetType;
  sort: "recent" | "name" | "type";
};

type ConfirmState = {
  title: string;
  message: string;
  confirmLabel?: string;
  tone?: "default" | "danger";
  action: () => void | Promise<void>;
};

const typeOptions: Array<{
  value: TargetType;
  label: string;
  description: string;
}> = [
  { value: "http", label: "HTTP / HTTPS", description: "优先建议通过 blackbox_exporter 探测，未接入时再回退到内置 HTTP 检测。" },
  { value: "tcp", label: "TCP 端口", description: "轻量探活端口连通性，适合 Redis、MQ、数据库等基础环境。" },
  { value: "redis", label: "Redis", description: "执行 PING 和 INFO，采集内存、连接数、主从角色等关键信息。" },
  { value: "rabbitmq", label: "RabbitMQ", description: "调用 management API 获取节点、队列积压和消息情况。" },
  { value: "elasticsearch", label: "Elasticsearch", description: "读取集群健康和节点状态，判断集群是否异常。" },
  { value: "java-app", label: "Java 应用", description: "优先接 JMX Exporter 或 Actuator；阶段 1 支持 hybrid 模式同时采 JVM 指标和健康状态。" },
];

const templateByType: Record<TargetType, TargetForm> = {
  http: {
    name: "测试环境 HTTP 健康检查",
    type: "http",
    serviceKey: "demo-http-blackbox",
    cronExpr: "*/5 * * * *",
    endpoint: "http://192.168.1.30:8080/health",
    configJson: JSON.stringify({ collectionMode: "blackbox", blackboxBaseUrl: "http://192.168.1.50:9115", blackboxModule: "http_2xx", blackboxTarget: "http://192.168.1.30:8080/health", timeoutMs: 8000, expectedStatus: 200 }, null, 2),
    secretJson: JSON.stringify({}, null, 2),
  },
  tcp: {
    name: "测试环境 TCP 端口探活",
    type: "tcp",
    serviceKey: "demo-tcp",
    cronExpr: "*/5 * * * *",
    endpoint: "192.168.1.10",
    configJson: JSON.stringify({ collectionMode: "blackbox", blackboxBaseUrl: "http://192.168.1.50:9115", blackboxModule: "tcp_connect", tcpHost: "192.168.1.10", tcpPort: 6379, blackboxTarget: "192.168.1.10:6379", timeoutMs: 5000 }, null, 2),
    secretJson: JSON.stringify({}, null, 2),
  },
  redis: {
    name: "Redis 测试环境",
    type: "redis",
    serviceKey: "infra-redis-test",
    cronExpr: "*/5 * * * *",
    endpoint: "192.168.1.10",
    configJson: JSON.stringify({ tcpHost: "192.168.1.10", tcpPort: 6379, timeoutMs: 5000, redisSections: ["server", "memory", "stats", "replication"] }, null, 2),
    secretJson: JSON.stringify({ authPassword: "", sshHost: "", sshPort: 22, sshUsername: "", sshPassword: "", logFilePath: "", logTailLines: 120, logKeyword: "ERROR|Exception|Timeout|Refused" }, null, 2),
  },
  rabbitmq: {
    name: "RabbitMQ 测试环境",
    type: "rabbitmq",
    serviceKey: "infra-rabbitmq-test",
    cronExpr: "*/5 * * * *",
    endpoint: "http://192.168.1.10:15672",
    configJson: JSON.stringify({ rabbitmqOverviewPath: "/api/overview", rabbitmqQueuesPath: "/api/queues", rabbitmqNodesPath: "/api/nodes" }, null, 2),
    secretJson: JSON.stringify({ authUsername: "guest", authPassword: "guest" }, null, 2),
  },
  elasticsearch: {
    name: "Elasticsearch 测试环境",
    type: "elasticsearch",
    serviceKey: "infra-es-test",
    cronExpr: "*/5 * * * *",
    endpoint: "http://192.168.1.10:9200",
    configJson: JSON.stringify({ elasticsearchHealthPath: "/_cluster/health", elasticsearchNodesPath: "/_nodes/stats" }, null, 2),
    secretJson: JSON.stringify({}, null, 2),
  },
  "java-app": {
    name: "Java 程序宿主机 Agent 监测",
    type: "java-app",
    serviceKey: "java-demo-app",
    cronExpr: "*/5 * * * *",
    endpoint: "host-agent://local-windows-agent",
    configJson: JSON.stringify({ collectionMode: "host-agent", agentKey: "local-windows-agent", processMatchMode: "command", processMatchValue: "java-demo-app-0.0.1-SNAPSHOT.jar", jcmdCommands: ["VM.command_line", "GC.heap_info"], jstatOptions: ["-gcutil"], jcmdPath: "D:\\Antigravity\\soft\\Java\\bin\\jcmd.exe", jstatPath: "D:\\Antigravity\\soft\\Java\\bin\\jstat.exe", nodeKey: "java-demo-app-node-1", host: "localhost" }, null, 2),
    secretJson: JSON.stringify({ logFilePath: "d:/soft/codex/SentAI/examples/java-demo-app/logs/java-demo-app.log", logTailLines: 120, logKeyword: "ERROR|WARN|Exception|Timeout|Refused" }, null, 2),
  },
};

function formatTime(value?: string | null) {
  return value ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "暂无";
}

function readErrorMessage(error: unknown, apiBase?: string) {
  if (error instanceof TypeError) return readBackendUnavailableMessage(apiBase);
  if (error instanceof Error) return error.message;
  if (error && typeof error === "object" && "message" in error) {
    const message = (error as { message?: string | string[] }).message;
    return Array.isArray(message) ? message.join("；") : (message ?? "请求失败，请稍后重试。");
  }
  return "请求失败，请稍后重试。";
}

function statusLabel(status: TargetStatus | RunStatus) {
  return ({ enabled: "启用", disabled: "停用", queued: "排队中", running: "执行中", success: "成功", failed: "失败", partial: "部分成功" } as const)[status];
}

function severityLabel(value: LogSeverity) {
  return ({ info: "信息", warning: "预警", major: "严重", critical: "紧急" } as const)[value];
}

function confidenceLabel(value: "low" | "medium" | "high") {
  return ({ low: "低", medium: "中", high: "高" } as const)[value];
}

function targetTypeLabel(type: TargetType) {
  return typeOptions.find((item) => item.value === type)?.label ?? type;
}

function compactTraceId(traceId: string) {
  return traceId.length > 16 ? `${traceId.slice(0, 8)}...${traceId.slice(-6)}` : traceId;
}

function safePreviewText(value: string | null | undefined, fallback: string) {
  const text = value?.trim();
  if (!text) return fallback;
  if (text.includes("�")) return fallback;
  return text;
}

function Field(props: { label: string; hint: string; children: React.ReactNode }) {
  return <label className={styles.field}><span className={styles.fieldLabel}>{props.label}</span><span className={styles.fieldHint}>{props.hint}</span>{props.children}</label>;
}

function StatCard(props: { label: string; value: number; tone?: string }) {
  return <article className={styles.statCard} data-tone={props.tone ?? "default"}><span>{props.label}</span><strong>{props.value}</strong></article>;
}

function parseJsonObject(value: string | null | undefined) {
  if (!value?.trim()) return {};
  try {
    const parsed = JSON.parse(value);
    return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
  } catch {
    return {};
  }
}

function toMultilineValue(value: unknown, fallback = "") {
  return Array.isArray(value) ? value.join("\n") : typeof value === "string" ? value : fallback;
}

function deriveHostAgentVisualForm(form: TargetForm): HostAgentVisualForm {
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);
  const endpointAgentKey = form.endpoint.startsWith("host-agent://") ? form.endpoint.replace("host-agent://", "") : "";

  return {
    agentKey: typeof config.agentKey === "string" ? config.agentKey : endpointAgentKey,
    processMatchMode: config.processMatchMode === "pid" ? "pid" : "command",
    processMatchValue: typeof config.processMatchValue === "string"
      ? config.processMatchValue
      : typeof config.processPid === "number"
        ? String(config.processPid)
        : "",
    nodeKey: typeof config.nodeKey === "string" ? config.nodeKey : "",
    host: typeof config.host === "string" ? config.host : "",
    jcmdPath: typeof config.jcmdPath === "string" ? config.jcmdPath : "",
    jstatPath: typeof config.jstatPath === "string" ? config.jstatPath : "",
    jcmdCommandsText: toMultilineValue(config.jcmdCommands, "VM.command_line\nGC.heap_info"),
    jstatOptionsText: toMultilineValue(config.jstatOptions, "-gcutil"),
    logFilePath: typeof secret.logFilePath === "string" ? secret.logFilePath : "",
    logTailLines: typeof secret.logTailLines === "number" ? String(secret.logTailLines) : "120",
    logKeyword: typeof secret.logKeyword === "string" ? secret.logKeyword : "ERROR|WARN|Exception|Timeout|Refused",
  };
}

function syncHostAgentJson(form: TargetForm, patch: Partial<HostAgentVisualForm>): TargetForm {
  const current = deriveHostAgentVisualForm(form);
  const next = { ...current, ...patch };
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);

  const processMatchMode = next.processMatchMode === "pid" ? "pid" : "command";
  const configPayload: Record<string, unknown> = {
    ...config,
    collectionMode: "host-agent",
    agentKey: next.agentKey.trim(),
    processMatchMode,
    processMatchValue: next.processMatchValue.trim(),
    jcmdCommands: next.jcmdCommandsText.split(/\r?\n/).map((item) => item.trim()).filter(Boolean),
    jstatOptions: next.jstatOptionsText.split(/\r?\n/).map((item) => item.trim()).filter(Boolean),
    jcmdPath: next.jcmdPath.trim(),
    jstatPath: next.jstatPath.trim(),
    nodeKey: next.nodeKey.trim(),
    host: next.host.trim(),
  };

  if (processMatchMode === "pid") {
    const processPid = Number(next.processMatchValue.trim());
    configPayload.processPid = Number.isFinite(processPid) ? processPid : undefined;
  } else {
    delete configPayload.processPid;
  }

  const secretPayload: Record<string, unknown> = {
    ...secret,
    logFilePath: next.logFilePath.trim(),
    logTailLines: Number(next.logTailLines.trim()) || 120,
    logKeyword: next.logKeyword.trim() || "ERROR|WARN|Exception|Timeout|Refused",
  };

  return {
    ...form,
    endpoint: next.agentKey.trim() ? `host-agent://${next.agentKey.trim()}` : form.endpoint,
    configJson: JSON.stringify(configPayload, null, 2),
    secretJson: JSON.stringify(secretPayload, null, 2),
  };
}

function deriveRedisVisualForm(form: TargetForm): RedisVisualForm {
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);
  return {
    collectionMode: config.collectionMode === "host-agent" ? "host-agent" : "builtin",
    agentKey: typeof config.agentKey === "string" ? config.agentKey : "",
    tcpHost: typeof config.tcpHost === "string" ? config.tcpHost : form.endpoint,
    tcpPort: typeof config.tcpPort === "number" ? String(config.tcpPort) : "6379",
    timeoutMs: typeof config.timeoutMs === "number" ? String(config.timeoutMs) : "5000",
    authPassword: typeof secret.authPassword === "string" ? secret.authPassword : "",
    logFilePath: typeof secret.logFilePath === "string" ? secret.logFilePath : "",
    logTailLines: typeof secret.logTailLines === "number" ? String(secret.logTailLines) : "120",
    logKeyword: typeof secret.logKeyword === "string" ? secret.logKeyword : "ERROR|WARN|Exception|Timeout|Refused",
  };
}

function syncRedisJson(form: TargetForm, patch: Partial<RedisVisualForm>): TargetForm {
  const current = deriveRedisVisualForm(form);
  const next = { ...current, ...patch };
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);

  const configPayload: Record<string, unknown> = {
    ...config,
    collectionMode: next.collectionMode,
    agentKey: next.collectionMode === "host-agent" ? next.agentKey.trim() : undefined,
    tcpHost: next.tcpHost.trim(),
    tcpPort: Number(next.tcpPort.trim()) || 6379,
    timeoutMs: Number(next.timeoutMs.trim()) || 5000,
    redisSections: Array.isArray(config.redisSections) && config.redisSections.length
      ? config.redisSections
      : ["server", "memory", "stats", "replication"],
  };

  const secretPayload: Record<string, unknown> = {
    ...secret,
    authPassword: next.authPassword,
    logFilePath: next.logFilePath.trim(),
    logTailLines: Number(next.logTailLines.trim()) || 120,
    logKeyword: next.logKeyword.trim() || "ERROR|WARN|Exception|Timeout|Refused",
  };

  if (next.collectionMode !== "host-agent") {
    delete configPayload.agentKey;
  }

  return {
    ...form,
    endpoint: next.tcpHost.trim(),
    configJson: JSON.stringify(configPayload, null, 2),
    secretJson: JSON.stringify(secretPayload, null, 2),
  };
}

function deriveHttpVisualForm(form: TargetForm): HttpVisualForm {
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);
  return {
    collectionMode:
      config.collectionMode === "host-agent"
        ? "host-agent"
        : config.collectionMode === "builtin"
          ? "builtin"
          : "blackbox",
    agentKey: typeof config.agentKey === "string" ? config.agentKey : "",
    url: typeof config.blackboxTarget === "string"
      ? config.blackboxTarget
      : typeof config.url === "string"
        ? config.url
        : form.endpoint,
    timeoutMs: typeof config.timeoutMs === "number" ? String(config.timeoutMs) : "8000",
    expectedStatus: typeof config.expectedStatus === "number" ? String(config.expectedStatus) : "200",
    authUsername: typeof secret.authUsername === "string" ? secret.authUsername : "",
    authPassword: typeof secret.authPassword === "string" ? secret.authPassword : "",
    blackboxBaseUrl: typeof config.blackboxBaseUrl === "string" ? config.blackboxBaseUrl : "http://127.0.0.1:9115",
    blackboxModule: typeof config.blackboxModule === "string" ? config.blackboxModule : "http_2xx",
  };
}

function syncHttpJson(form: TargetForm, patch: Partial<HttpVisualForm>): TargetForm {
  const current = deriveHttpVisualForm(form);
  const next = { ...current, ...patch };
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);

  const configPayload: Record<string, unknown> = {
    ...config,
    collectionMode: next.collectionMode,
    agentKey: next.collectionMode === "host-agent" ? next.agentKey.trim() : undefined,
    timeoutMs: Number(next.timeoutMs.trim()) || 8000,
    expectedStatus: Number(next.expectedStatus.trim()) || 200,
  };

  if (next.collectionMode === "blackbox") {
    Object.assign(configPayload, {
      blackboxBaseUrl: next.blackboxBaseUrl.trim(),
      blackboxModule: next.blackboxModule.trim() || "http_2xx",
      blackboxTarget: next.url.trim(),
    });
    delete configPayload.url;
  } else {
    Object.assign(configPayload, {
      url: next.url.trim(),
    });
    delete configPayload.blackboxBaseUrl;
    delete configPayload.blackboxModule;
    delete configPayload.blackboxTarget;
  }

  if (next.collectionMode !== "host-agent") {
    delete configPayload.agentKey;
  }

  const secretPayload: Record<string, unknown> = {
    ...secret,
    authUsername: next.authUsername.trim(),
    authPassword: next.authPassword,
  };

  return {
    ...form,
    endpoint: next.url.trim(),
    configJson: JSON.stringify(configPayload, null, 2),
    secretJson: JSON.stringify(secretPayload, null, 2),
  };
}

function deriveRabbitMqVisualForm(form: TargetForm): RabbitMqVisualForm {
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);
  return {
    collectionMode: config.collectionMode === "host-agent" ? "host-agent" : "builtin",
    agentKey: typeof config.agentKey === "string" ? config.agentKey : "",
    baseUrl: form.endpoint,
    authUsername: typeof secret.authUsername === "string" ? secret.authUsername : "guest",
    authPassword: typeof secret.authPassword === "string" ? secret.authPassword : "guest",
    overviewPath: typeof config.rabbitmqOverviewPath === "string" ? config.rabbitmqOverviewPath : "/api/overview",
    queuesPath: typeof config.rabbitmqQueuesPath === "string" ? config.rabbitmqQueuesPath : "/api/queues",
    nodesPath: typeof config.rabbitmqNodesPath === "string" ? config.rabbitmqNodesPath : "/api/nodes",
  };
}

function syncRabbitMqJson(form: TargetForm, patch: Partial<RabbitMqVisualForm>): TargetForm {
  const current = deriveRabbitMqVisualForm(form);
  const next = { ...current, ...patch };
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);

  const configPayload: Record<string, unknown> = {
    ...config,
    collectionMode: next.collectionMode,
    agentKey: next.collectionMode === "host-agent" ? next.agentKey.trim() : undefined,
    rabbitmqOverviewPath: next.overviewPath.trim() || "/api/overview",
    rabbitmqQueuesPath: next.queuesPath.trim() || "/api/queues",
    rabbitmqNodesPath: next.nodesPath.trim() || "/api/nodes",
  };
  if (next.collectionMode !== "host-agent") {
    delete configPayload.agentKey;
  }

  const secretPayload: Record<string, unknown> = {
    ...secret,
    authUsername: next.authUsername.trim(),
    authPassword: next.authPassword,
  };

  return {
    ...form,
    endpoint: next.baseUrl.trim(),
    configJson: JSON.stringify(configPayload, null, 2),
    secretJson: JSON.stringify(secretPayload, null, 2),
  };
}

function deriveElasticsearchVisualForm(form: TargetForm): ElasticsearchVisualForm {
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);
  const authMode =
    secret.bearerToken
      ? "bearer"
      : secret.authUsername || secret.authPassword
        ? "basic"
        : "none";
  return {
    collectionMode: config.collectionMode === "host-agent" ? "host-agent" : "builtin",
    agentKey: typeof config.agentKey === "string" ? config.agentKey : "",
    baseUrl: form.endpoint,
    authMode,
    authUsername: typeof secret.authUsername === "string" ? secret.authUsername : "",
    authPassword: typeof secret.authPassword === "string" ? secret.authPassword : "",
    bearerToken: typeof secret.bearerToken === "string" ? secret.bearerToken : "",
    healthPath: typeof config.elasticsearchHealthPath === "string" ? config.elasticsearchHealthPath : "/_cluster/health",
    nodesPath: typeof config.elasticsearchNodesPath === "string" ? config.elasticsearchNodesPath : "/_nodes/stats",
  };
}

function syncElasticsearchJson(form: TargetForm, patch: Partial<ElasticsearchVisualForm>): TargetForm {
  const current = deriveElasticsearchVisualForm(form);
  const next = { ...current, ...patch };
  const config = parseJsonObject(form.configJson);
  const secret = parseJsonObject(form.secretJson);

  const configPayload: Record<string, unknown> = {
    ...config,
    collectionMode: next.collectionMode,
    agentKey: next.collectionMode === "host-agent" ? next.agentKey.trim() : undefined,
    elasticsearchHealthPath: next.healthPath.trim() || "/_cluster/health",
    elasticsearchNodesPath: next.nodesPath.trim() || "/_nodes/stats",
  };
  if (next.collectionMode !== "host-agent") {
    delete configPayload.agentKey;
  }

  const secretPayload: Record<string, unknown> = {
    ...secret,
    authUsername: next.authMode === "basic" ? next.authUsername.trim() : "",
    authPassword: next.authMode === "basic" ? next.authPassword : "",
    bearerToken: next.authMode === "bearer" ? next.bearerToken.trim() : "",
  };

  return {
    ...form,
    endpoint: next.baseUrl.trim(),
    configJson: JSON.stringify(configPayload, null, 2),
    secretJson: JSON.stringify(secretPayload, null, 2),
  };
}

export function MonitoringConsole(props?: {
  initialEditTargetId?: string;
  initialFocusTargetId?: string;
  initialFocusRunId?: string;
  standaloneEdit?: boolean;
}) {
  const router = useRouter();
  const apiBase = useResolvedApiBase();
  const [token, setToken] = useState("");
  const [summary, setSummary] = useState<MonitorSummary | null>(null);
  const [targets, setTargets] = useState<MonitorTarget[]>([]);
  const [agents, setAgents] = useState<MonitorAgentOption[]>([]);
  const [runs, setRuns] = useState<MonitorRun[]>([]);
  const [selectedRunIds, setSelectedRunIds] = useState<string[]>([]);
  const [selectedRunId, setSelectedRunId] = useState<string | null>(null);
  const [selectedRunDetail, setSelectedRunDetail] = useState<MonitorRunDetail | null>(null);
  const [editingTargetId, setEditingTargetId] = useState<string | null>(null);
  const [focusTargetApplied, setFocusTargetApplied] = useState(false);
  const [focusRunApplied, setFocusRunApplied] = useState(false);
  const [form, setForm] = useState<TargetForm>(templateByType.redis);
  const [targetFilters, setTargetFilters] = useState<TargetFilters>({
    keyword: "",
    status: "",
    type: "",
    sort: "recent",
  });
  const [errorMessage, setErrorMessage] = useState("");
  const [infoMessage, setInfoMessage] = useState("");
  const [submitting, setSubmitting] = useState(false);
  const [executingId, setExecutingId] = useState<string | null>(null);
  const [queueAction, setQueueAction] = useState<string | null>(null);
  const [refreshing, setRefreshing] = useState(false);
  const [showAdvancedJson, setShowAdvancedJson] = useState(false);
  const [runFilters, setRunFilters] = useState<RunFilters>({
    targetId: "",
    status: "",
    latestPerTarget: false,
    recentOnly: true,
    limit: "20",
  });
  const [queueThresholds, setQueueThresholds] = useState({
    queuedOlderThanMinutes: "10",
    staleRunningMinutes: "20",
  });
  const [runPage, setRunPage] = useState(1);
  const [runPageSize, setRunPageSize] = useState("20");
  const [collapsedMonths, setCollapsedMonths] = useState<string[]>([]);
  const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
  const [confirmBusy, setConfirmBusy] = useState(false);

  useEffect(() => {
    const savedToken = window.localStorage.getItem("sentai.token");
    if (savedToken) setToken(savedToken);
  }, []);

  useEffect(() => {
    if (!infoMessage) return;
    const timer = window.setTimeout(() => setInfoMessage(""), 4000);
    return () => window.clearTimeout(timer);
  }, [infoMessage]);

  const filteredTargets = useMemo(() => {
    const value = targetFilters.keyword.trim().toLowerCase();
    const items = targets.filter((item) => {
      if (
        value &&
        !item.name.toLowerCase().includes(value) &&
        !item.serviceKey.toLowerCase().includes(value) &&
        !item.endpoint.toLowerCase().includes(value)
      ) {
        return false;
      }
      if (targetFilters.status && item.status !== targetFilters.status) {
        return false;
      }
      if (targetFilters.type && item.type !== targetFilters.type) {
        return false;
      }
      return true;
    });

    items.sort((left, right) => {
      if (targetFilters.sort === "name") {
        return left.name.localeCompare(right.name, "zh-CN");
      }
      if (targetFilters.sort === "type") {
        return targetTypeLabel(left.type).localeCompare(targetTypeLabel(right.type), "zh-CN");
      }
      const leftTime = left.lastRunAt ? new Date(left.lastRunAt).getTime() : 0;
      const rightTime = right.lastRunAt ? new Date(right.lastRunAt).getTime() : 0;
      return rightTime - leftTime;
    });

    return items;
  }, [targetFilters, targets]);

  const visibleRuns = useMemo(() => {
    let items = [...runs];
    if (runFilters.targetId) items = items.filter((item) => item.target?.id === runFilters.targetId);
    if (runFilters.status) items = items.filter((item) => item.status === runFilters.status);
    if (runFilters.latestPerTarget) {
      const latestByTarget = new Map<string, MonitorRun>();
      for (const item of items) {
        const targetId = item.target?.id ?? item.id;
        if (!latestByTarget.has(targetId)) latestByTarget.set(targetId, item);
      }
      items = Array.from(latestByTarget.values());
    }
    if (runFilters.recentOnly) {
      const limit = Math.max(Number(runFilters.limit) || 20, 1);
      items = items.slice(0, limit);
    }
    return items;
  }, [runFilters, runs]);

  const pagedRuns = useMemo(() => {
    const size = Math.max(Number(runPageSize) || 20, 1);
    const start = (runPage - 1) * size;
    return visibleRuns.slice(start, start + size);
  }, [runPage, runPageSize, visibleRuns]);

  const totalRunPages = useMemo(() => {
    const size = Math.max(Number(runPageSize) || 20, 1);
    return Math.max(Math.ceil(visibleRuns.length / size), 1);
  }, [runPageSize, visibleRuns.length]);

  const monthGroups = useMemo<RunMonthGroup[]>(() => {
    const groups = new Map<string, MonitorRun[]>();
    for (const run of pagedRuns) {
      const date = new Date(run.startedAt);
      const month = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
      const existing = groups.get(month) ?? [];
      existing.push(run);
      groups.set(month, existing);
    }
    return Array.from(groups.entries()).map(([month, monthRuns]) => ({
      month,
      label: `${month.slice(0, 4)} 年 ${month.slice(5)} 月`,
      runs: monthRuns,
    }));
  }, [pagedRuns]);

  const allVisibleRunIds = useMemo(() => visibleRuns.map((item) => item.id), [visibleRuns]);
  const allVisibleSelected = allVisibleRunIds.length > 0 && allVisibleRunIds.every((id) => selectedRunIds.includes(id));
  const selectedRunCount = selectedRunIds.length;

  const isHostAgentJavaForm = useMemo(() => {
    if (form.type !== "java-app") return false;
    const config = parseJsonObject(form.configJson);
    return (config.collectionMode ?? "builtin") === "host-agent";
  }, [form.configJson, form.type]);

  const hostAgentVisualForm = useMemo(() => deriveHostAgentVisualForm(form), [form]);
  const redisVisualForm = useMemo(() => deriveRedisVisualForm(form), [form]);
  const httpVisualForm = useMemo(() => deriveHttpVisualForm(form), [form]);
  const rabbitMqVisualForm = useMemo(() => deriveRabbitMqVisualForm(form), [form]);
  const elasticsearchVisualForm = useMemo(() => deriveElasticsearchVisualForm(form), [form]);
  const selectableAgents = useMemo(
    () => agents.filter((item) => item.platform === "linux" || item.platform === "windows"),
    [agents],
  );
  const selectedAgent = useMemo(
    () => selectableAgents.find((item) => item.agentKey === hostAgentVisualForm.agentKey) ?? null,
    [hostAgentVisualForm.agentKey, selectableAgents],
  );
  const selectedRedisAgent = useMemo(
    () => selectableAgents.find((item) => item.agentKey === redisVisualForm.agentKey) ?? null,
    [redisVisualForm.agentKey, selectableAgents],
  );
  const selectedHttpAgent = useMemo(
    () => selectableAgents.find((item) => item.agentKey === httpVisualForm.agentKey) ?? null,
    [httpVisualForm.agentKey, selectableAgents],
  );
  const selectedRabbitMqAgent = useMemo(
    () => selectableAgents.find((item) => item.agentKey === rabbitMqVisualForm.agentKey) ?? null,
    [rabbitMqVisualForm.agentKey, selectableAgents],
  );
  const selectedElasticsearchAgent = useMemo(
    () => selectableAgents.find((item) => item.agentKey === elasticsearchVisualForm.agentKey) ?? null,
    [elasticsearchVisualForm.agentKey, selectableAgents],
  );

  async function request<T>(path: string, init?: RequestInit) {
    const headers = new Headers(init?.headers);
    headers.set("Content-Type", "application/json");
    if (token) headers.set("Authorization", `Bearer ${token}`);

    try {
      const response = await fetch(`${apiBase}${path}`, { ...init, headers, cache: "no-store" });
      if (!response.ok) {
        let payload: unknown = null;
        try { payload = await response.json(); } catch { payload = null; }
        throw new Error(readErrorMessage(payload, apiBase));
      }
      return await response.json() as T;
    } catch (error) {
      throw new Error(readErrorMessage(error, apiBase));
    }
  }

  async function loadRunDetail(runId: string) {
    const detail = await request<MonitorRunDetail>(`/api/monitoring/runs/${runId}`);
    setSelectedRunId(runId);
    setSelectedRunDetail(detail);
  }

  useEffect(() => {
    setSelectedRunIds((current) => current.filter((id) => runs.some((item) => item.id === id)));
  }, [runs]);

  useEffect(() => {
    setRunPage(1);
  }, [runFilters, runPageSize]);

  useEffect(() => {
    if (runPage > totalRunPages) {
      setRunPage(totalRunPages);
    }
  }, [runPage, totalRunPages]);

  useEffect(() => {
    setCollapsedMonths((current) => {
      const knownMonths = monthGroups.map((item) => item.month);
      const next = current.filter((item) => knownMonths.includes(item));
      const currentMonth = new Date().toISOString().slice(0, 7);
      for (const group of monthGroups) {
        if (group.month !== currentMonth && !next.includes(group.month)) {
          next.push(group.month);
        }
      }
      return next;
    });
  }, [monthGroups]);

  async function refreshData(preferredRunId?: string) {
    if (!token) return;

    setRefreshing(true);
    setErrorMessage("");
    try {
      const [nextSummary, targetPayload, runPayload, agentPayload] = await Promise.all([
        request<MonitorSummary>("/api/monitoring/summary"),
        request<{ list: MonitorTarget[] }>("/api/monitoring/targets"),
        request<{ list: MonitorRun[] }>("/api/monitoring/runs?limit=200"),
        request<{ list: MonitorAgentOption[] }>("/api/monitoring/agents"),
      ]);

      setSummary(nextSummary);
      setTargets(targetPayload.list);
      setRuns(runPayload.list);
      setAgents(agentPayload.list);

      const nextRunId = preferredRunId ?? (selectedRunId && runPayload.list.some((item) => item.id === selectedRunId) ? selectedRunId : runPayload.list[0]?.id ?? null);
      if (nextRunId) await loadRunDetail(nextRunId);
      else {
        setSelectedRunId(null);
        setSelectedRunDetail(null);
      }
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setRefreshing(false);
    }
  }

  useEffect(() => {
    if (token) void refreshData();
  }, [token]);

  useEffect(() => {
    if (!props?.initialEditTargetId || !targets.length) return;
    const target = targets.find((item) => item.id === props.initialEditTargetId);
    if (!target) {
      setErrorMessage("要编辑的监测目标不存在，可能已被删除。");
      return;
    }

    setEditingTargetId(target.id);
    setForm({
      name: target.name,
      type: target.type,
      serviceKey: target.serviceKey,
      cronExpr: target.cronExpr,
      endpoint: target.endpoint,
      configJson: target.configJson,
      secretJson: target.secretJson ?? "{}",
    });
    setShowAdvancedJson(false);
  }, [props?.initialEditTargetId, targets]);

  useEffect(() => {
    if (props?.standaloneEdit || !props?.initialFocusTargetId || !targets.length || focusTargetApplied) return;
    const target = targets.find((item) => item.id === props.initialFocusTargetId);
    if (!target) return;
    setTargetFilters((current) => ({ ...current, keyword: target.name, status: current.status, type: current.type, sort: current.sort }));
    setRunFilters((current) => ({ ...current, targetId: target.id }));
    setEditingTargetId(target.id);
    setForm({
      name: target.name,
      type: target.type,
      serviceKey: target.serviceKey,
      cronExpr: target.cronExpr,
      endpoint: target.endpoint,
      configJson: target.configJson,
      secretJson: target.secretJson ?? "{}",
    });
    setShowAdvancedJson(false);
    setInfoMessage(`已定位到监测目标“${target.name}”。`);
    setFocusTargetApplied(true);
  }, [focusTargetApplied, props?.initialFocusTargetId, props?.standaloneEdit, targets]);

  useEffect(() => {
    if (!props?.initialFocusRunId || !runs.length || focusRunApplied) return;
    const run = runs.find((item) => item.id === props.initialFocusRunId);
    if (!run) return;
    void loadRunDetail(run.id);
    if (run.target?.id) {
      setRunFilters((current) => ({ ...current, targetId: run.target?.id ?? current.targetId }));
    }
    setInfoMessage(`已定位到监测记录 ${compactTraceId(run.traceId)}。`);
    setFocusRunApplied(true);
  }, [focusRunApplied, props?.initialFocusRunId, runs]);

  function resetForm(nextType?: TargetType) {
    const targetType = nextType ?? form.type;
    setEditingTargetId(null);
    setForm(templateByType[targetType]);
    setShowAdvancedJson(false);
    if (props?.standaloneEdit) {
      router.push("/monitors");
    }
  }

  function editTarget(target: MonitorTarget) {
    if (!props?.standaloneEdit) {
      router.push(`/monitors/${target.id}/edit`);
      return;
    }
    setEditingTargetId(target.id);
    setForm({ name: target.name, type: target.type, serviceKey: target.serviceKey, cronExpr: target.cronExpr, endpoint: target.endpoint, configJson: target.configJson, secretJson: target.secretJson ?? "{}" });
    setShowAdvancedJson(false);
    setInfoMessage("已加载监测目标配置，可直接修改并保存。");
  }

  async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setSubmitting(true);
    setErrorMessage("");

    try {
      JSON.parse(form.configJson);
      JSON.parse(form.secretJson || "{}");
      await request(editingTargetId ? `/api/monitoring/targets/${editingTargetId}` : "/api/monitoring/targets", {
        method: editingTargetId ? "PUT" : "POST",
        body: JSON.stringify({ ...form, secretJson: form.secretJson }),
      });
      setInfoMessage(editingTargetId ? "监测目标已更新。" : "监测目标已创建。");
      if (props?.standaloneEdit && editingTargetId) {
        router.push("/monitors");
      } else {
        resetForm(form.type);
      }
      await refreshData();
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setSubmitting(false);
    }
  }

  async function handleToggleTarget(target: MonitorTarget) {
    try {
      await request(`/api/monitoring/targets/${target.id}/status`, {
        method: "PATCH",
        body: JSON.stringify({ status: target.status === "enabled" ? "disabled" : "enabled" }),
      });
      setInfoMessage(target.status === "enabled" ? "监测目标已停用。" : "监测目标已启用。");
      await refreshData();
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    }
  }

  async function handleExecuteTarget(targetId: string, replaceQueued = false) {
    setExecutingId(targetId);
    setErrorMessage("");
    try {
      const result = await request<{ runId: string; clearedQueuedCount?: number }>(`/api/monitoring/runs/execute/${targetId}`, {
        method: "POST",
        body: JSON.stringify({ replaceQueued }),
      });
      setInfoMessage(
        replaceQueued
          ? `已创建最新测试任务，并清理 ${result.clearedQueuedCount ?? 0} 条旧排队记录。`
          : "监测任务已执行完成，最新记录已同步。",
      );
      await refreshData(result.runId);
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setExecutingId(null);
    }
  }

  function openConfirm(config: ConfirmState) {
    setConfirmState(config);
  }

  async function executeConfirm() {
    if (!confirmState) return;
    setConfirmBusy(true);
    try {
      await confirmState.action();
      setConfirmState(null);
    } finally {
      setConfirmBusy(false);
    }
  }

  function closeConfirm() {
    if (confirmBusy) return;
    setConfirmState(null);
  }

  async function handleClearQueuedRuns() {
    const scopeText = runFilters.targetId
      ? `当前目标“${targets.find((item) => item.id === runFilters.targetId)?.name ?? "未命名目标"}”`
      : "全部目标";
    openConfirm({
      title: "清理长期等待队列",
      message: `确认清理 ${scopeText} 下长期等待的排队记录吗？此操作会删除 queued 状态记录。`,
      confirmLabel: "确认清理",
      tone: "danger",
      action: async () => {
        setQueueAction("clear-queued");
        try {
          const result = await request<{ cleared: number }>("/api/monitoring/runs/actions/clear-queued", {
            method: "POST",
            body: JSON.stringify({
              targetId: runFilters.targetId || undefined,
              olderThanMinutes: Math.max(Number(queueThresholds.queuedOlderThanMinutes) || 10, 1),
            }),
          });
          setInfoMessage(`已清理 ${result.cleared} 条排队记录。`);
          await refreshData();
        } catch (error) {
          setErrorMessage(readErrorMessage(error));
        } finally {
          setQueueAction(null);
        }
      },
    });
  }

  async function handleRequeueStaleRuns() {
    const scopeText = runFilters.targetId
      ? `当前目标“${targets.find((item) => item.id === runFilters.targetId)?.name ?? "未命名目标"}”`
      : "全部目标";
    openConfirm({
      title: "重新派发阻塞执行",
      message: `确认重置 ${scopeText} 下疑似阻塞的执行吗？系统会把长时间 running 的记录改回 queued。`,
      confirmLabel: "确认重置",
      tone: "danger",
      action: async () => {
        setQueueAction("requeue-stale");
        try {
          const result = await request<{ requeued: number; staleMinutes: number }>("/api/monitoring/runs/actions/requeue-stale", {
            method: "POST",
            body: JSON.stringify({
              targetId: runFilters.targetId || undefined,
              staleMinutes: Math.max(Number(queueThresholds.staleRunningMinutes) || 20, 1),
            }),
          });
          setInfoMessage(`已重置 ${result.requeued} 条阻塞记录，判定窗口为 ${result.staleMinutes} 分钟。`);
          await refreshData();
        } catch (error) {
          setErrorMessage(readErrorMessage(error));
        } finally {
          setQueueAction(null);
        }
      },
    });
  }

  async function handleDeleteRun(runId: string) {
    openConfirm({
      title: "删除执行记录",
      message: "确认删除这条执行记录吗？删除后不会再出现在执行列表中。",
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        setQueueAction(`delete-${runId}`);
        try {
          await request(`/api/monitoring/runs/${runId}`, { method: "DELETE" });
          setRuns((current) => current.filter((item) => item.id !== runId));
          setInfoMessage("执行记录已删除。");
          if (selectedRunId === runId) {
            setSelectedRunId(null);
            setSelectedRunDetail(null);
          }
          await refreshData();
        } catch (error) {
          setErrorMessage(readErrorMessage(error));
        } finally {
          setQueueAction(null);
        }
      },
    });
  }

  async function handleDeleteSelectedRuns() {
    if (!selectedRunIds.length) return;
    openConfirm({
      title: "批量删除执行记录",
      message: `确认删除已选中的 ${selectedRunIds.length} 条执行记录吗？删除后无法恢复。`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        setQueueAction("delete-selected");
        try {
          const result = await request<{ deleted: number; runIds?: string[] }>("/api/monitoring/runs/actions/delete", {
            method: "POST",
            body: JSON.stringify({ runIds: selectedRunIds }),
          });
          const deletedIds = result.runIds ?? selectedRunIds;
          setRuns((current) => current.filter((item) => !deletedIds.includes(item.id)));
          setInfoMessage(`已删除 ${result.deleted} 条执行记录。`);
          if (selectedRunId && deletedIds.includes(selectedRunId)) {
            setSelectedRunId(null);
            setSelectedRunDetail(null);
          }
          setSelectedRunIds([]);
          await refreshData();
        } catch (error) {
          setErrorMessage(readErrorMessage(error));
        } finally {
          setQueueAction(null);
        }
      },
    });
  }

  async function handleDeleteVisibleRuns() {
    if (!allVisibleRunIds.length) return;
    openConfirm({
      title: "删除当前筛选结果",
      message: `确认删除当前筛选结果中的 ${allVisibleRunIds.length} 条执行记录吗？这个操作会一次性清掉当前过滤后的结果。`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        setQueueAction("delete-visible");
        try {
          const result = await request<{ deleted: number; runIds?: string[] }>("/api/monitoring/runs/actions/delete", {
            method: "POST",
            body: JSON.stringify({ runIds: allVisibleRunIds }),
          });
          const deletedIds = result.runIds ?? allVisibleRunIds;
          setRuns((current) => current.filter((item) => !deletedIds.includes(item.id)));
          setInfoMessage(`已删除当前筛选结果中的 ${result.deleted} 条记录。`);
          if (selectedRunId && deletedIds.includes(selectedRunId)) {
            setSelectedRunId(null);
            setSelectedRunDetail(null);
          }
          setSelectedRunIds((current) => current.filter((id) => !deletedIds.includes(id)));
          await refreshData();
        } catch (error) {
          setErrorMessage(readErrorMessage(error));
        } finally {
          setQueueAction(null);
        }
      },
    });
  }

  function toggleRunSelected(runId: string, checked: boolean) {
    setSelectedRunIds((current) => {
      if (checked) return current.includes(runId) ? current : [...current, runId];
      return current.filter((id) => id !== runId);
    });
  }

  function toggleVisibleRunsSelected(checked: boolean) {
    setSelectedRunIds((current) => {
      if (checked) {
        return Array.from(new Set([...current, ...allVisibleRunIds]));
      }
      return current.filter((id) => !allVisibleRunIds.includes(id));
    });
  }

  function toggleMonthCollapsed(month: string) {
    setCollapsedMonths((current) => current.includes(month) ? current.filter((item) => item !== month) : [...current, month]);
  }

  async function handleRequeueRun(runId: string) {
    setQueueAction(`requeue-${runId}`);
    try {
      await request(`/api/monitoring/runs/${runId}/requeue`, { method: "POST" });
      setInfoMessage("这条执行记录已重新入队，等待 Agent 重新领取。");
      await refreshData(runId);
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setQueueAction(null);
    }
  }

  async function handleDeleteTarget(target: MonitorTarget) {
    openConfirm({
      title: "删除监测目标",
      message: `确认删除目标“${target.name}”吗？关联执行记录会一并删除。`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        try {
          await request(`/api/monitoring/targets/${target.id}`, { method: "DELETE" });
          setInfoMessage("监测目标已删除。");
          if (props?.standaloneEdit) {
            router.push("/monitors");
          } else {
            await refreshData();
          }
        } catch (error) {
          setErrorMessage(readErrorMessage(error));
        }
      },
    });
  }

  const runSummary = useMemo(() => ({
    total: runs.length,
    success: runs.filter((item) => item.status === "success").length,
    exception: runs.filter((item) => item.status === "failed" || item.status === "partial").length,
    running: runs.filter((item) => item.status === "running").length,
    queued: runs.filter((item) => item.status === "queued").length,
  }), [runs]);

  function downloadJson(fileName: string, payload: unknown) {
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
    const url = window.URL.createObjectURL(blob);
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = fileName;
    document.body.appendChild(anchor);
    anchor.click();
    anchor.remove();
    window.URL.revokeObjectURL(url);
  }

  function exportTargets() {
    downloadJson("monitor-targets.json", filteredTargets);
    setInfoMessage(`已导出 ${filteredTargets.length} 条监测目标。`);
  }

  function exportRuns() {
    downloadJson("monitor-runs.json", visibleRuns);
    setInfoMessage(`已导出 ${visibleRuns.length} 条执行记录。`);
  }

  return (
    <>
    <AdminFrame
      section="monitors"
      title="环境监测"
      description="围绕监测目标、执行记录、证据与批量处理组织页面路径，优先提高完成任务效率。"
      actions={<PageStatus busy={refreshing} busyLabel="同步中" idleLabel="已就绪" />}
    >
      <NoticeBanner message={infoMessage} />
      <ErrorBanner message={errorMessage} />

      <section className={styles.mainContent}>
        {props?.standaloneEdit ? (
          <section className={styles.panel}>
            <div className={styles.pageHeader}>
              <div>
                <p className={styles.pageKicker}>独立编辑页</p>
                <h2>编辑监测目标</h2>
                <p>修改在独立页面完成，避免列表、表单和执行记录互相干扰。保存后会自动返回目标列表。</p>
              </div>
              <div className={styles.buttonRow}>
                <Link className={styles.linkButton} href="/monitors">返回目标列表</Link>
              </div>
            </div>
          </section>
        ) : null}

        {!props?.standaloneEdit ? <section className={styles.panel}>
          <div className={styles.pageHeader}>
            <div>
              <p className={styles.pageKicker}>监测概览</p>
              <h2>当前环境接入情况</h2>
            </div>
            <div className={styles.buttonRow}><button className={styles.secondaryButton} onClick={() => void refreshData()} type="button">刷新数据</button></div>
          </div>
          <div className={styles.statsGrid}>
            <StatCard label="监测目标" value={summary?.targetCount ?? 0} />
            <StatCard label="执行次数" value={summary?.runCount ?? 0} />
            <StatCard label="异常次数" value={summary?.exceptionCount ?? 0} tone="danger" />
            <StatCard label="运行中" value={runSummary.running} tone="warning" />
          </div>
        </section> : null}

        <section className={styles.panel}>
          <div className={styles.pageHeader}>
            <div>
              <p className={styles.pageKicker}>接入配置</p>
              <h2>{editingTargetId ? "编辑监测目标" : "新增监测目标"}</h2>
              <p>这里创建的是“监测目标”，不是 Agent 安装包。正确流程是：先到 Agent 节点页面生成并部署 Agent，再回到这里绑定节点、配置要监测的服务。</p>
            </div>
            <div className={styles.buttonRow}>
              <Link className={styles.linkButton} href="/agents">前往 Agent 节点页面</Link>
            </div>
          </div>

          <div className={styles.tipPanel}>
            <strong>先做什么</strong>
            <p>1. 去“Agent 节点”页面创建节点并下载部署包。2. 把部署包放到对应 Linux 服务器安装运行。3. 等节点在线后，再回来创建 Redis、MQ、ES、Java 等监测目标。</p>
          </div>

          <div className={styles.taskComposer}>
            <form className={styles.form} onSubmit={handleSubmit}>
              <div className={styles.formGrid}>
                <Field label="目标类型" hint="选择接入协议后，表单会自动切换到推荐模板。">
                  <select value={form.type} onChange={(event) => { const nextType = event.target.value as TargetType; setEditingTargetId(null); setForm(templateByType[nextType]); }}>
                    {typeOptions.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
                  </select>
                </Field>
                <Field label="服务标识" hint="一个逻辑服务固定一个 service_key；多节点部署时通过 nodeKey 区分节点。">
                  <input value={form.serviceKey} onChange={(event) => setForm((current) => ({ ...current, serviceKey: event.target.value }))} />
                </Field>
                <Field label="目标名称" hint="建议用环境 + 组件名，例如“测试环境 Redis 主节点”。">
                  <input value={form.name} onChange={(event) => setForm((current) => ({ ...current, name: event.target.value }))} />
                </Field>
                <Field label="执行周期" hint="使用 cron 表达式，例如 */5 * * * * 表示每 5 分钟一次。">
                  <input value={form.cronExpr} onChange={(event) => setForm((current) => ({ ...current, cronExpr: event.target.value }))} />
                </Field>
              </div>
              <Field label="接入地址" hint="HTTP 类填写完整 URL；TCP/Redis 可填写主机名或 IP。">
                <input value={form.endpoint} onChange={(event) => setForm((current) => ({ ...current, endpoint: event.target.value }))} />
              </Field>

              {isHostAgentJavaForm ? (
                <section className={styles.formSection}>
                  <div className={styles.sectionHeader}>
                    <div>
                      <h3>宿主机 Agent 配置</h3>
                      <p>这里填写“Agent 在哪台机器上执行、如何定位目标 Java 进程、抓哪些 JVM 指标和日志”。保存时会自动转换成平台内部的 config_json / secret_json。</p>
                    </div>
                    <span className={styles.strategyTag}>无需手写 JSON</span>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>配置顺序</strong>
                    <p>先选已经部署并在线的 Agent 节点，再填写进程关键字和日志路径。`jcmd`、`jstat` 只在你需要更深的 JVM 诊断时再填，普通监测可以先留空。</p>
                  </div>

                  <div className={styles.formGrid}>
                    <Field label="Agent 节点" hint="优先直接选择平台里已登记的 Agent 节点，避免 agentKey 手工填错。">
                      <select
                        value={hostAgentVisualForm.agentKey}
                        onChange={(event) =>
                          setForm((current) =>
                            syncHostAgentJson(current, { agentKey: event.target.value }),
                          )
                        }
                      >
                        <option value="">请选择已登记的 Agent 节点</option>
                        {selectableAgents.map((agent) => (
                          <option key={agent.id} value={agent.agentKey}>
                            {agent.name} / {agent.agentKey}
                            {agent.host ? ` / ${agent.host}` : ""}
                          </option>
                        ))}
                      </select>
                    </Field>
                    <Field label="节点标识" hint="同一个 service_key 多节点部署时，用它区分具体节点。">
                      <input value={hostAgentVisualForm.nodeKey} onChange={(event) => setForm((current) => syncHostAgentJson(current, { nodeKey: event.target.value }))} />
                    </Field>
                    <Field label="主机名" hint="用于报告展示，例如 app-server-01 或 192.168.1.45。">
                      <input value={hostAgentVisualForm.host} onChange={(event) => setForm((current) => syncHostAgentJson(current, { host: event.target.value }))} />
                    </Field>
                    <Field label="进程匹配方式" hint="推荐用“命令行关键字”匹配 jar 包名或启动参数，更灵活。">
                      <select value={hostAgentVisualForm.processMatchMode} onChange={(event) => setForm((current) => syncHostAgentJson(current, { processMatchMode: event.target.value as "pid" | "command" }))}>
                        <option value="command">命令行关键字</option>
                        <option value="pid">固定 PID</option>
                      </select>
                    </Field>
                    <Field label={hostAgentVisualForm.processMatchMode === "pid" ? "目标 PID" : "命令行关键字"} hint={hostAgentVisualForm.processMatchMode === "pid" ? "例如 33420。适合短期定位单个固定进程。" : "例如 java-demo-app-0.0.1-SNAPSHOT.jar 或服务独有参数。"}>
                      <input value={hostAgentVisualForm.processMatchValue} onChange={(event) => setForm((current) => syncHostAgentJson(current, { processMatchValue: event.target.value }))} />
                    </Field>
                    <Field label="日志文件路径" hint="Agent 会从这个文件里提取最近的异常片段，不会全量上传整个日志。">
                      <input value={hostAgentVisualForm.logFilePath} onChange={(event) => setForm((current) => syncHostAgentJson(current, { logFilePath: event.target.value }))} />
                    </Field>
                    <Field label="jcmd 路径" hint="可选。只有要采集 JVM 深度信息时才填，Linux 常见路径如 /usr/bin/jcmd。">
                      <input value={hostAgentVisualForm.jcmdPath} onChange={(event) => setForm((current) => syncHostAgentJson(current, { jcmdPath: event.target.value }))} />
                    </Field>
                    <Field label="jstat 路径" hint="可选。只有要采集 GC/内存统计时才填，Linux 常见路径如 /usr/bin/jstat。">
                      <input value={hostAgentVisualForm.jstatPath} onChange={(event) => setForm((current) => syncHostAgentJson(current, { jstatPath: event.target.value }))} />
                    </Field>
                    <Field label="日志回看行数" hint="建议 80 到 200 行，既能看到上下文，也不至于太重。">
                      <input value={hostAgentVisualForm.logTailLines} onChange={(event) => setForm((current) => syncHostAgentJson(current, { logTailLines: event.target.value }))} />
                    </Field>
                    <Field label="日志关键字" hint="支持正则，优先保留 ERROR、Exception、Timeout 这类异常关键词。">
                      <input value={hostAgentVisualForm.logKeyword} onChange={(event) => setForm((current) => syncHostAgentJson(current, { logKeyword: event.target.value }))} />
                    </Field>
                  </div>

                  <div className={styles.formGrid}>
                    <Field label="jcmd 指令列表" hint="一行一条。推荐保留 VM.command_line、GC.heap_info。">
                      <textarea rows={5} value={hostAgentVisualForm.jcmdCommandsText} onChange={(event) => setForm((current) => syncHostAgentJson(current, { jcmdCommandsText: event.target.value }))} />
                    </Field>
                    <Field label="jstat 参数列表" hint="一行一条。推荐先用 -gcutil。">
                      <textarea rows={5} value={hostAgentVisualForm.jstatOptionsText} onChange={(event) => setForm((current) => syncHostAgentJson(current, { jstatOptionsText: event.target.value }))} />
                    </Field>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>推荐配置方式</strong>
                    <p>阶段 1 建议优先用“命令行关键字”匹配进程，再结合 `jcmd + jstat + 日志文件路径` 取证。这样不需要改原有 Java 程序，也不依赖程序暴露 Actuator。</p>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>当前绑定的 Agent 节点</strong>
                    <p>
                      {selectedAgent
                        ? `${selectedAgent.name} / ${selectedAgent.agentKey} / ${selectedAgent.host ?? "未填写主机"} / ${selectedAgent.status === "enabled" ? "已启用" : "停用"}`
                        : "还没有选中 Agent 节点。建议先去 /agents 创建并启用节点，再回来绑定。"}
                    </p>
                  </div>
                </section>
              ) : null}

              {form.type === "redis" ? (
                <section className={styles.formSection}>
                  <div className={styles.sectionHeader}>
                    <div>
                      <h3>Redis 监测配置</h3>
                      <p>同一个 Agent 节点可以同时监测 Java、Redis、MQ、ES。你现在如果已经有 `linux-IIP-01` 在线，完全可以继续让它去执行 Redis 监测。</p>
                    </div>
                    <span className={styles.strategyTag}>密码可视化配置</span>
                  </div>

                  <div className={styles.formGrid}>
                    <Field label="执行方式" hint="内置执行表示平台直接连 Redis；宿主机 Agent 表示让指定服务器上的 Agent 去执行。">
                      <select value={redisVisualForm.collectionMode} onChange={(event) => setForm((current) => syncRedisJson(current, { collectionMode: event.target.value as "builtin" | "host-agent" }))}>
                        <option value="builtin">平台直连</option>
                        <option value="host-agent">宿主机 Agent</option>
                      </select>
                    </Field>

                    {redisVisualForm.collectionMode === "host-agent" ? (
                      <Field label="Agent 节点" hint="选择已经在线的节点，例如你当前的 `linux-IIP-01`。同一节点可挂多个监测目标。">
                        <select value={redisVisualForm.agentKey} onChange={(event) => setForm((current) => syncRedisJson(current, { agentKey: event.target.value }))}>
                          <option value="">请选择已登记的 Agent 节点</option>
                          {selectableAgents.map((agent) => (
                            <option key={agent.id} value={agent.agentKey}>
                              {agent.name} / {agent.agentKey}
                              {agent.host ? ` / ${agent.host}` : ""}
                            </option>
                          ))}
                        </select>
                      </Field>
                    ) : (
                      <Field label="说明" hint="当前模式下由平台直接连接 Redis，不依赖宿主机 Agent。">
                        <input disabled value="当前不需要绑定 Agent 节点" />
                      </Field>
                    )}

                    <Field label="Redis 主机" hint="填写 Redis 实际地址，例如 192.168.1.100。">
                      <input value={redisVisualForm.tcpHost} onChange={(event) => setForm((current) => syncRedisJson(current, { tcpHost: event.target.value }))} />
                    </Field>
                    <Field label="Redis 端口" hint="默认 6379。">
                      <input value={redisVisualForm.tcpPort} onChange={(event) => setForm((current) => syncRedisJson(current, { tcpPort: event.target.value }))} />
                    </Field>
                    <Field label="超时时间(ms)" hint="建议 3000 到 8000。">
                      <input value={redisVisualForm.timeoutMs} onChange={(event) => setForm((current) => syncRedisJson(current, { timeoutMs: event.target.value }))} />
                    </Field>
                    <Field label="Redis 密码" hint="如果 Redis 开了密码，这里直接填。系统会保存到 secret_json，并在执行 PING/INFO 时自动带上认证。">
                      <input type="password" value={redisVisualForm.authPassword} onChange={(event) => setForm((current) => syncRedisJson(current, { authPassword: event.target.value }))} />
                    </Field>
                    <Field label="日志文件路径" hint="如果你希望 Agent 顺带抓 Redis 相关日志摘录，这里填服务器上的日志文件路径。">
                      <input value={redisVisualForm.logFilePath} onChange={(event) => setForm((current) => syncRedisJson(current, { logFilePath: event.target.value }))} />
                    </Field>
                    <Field label="日志回看行数" hint="建议 80 到 200 行。Redis 日志通常不需要太大窗口。">
                      <input value={redisVisualForm.logTailLines} onChange={(event) => setForm((current) => syncRedisJson(current, { logTailLines: event.target.value }))} />
                    </Field>
                    <Field label="日志关键字" hint="支持正则，建议先保留 ERROR、FAIL、OOM、Timeout 等关键词。">
                      <input value={redisVisualForm.logKeyword} onChange={(event) => setForm((current) => syncRedisJson(current, { logKeyword: event.target.value }))} />
                    </Field>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>当前 Redis 执行节点</strong>
                    <p>
                      {redisVisualForm.collectionMode === "host-agent"
                        ? selectedRedisAgent
                          ? `${selectedRedisAgent.name} / ${selectedRedisAgent.agentKey} / ${selectedRedisAgent.host ?? "未填写主机"} / ${selectedRedisAgent.status === "enabled" ? "已启用" : "停用"}`
                          : "还没有选中 Agent 节点。若你想让服务器上的 Agent 去探测 Redis，请先选择一个在线节点。"
                        : "当前使用平台直连模式，不通过宿主机 Agent 执行。"}
                    </p>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>Redis 日志采集说明</strong>
                    <p>如果你走的是宿主机 Agent 模式，且这里配置了日志文件路径，Agent 会在执行 Redis 检测后顺带提取最近的异常日志片段。平台直连模式下不建议直接读远程日志。</p>
                  </div>
                </section>
              ) : null}

              {form.type === "http" ? (
                <section className={styles.formSection}>
                  <div className={styles.sectionHeader}>
                    <div>
                      <h3>HTTP 监测配置</h3>
                      <p>把地址、鉴权和执行方式放到可视化表单里。常见健康接口、Basic Auth 和 blackbox 都可以直接配置，不必再手写 JSON。</p>
                    </div>
                    <span className={styles.strategyTag}>HTTP Basic Auth</span>
                  </div>

                  <div className={styles.formGrid}>
                    <Field label="执行方式" hint="blackbox 适合统一探测；平台直连适合简单健康检查；宿主机 Agent 适合受内网限制的地址。">
                      <select value={httpVisualForm.collectionMode} onChange={(event) => setForm((current) => syncHttpJson(current, { collectionMode: event.target.value as "blackbox" | "builtin" | "host-agent" }))}>
                        <option value="blackbox">blackbox_exporter</option>
                        <option value="builtin">平台直连</option>
                        <option value="host-agent">宿主机 Agent</option>
                      </select>
                    </Field>
                    {httpVisualForm.collectionMode === "host-agent" ? (
                      <Field label="Agent 节点" hint="受网络隔离影响时，让指定 Agent 在服务器本机发起 HTTP 监测。">
                        <select value={httpVisualForm.agentKey} onChange={(event) => setForm((current) => syncHttpJson(current, { agentKey: event.target.value }))}>
                          <option value="">请选择已登记的 Agent 节点</option>
                          {selectableAgents.map((agent) => (
                            <option key={agent.id} value={agent.agentKey}>
                              {agent.name} / {agent.agentKey}
                              {agent.host ? ` / ${agent.host}` : ""}
                            </option>
                          ))}
                        </select>
                      </Field>
                    ) : (
                      <Field label="Agent 节点" hint="当前模式不需要绑定 Agent。">
                        <input disabled value="当前不需要绑定 Agent 节点" />
                      </Field>
                    )}
                    <Field label="HTTP 地址" hint="填写完整 URL，例如 http://192.168.1.30:8080/health。">
                      <input value={httpVisualForm.url} onChange={(event) => setForm((current) => syncHttpJson(current, { url: event.target.value }))} />
                    </Field>
                    <Field label="超时时间(ms)" hint="建议 3000 到 10000。">
                      <input value={httpVisualForm.timeoutMs} onChange={(event) => setForm((current) => syncHttpJson(current, { timeoutMs: event.target.value }))} />
                    </Field>
                    <Field label="期望状态码" hint="默认 200。健康接口返回 204 时，也可以改成 204。">
                      <input value={httpVisualForm.expectedStatus} onChange={(event) => setForm((current) => syncHttpJson(current, { expectedStatus: event.target.value }))} />
                    </Field>
                    {httpVisualForm.collectionMode === "blackbox" ? (
                      <>
                        <Field label="blackbox 地址" hint="例如 http://192.168.1.50:9115。">
                          <input value={httpVisualForm.blackboxBaseUrl} onChange={(event) => setForm((current) => syncHttpJson(current, { blackboxBaseUrl: event.target.value }))} />
                        </Field>
                        <Field label="blackbox 模块" hint="默认 http_2xx。特殊场景可切换到自定义 module。">
                          <input value={httpVisualForm.blackboxModule} onChange={(event) => setForm((current) => syncHttpJson(current, { blackboxModule: event.target.value }))} />
                        </Field>
                      </>
                    ) : null}
                    <Field label="Basic Auth 用户名" hint="无鉴权可留空。填写后会在请求中自动带上 Basic Auth。">
                      <input value={httpVisualForm.authUsername} onChange={(event) => setForm((current) => syncHttpJson(current, { authUsername: event.target.value }))} />
                    </Field>
                    <Field label="Basic Auth 密码" hint="无鉴权可留空。">
                      <input type="password" value={httpVisualForm.authPassword} onChange={(event) => setForm((current) => syncHttpJson(current, { authPassword: event.target.value }))} />
                    </Field>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>当前 HTTP 执行节点</strong>
                    <p>
                      {httpVisualForm.collectionMode === "host-agent"
                        ? selectedHttpAgent
                          ? `${selectedHttpAgent.name} / ${selectedHttpAgent.agentKey} / ${selectedHttpAgent.host ?? "未填写主机"} / ${selectedHttpAgent.status === "enabled" ? "已启用" : "停用"}`
                          : "还没有选中 Agent 节点。"
                        : "当前由平台或 blackbox_exporter 直接执行，不通过宿主机 Agent。"}
                    </p>
                  </div>
                </section>
              ) : null}

              {form.type === "rabbitmq" ? (
                <section className={styles.formSection}>
                  <div className={styles.sectionHeader}>
                    <div>
                      <h3>RabbitMQ 监测配置</h3>
                      <p>把 Management API 地址和账号密码直接配出来。这样后续接企业内 RabbitMQ 时，不用再翻高级 JSON。</p>
                    </div>
                    <span className={styles.strategyTag}>用户名 / 密码</span>
                  </div>

                  <div className={styles.formGrid}>
                    <Field label="执行方式" hint="能直连就用平台直连；内网隔离时再切宿主机 Agent。">
                      <select value={rabbitMqVisualForm.collectionMode} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { collectionMode: event.target.value as "builtin" | "host-agent" }))}>
                        <option value="builtin">平台直连</option>
                        <option value="host-agent">宿主机 Agent</option>
                      </select>
                    </Field>
                    {rabbitMqVisualForm.collectionMode === "host-agent" ? (
                      <Field label="Agent 节点" hint="让指定 Agent 在目标网络内调用 RabbitMQ management API。">
                        <select value={rabbitMqVisualForm.agentKey} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { agentKey: event.target.value }))}>
                          <option value="">请选择已登记的 Agent 节点</option>
                          {selectableAgents.map((agent) => (
                            <option key={agent.id} value={agent.agentKey}>
                              {agent.name} / {agent.agentKey}
                              {agent.host ? ` / ${agent.host}` : ""}
                            </option>
                          ))}
                        </select>
                      </Field>
                    ) : (
                      <Field label="Agent 节点" hint="当前模式不需要绑定 Agent。">
                        <input disabled value="当前不需要绑定 Agent 节点" />
                      </Field>
                    )}
                    <Field label="Management 地址" hint="例如 http://192.168.1.10:15672。">
                      <input value={rabbitMqVisualForm.baseUrl} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { baseUrl: event.target.value }))} />
                    </Field>
                    <Field label="用户名" hint="通常是 RabbitMQ management 的登录用户。">
                      <input value={rabbitMqVisualForm.authUsername} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { authUsername: event.target.value }))} />
                    </Field>
                    <Field label="密码" hint="Management API 密码。">
                      <input type="password" value={rabbitMqVisualForm.authPassword} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { authPassword: event.target.value }))} />
                    </Field>
                    <Field label="Overview 路径" hint="默认 /api/overview。一般无需改。">
                      <input value={rabbitMqVisualForm.overviewPath} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { overviewPath: event.target.value }))} />
                    </Field>
                    <Field label="Queues 路径" hint="默认 /api/queues。">
                      <input value={rabbitMqVisualForm.queuesPath} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { queuesPath: event.target.value }))} />
                    </Field>
                    <Field label="Nodes 路径" hint="默认 /api/nodes。">
                      <input value={rabbitMqVisualForm.nodesPath} onChange={(event) => setForm((current) => syncRabbitMqJson(current, { nodesPath: event.target.value }))} />
                    </Field>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>当前 RabbitMQ 执行节点</strong>
                    <p>
                      {rabbitMqVisualForm.collectionMode === "host-agent"
                        ? selectedRabbitMqAgent
                          ? `${selectedRabbitMqAgent.name} / ${selectedRabbitMqAgent.agentKey} / ${selectedRabbitMqAgent.host ?? "未填写主机"} / ${selectedRabbitMqAgent.status === "enabled" ? "已启用" : "停用"}`
                          : "还没有选中 Agent 节点。"
                        : "当前由平台直接调用 RabbitMQ management API。"}
                    </p>
                  </div>
                </section>
              ) : null}

              {form.type === "elasticsearch" ? (
                <section className={styles.formSection}>
                  <div className={styles.sectionHeader}>
                    <div>
                      <h3>Elasticsearch 监测配置</h3>
                      <p>把认证方式、用户名密码或 Bearer Token 显式展示出来。这样接企业 ES 时，表单可读性会高很多。</p>
                    </div>
                    <span className={styles.strategyTag}>认证配置</span>
                  </div>

                  <div className={styles.formGrid}>
                    <Field label="执行方式" hint="优先平台直连，网络隔离时再切宿主机 Agent。">
                      <select value={elasticsearchVisualForm.collectionMode} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { collectionMode: event.target.value as "builtin" | "host-agent" }))}>
                        <option value="builtin">平台直连</option>
                        <option value="host-agent">宿主机 Agent</option>
                      </select>
                    </Field>
                    {elasticsearchVisualForm.collectionMode === "host-agent" ? (
                      <Field label="Agent 节点" hint="让 Agent 在目标网络内访问 ES。">
                        <select value={elasticsearchVisualForm.agentKey} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { agentKey: event.target.value }))}>
                          <option value="">请选择已登记的 Agent 节点</option>
                          {selectableAgents.map((agent) => (
                            <option key={agent.id} value={agent.agentKey}>
                              {agent.name} / {agent.agentKey}
                              {agent.host ? ` / ${agent.host}` : ""}
                            </option>
                          ))}
                        </select>
                      </Field>
                    ) : (
                      <Field label="Agent 节点" hint="当前模式不需要绑定 Agent。">
                        <input disabled value="当前不需要绑定 Agent 节点" />
                      </Field>
                    )}
                    <Field label="ES 地址" hint="例如 http://192.168.1.10:9200。">
                      <input value={elasticsearchVisualForm.baseUrl} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { baseUrl: event.target.value }))} />
                    </Field>
                    <Field label="认证方式" hint="无认证时选“无”；常见内网 ES 可选 Basic 或 Bearer。">
                      <select value={elasticsearchVisualForm.authMode} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { authMode: event.target.value as "none" | "basic" | "bearer" }))}>
                        <option value="none">无认证</option>
                        <option value="basic">Basic Auth</option>
                        <option value="bearer">Bearer Token</option>
                      </select>
                    </Field>
                    {elasticsearchVisualForm.authMode === "basic" ? (
                      <>
                        <Field label="用户名" hint="Basic Auth 用户名。">
                          <input value={elasticsearchVisualForm.authUsername} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { authUsername: event.target.value }))} />
                        </Field>
                        <Field label="密码" hint="Basic Auth 密码。">
                          <input type="password" value={elasticsearchVisualForm.authPassword} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { authPassword: event.target.value }))} />
                        </Field>
                      </>
                    ) : null}
                    {elasticsearchVisualForm.authMode === "bearer" ? (
                      <Field label="Bearer Token" hint="例如 Elasticsearch API Token。">
                        <input type="password" value={elasticsearchVisualForm.bearerToken} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { bearerToken: event.target.value }))} />
                      </Field>
                    ) : null}
                    <Field label="健康路径" hint="默认 /_cluster/health。">
                      <input value={elasticsearchVisualForm.healthPath} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { healthPath: event.target.value }))} />
                    </Field>
                    <Field label="节点路径" hint="默认 /_nodes/stats。">
                      <input value={elasticsearchVisualForm.nodesPath} onChange={(event) => setForm((current) => syncElasticsearchJson(current, { nodesPath: event.target.value }))} />
                    </Field>
                  </div>

                  <div className={styles.tipPanel}>
                    <strong>当前 Elasticsearch 执行节点</strong>
                    <p>
                      {elasticsearchVisualForm.collectionMode === "host-agent"
                        ? selectedElasticsearchAgent
                          ? `${selectedElasticsearchAgent.name} / ${selectedElasticsearchAgent.agentKey} / ${selectedElasticsearchAgent.host ?? "未填写主机"} / ${selectedElasticsearchAgent.status === "enabled" ? "已启用" : "停用"}`
                          : "还没有选中 Agent 节点。"
                        : "当前由平台直接访问 Elasticsearch。"}
                    </p>
                  </div>
                </section>
              ) : null}

              <details className={styles.advancedSection} open={!isHostAgentJavaForm || showAdvancedJson} onToggle={(event) => setShowAdvancedJson((event.target as HTMLDetailsElement).open)}>
                <summary>高级配置：查看或手动编辑 JSON</summary>
                <div className={styles.advancedBody}>
                  <Field label="config_json" hint="放超时、检测路径、端口、指标采集项等非敏感配置。">
                    <textarea rows={14} value={form.configJson} onChange={(event) => setForm((current) => ({ ...current, configJson: event.target.value }))} />
                  </Field>
                  <Field label="secret_json" hint="放密码、认证信息、SSH 与日志文件路径。暂时没有可填 {}。">
                    <textarea rows={12} value={form.secretJson} onChange={(event) => setForm((current) => ({ ...current, secretJson: event.target.value }))} />
                  </Field>
                </div>
              </details>
              <div className={styles.buttonRow}>
                <button disabled={submitting} type="submit">{submitting ? "保存中..." : editingTargetId ? "保存修改" : "创建目标"}</button>
                <button className={styles.secondaryButton} onClick={() => resetForm(form.type)} type="button">重置为模板</button>
              </div>
            </form>

            <aside className={styles.stack}>
              <article className={styles.strategyCard}>
                <div className={styles.cardHeader}>
                  <div>
                    <p className={styles.pageKicker}>接入建议</p>
                    <h3>{targetTypeLabel(form.type)}</h3>
                  </div>
                  <span className={styles.strategyTag}>低侵入优先</span>
                </div>
                <p>{typeOptions.find((option) => option.value === form.type)?.description}</p>
                <ul className={styles.guideList}>
                  <li>HTTP 和 TCP 优先建议接入 blackbox_exporter，平台只做配置编排和结果聚合。</li>
                  <li>Redis 优先用 PING 与 INFO，不建议阶段 1 就跑高成本命令。</li>
                  <li>RabbitMQ 优先接 management API，而不是直接扫 Erlang 内部状态。</li>
                  <li>如果不想改原有 Java 程序，推荐改用宿主机 Agent：按进程匹配 + jcmd/jstat + 日志 tail。</li>
                </ul>
              </article>

              <article className={styles.guideCard}>
                <div className={styles.cardHeader}>
                  <div>
                    <p className={styles.pageKicker}>日志策略</p>
                    <h3>推荐做法</h3>
                  </div>
                </div>
                <ul className={styles.guideList}>
                  <li>默认先做健康检查和轻量指标采集，日志作为补充证据。</li>
                  <li>只有配置了 SSH 与日志路径时，系统才会抓取最近错误摘要。</li>
                  <li>建议只保留最近 40 行匹配关键字的日志片段，避免对业务机造成压力。</li>
                </ul>
              </article>
            </aside>
          </div>
        </section>

        {!props?.standaloneEdit ? <section className={styles.panel}>
          <ResourceToolbar title="目标列表" description="搜索、筛选、排序和导出都在同一处完成，避免长列表反复滚动。">
            <label className={styles.inlineField}>
              <span>筛选</span>
                <input
                  placeholder="按名称、service_key、地址筛选"
                  value={targetFilters.keyword}
                  onChange={(event) =>
                    setTargetFilters((current) => ({ ...current, keyword: event.target.value }))
                  }
                />
              </label>
              <label className={styles.inlineField}>
                <span>状态</span>
                <select
                  value={targetFilters.status}
                  onChange={(event) =>
                    setTargetFilters((current) => ({
                      ...current,
                      status: event.target.value as "" | TargetStatus,
                    }))
                  }
                >
                  <option value="">全部状态</option>
                  <option value="enabled">启用</option>
                  <option value="disabled">停用</option>
                </select>
              </label>
              <label className={styles.inlineField}>
                <span>类型</span>
                <select
                  value={targetFilters.type}
                  onChange={(event) =>
                    setTargetFilters((current) => ({
                      ...current,
                      type: event.target.value as "" | TargetType,
                    }))
                  }
                >
                  <option value="">全部类型</option>
                  {typeOptions.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
              </label>
              <label className={styles.inlineField}>
                <span>排序</span>
                <select
                  value={targetFilters.sort}
                  onChange={(event) =>
                    setTargetFilters((current) => ({
                      ...current,
                      sort: event.target.value as "recent" | "name" | "type",
                    }))
                  }
                >
                  <option value="recent">最近执行</option>
                  <option value="name">名称</option>
                  <option value="type">类型</option>
                </select>
              </label>
              <div className={styles.buttonRow}>
                <button className={styles.secondaryButton} onClick={exportTargets} type="button">
                  导出
                </button>
                <button
                  className={styles.secondaryButton}
                  onClick={() => resetForm(form.type)}
                  type="button"
                >
                  新建目标
                </button>
              </div>
          </ResourceToolbar>
            <div className={styles.taskList}>
              {filteredTargets.length ? filteredTargets.map((target) => (
              <ResourceCard key={target.id}>
                <ResourceCardHeader
                  title={target.name}
                  subtitle={target.endpoint}
                  badges={
                    <>
                      <span className={styles.typeBadge}>{targetTypeLabel(target.type)}</span>
                      <span className={styles.badge} data-status={target.status}>{statusLabel(target.status)}</span>
                    </>
                  }
                  actions={
                    <>
                      <button className={styles.secondaryButton} onClick={() => editTarget(target)} type="button">编辑</button>
                      <button className={styles.secondaryButton} onClick={() => void handleToggleTarget(target)} type="button">{target.status === "enabled" ? "停用" : "启用"}</button>
                      <button disabled={executingId === target.id} onClick={() => void handleExecuteTarget(target.id)} type="button">{executingId === target.id ? "执行中..." : "立即执行"}</button>
                      <button className={styles.secondaryButton} disabled={executingId === target.id} onClick={() => void handleExecuteTarget(target.id, true)} type="button">仅测试最新一次</button>
                      <button className={styles.ghostDangerButton} onClick={() => void handleDeleteTarget(target)} type="button">删除</button>
                    </>
                  }
                >
                  <div className={styles.metaChipList}>
                    <span className={styles.metaChip}><strong>service_key</strong>{target.serviceKey}</span>
                    <span className={styles.metaChip}><strong>执行周期</strong>{target.cronExpr}</span>
                    <span className={styles.metaChip}><strong>最近执行</strong>{formatTime(target.lastRunAt)}</span>
                  </div>
                </ResourceCardHeader>
              </ResourceCard>
              )) : <EmptyPanel message="当前还没有符合筛选条件的监测目标。" />}
            </div>
        </section> : null}

        {!props?.standaloneEdit ? <section className={styles.panel}>
          <div className={styles.pageHeader}>
            <div>
              <p className={styles.pageKicker}>执行记录</p>
              <h2>监测结果与证据</h2>
            </div>
          </div>

          <div className={styles.runWorkspace}>
            <section className={styles.runListPanel}>
              <div className={styles.statsGrid}>
                <StatCard label="总记录" value={runSummary.total} />
                <StatCard label="成功" value={runSummary.success} tone="success" />
                <StatCard label="异常" value={runSummary.exception} tone="danger" />
                <StatCard label="运行中" value={runSummary.running} tone="warning" />
                <StatCard label="排队中" value={runSummary.queued} tone="warning" />
              </div>

              <div className={styles.tipPanel}>
                <strong>队列管理建议</strong>
                <p>如果只是验证刚刚新建的任务，优先用目标卡片里的“仅测试最新一次”。如果队列已经堆积，再用下面的“清理长期等待队列”或“重新派发阻塞执行”做收口。</p>
              </div>

              <div className={styles.toolbar}>
                <label className={styles.inlineField}>
                  <span>目标范围</span>
                  <select value={runFilters.targetId} onChange={(event) => setRunFilters((current) => ({ ...current, targetId: event.target.value }))}>
                    <option value="">全部目标</option>
                    {targets.map((target) => <option key={target.id} value={target.id}>{target.name}</option>)}
                  </select>
                </label>
                <label className={styles.inlineField}>
                  <span>状态筛选</span>
                  <select value={runFilters.status} onChange={(event) => setRunFilters((current) => ({ ...current, status: event.target.value as "" | RunStatus }))}>
                    <option value="">全部状态</option>
                    <option value="queued">排队中</option>
                    <option value="running">执行中</option>
                    <option value="success">成功</option>
                    <option value="failed">失败</option>
                    <option value="partial">部分成功</option>
                  </select>
                </label>
                <label className={styles.checkField}>
                  <input checked={runFilters.latestPerTarget} onChange={(event) => setRunFilters((current) => ({ ...current, latestPerTarget: event.target.checked }))} type="checkbox" />
                  <span>每个目标只看最新一条</span>
                </label>
                <label className={styles.checkField}>
                  <input checked={runFilters.recentOnly} onChange={(event) => setRunFilters((current) => ({ ...current, recentOnly: event.target.checked }))} type="checkbox" />
                  <span>仅看最近 N 条</span>
                </label>
                {runFilters.recentOnly ? (
                  <label className={styles.inlineField}>
                    <span>最近条数</span>
                    <select value={runFilters.limit} onChange={(event) => setRunFilters((current) => ({ ...current, limit: event.target.value }))}>
                      <option value="10">10</option>
                      <option value="20">20</option>
                      <option value="50">50</option>
                      <option value="100">100</option>
                    </select>
                  </label>
                ) : null}
                <div className={styles.buttonRow}>
                  <button className={styles.secondaryButton} onClick={exportRuns} type="button">
                    导出记录
                  </button>
                </div>
              </div>

              <div className={styles.toolbar}>
                <label className={styles.inlineField}>
                  <span>排队清理阈值</span>
                  <input value={queueThresholds.queuedOlderThanMinutes} onChange={(event) => setQueueThresholds((current) => ({ ...current, queuedOlderThanMinutes: event.target.value }))} />
                </label>
                <label className={styles.inlineField}>
                  <span>阻塞判定阈值</span>
                  <input value={queueThresholds.staleRunningMinutes} onChange={(event) => setQueueThresholds((current) => ({ ...current, staleRunningMinutes: event.target.value }))} />
                </label>
              </div>

              <div className={styles.buttonRow}>
                <button className={styles.secondaryButton} onClick={() => void refreshData()} type="button">刷新记录</button>
                <button className={styles.secondaryButton} disabled={queueAction !== null} onClick={() => void handleClearQueuedRuns()} type="button">{queueAction === "clear-queued" ? "清理中..." : "清理长期等待队列"}</button>
                <button className={styles.secondaryButton} disabled={queueAction !== null} onClick={() => void handleRequeueStaleRuns()} type="button">{queueAction === "requeue-stale" ? "重置中..." : "重新派发阻塞执行"}</button>
              </div>

              <div className={styles.runSelectionBar}>
                <label className={styles.selectionToggle}>
                  <input checked={allVisibleSelected} onChange={(event) => toggleVisibleRunsSelected(event.target.checked)} type="checkbox" />
                  <span>选中当前列表</span>
                </label>
                <span className={styles.selectionMeta}>当前列表 {visibleRuns.length} 条，已选 {selectedRunCount} 条</span>
                <div className={styles.actionRow}>
                  <button
                    className={styles.secondaryButton}
                    disabled={!allVisibleRunIds.length || queueAction !== null}
                    onClick={() => void handleDeleteVisibleRuns()}
                    type="button"
                  >
                    {queueAction === "delete-visible" ? "删除中..." : `删除当前筛选结果 (${allVisibleRunIds.length})`}
                  </button>
                  <button
                    className={styles.ghostDangerButton}
                    disabled={!selectedRunCount || queueAction !== null}
                    onClick={() => void handleDeleteSelectedRuns()}
                    type="button"
                  >
                    {queueAction === "delete-selected" ? "批量删除中..." : `删除已选 (${selectedRunCount})`}
                  </button>
                </div>
              </div>

              <div className={styles.runList}>
                <div className={styles.runPaginationBar}>
                  <label className={styles.inlineField}>
                    <span>每页条数</span>
                    <select value={runPageSize} onChange={(event) => setRunPageSize(event.target.value)}>
                      <option value="10">10</option>
                      <option value="20">20</option>
                      <option value="50">50</option>
                    </select>
                  </label>
                  <div className={styles.runPager}>
                    <button className={styles.secondaryButton} disabled={runPage <= 1} onClick={() => setRunPage((current) => Math.max(current - 1, 1))} type="button">上一页</button>
                    <span className={styles.selectionMeta}>第 {runPage} / {totalRunPages} 页</span>
                    <button className={styles.secondaryButton} disabled={runPage >= totalRunPages} onClick={() => setRunPage((current) => Math.min(current + 1, totalRunPages))} type="button">下一页</button>
                  </div>
                </div>

                {pagedRuns.length ? monthGroups.map((group) => {
                  const collapsed = collapsedMonths.includes(group.month);
                  return (
                    <section className={styles.monthGroup} key={group.month}>
                      <button className={styles.monthHeader} onClick={() => toggleMonthCollapsed(group.month)} type="button">
                        <div>
                          <strong>{group.label}</strong>
                          <span>{group.runs.length} 条记录</span>
                        </div>
                        <span>{collapsed ? "展开" : "折叠"}</span>
                      </button>
                      {!collapsed ? (
                        <div className={styles.monthRunList}>
                          {group.runs.map((run) => (
                            <article className={styles.runRow} data-active={selectedRunId === run.id} key={run.id}>
                              <label className={styles.runSelectCell}>
                                <input
                                  checked={selectedRunIds.includes(run.id)}
                                  onChange={(event) => toggleRunSelected(run.id, event.target.checked)}
                                  type="checkbox"
                                />
                              </label>
                              <button className={styles.runRowButton} onClick={() => void loadRunDetail(run.id)} type="button">
                                <div className={styles.runRowMain}>
                                  <div className={styles.runRowHeader}>
                                    <strong>{run.target?.name ?? "未知目标"}</strong>
                                    <span className={styles.badge} data-status={run.status}>{statusLabel(run.status)}</span>
                                  </div>
                                  <span className={styles.runMetaLine}>
                                    <span>{targetTypeLabel(run.target?.type ?? "http")}</span>
                                    <span>{formatTime(run.startedAt)}</span>
                                    <span title={run.traceId}>trace: {compactTraceId(run.traceId)}</span>
                                  </span>
                                  <span>{safePreviewText(run.summary, "等待执行结果摘要。")}</span>
                                  <span>{safePreviewText(run.errorTag ? `异常标签：${run.errorTag}` : "", "未命中异常标签")}</span>
                                </div>
                                <div className={styles.runRowSide}>
                                  <span>{run.failureReason ? "有失败原因" : "查看详情"}</span>
                                  <span>{run.target?.serviceKey ?? "未配置 service_key"}</span>
                                </div>
                              </button>
                            </article>
                          ))}
                        </div>
                      ) : null}
                    </section>
                  );
                }) : <EmptyPanel message="当前筛选条件下没有执行记录。你可以切换到“全部状态”，或先点一次“立即执行”。" />}
              </div>
            </section>

            <aside className={styles.runDetailPanel}>
              {selectedRunDetail ? (
                <div className={styles.detailStack}>
                  <div className={styles.detailHeader}>
                    <div>
                      <p className={styles.pageKicker}>记录详情</p>
                      <h3>{selectedRunDetail.run.target.name}</h3>
                    </div>
                    <span className={styles.badge} data-status={selectedRunDetail.run.status}>{statusLabel(selectedRunDetail.run.status)}</span>
                  </div>

                  <DetailKeyGrid
                    items={[
                      { label: "监测类型", value: targetTypeLabel(selectedRunDetail.run.target.type) },
                      { label: "目标地址", value: selectedRunDetail.run.target.endpoint },
                      { label: "开始时间", value: formatTime(selectedRunDetail.run.startedAt) },
                      { label: "结束时间", value: formatTime(selectedRunDetail.run.endedAt) },
                      { label: "异常标签", value: selectedRunDetail.run.errorTag ?? "无" },
                      { label: "trace_id", value: selectedRunDetail.run.traceId },
                    ]}
                  />

                  <DetailSection title="摘要区" description="先判断这次执行是否成功、失败点在哪里，决定是否继续深查。">
                    <div className={styles.metaList}>
                      <span>摘要：{selectedRunDetail.run.summary ?? "暂无"}</span>
                      <span>失败原因：{selectedRunDetail.run.failureReason ?? "当前未返回失败原因"}</span>
                    </div>
                  </DetailSection>

                  <DetailSection title="智能分析区" description="这里按 OpenObserve 查询边界和 Drain3 模板归并边界输出可读的问题结论。">
                    <div className={styles.metaChipList}>
                      <span className={styles.metaChip}><strong>总体等级</strong>{severityLabel(selectedRunDetail.detail.analysis.severity)}</span>
                      <span className={styles.metaChip}><strong>日志总量</strong>{selectedRunDetail.detail.analysis.stats.totalLogs}</span>
                      <span className={styles.metaChip}><strong>ERROR</strong>{selectedRunDetail.detail.analysis.stats.errorCount}</span>
                      <span className={styles.metaChip}><strong>WARN</strong>{selectedRunDetail.detail.analysis.stats.warnCount}</span>
                      <span className={styles.metaChip}><strong>模板数</strong>{selectedRunDetail.detail.analysis.stats.matchedTemplateCount}</span>
                      <span className={styles.metaChip}><strong>OpenObserve</strong>{selectedRunDetail.detail.analysis.source.openobserve === "configured" ? "已配置" : "本地回退"}</span>
                      <span className={styles.metaChip}><strong>Drain3</strong>{selectedRunDetail.detail.analysis.source.drain3 === "configured" ? "已配置" : "本地回退"}</span>
                    </div>
                    <p className={styles.reportConclusion}>{selectedRunDetail.detail.analysis.overview}</p>
                    <div className={styles.timeline}>
                      {selectedRunDetail.detail.analysis.issues.length ? selectedRunDetail.detail.analysis.issues.map((issue, index) => (
                        <article key={issue.id} className={styles.timelineItem}>
                          <div className={styles.timelineIndex}>{index + 1}</div>
                          <div className={styles.timelineBody}>
                            <div className={styles.timelineHeader}>
                              <strong>{issue.title}</strong>
                              <span className={styles.badge} data-status={issue.severity === "critical" || issue.severity === "major" ? "failed" : issue.severity === "warning" ? "partial" : "success"}>
                                {severityLabel(issue.severity)} / 置信度{confidenceLabel(issue.confidence)}
                              </span>
                            </div>
                            <p>{issue.summary}</p>
                            <div className={styles.metaList}>
                              <span>分类：{issue.category}</span>
                              <span>关联模板：{issue.templateIds.length ? issue.templateIds.join(", ") : "暂无"}</span>
                            </div>
                            <pre className={styles.codeBlock}>{["触发依据：", ...issue.evidence, "", "可能原因：", ...issue.possibleCauses.map((item) => `- ${item}`), "", "建议动作：", ...issue.recommendedActions.map((item) => `- ${item}`)].join("\n")}</pre>
                          </div>
                        </article>
                      )) : <EmptyPanel message="当前没有识别到需要升级处理的问题卡片。" />}
                    </div>
                  </DetailSection>

                  <DetailSection title="核心信息区" description="这里展示后端采集回来的核心指标，优先帮助你判断当前服务是否健康。">
                    <pre className={styles.codeBlock}>{JSON.stringify(selectedRunDetail.detail.metrics, null, 2)}</pre>
                  </DetailSection>

                  <DetailSection title="附属信息区" description="保留底层接口结构，便于继续扩展归因规则和二次处理。">
                    <pre className={styles.codeBlock}>{JSON.stringify(selectedRunDetail.detail.details, null, 2)}</pre>
                  </DetailSection>

                  <DetailSection title="历史与证据区" description="仅在配置了日志路径或附加采集能力时才会返回，适合继续排障。">
                    <pre className={styles.codeBlock}>{selectedRunDetail.detail.logExcerpt ?? "当前没有采集到日志摘录。"}</pre>
                  </DetailSection>

                  <DetailSection title="日志模板区" description="这里展示最近日志中归并出来的重复模式，后续可切到真实 Drain3 服务。">
                    <div className={styles.timeline}>
                      {selectedRunDetail.detail.analysis.templates.length ? selectedRunDetail.detail.analysis.templates.map((template, index) => (
                        <article key={template.templateId} className={styles.timelineItem}>
                          <div className={styles.timelineIndex}>{index + 1}</div>
                          <div className={styles.timelineBody}>
                            <div className={styles.timelineHeader}>
                              <strong>{template.category}</strong>
                              <span className={styles.badge} data-status={template.severity === "critical" || template.severity === "major" ? "failed" : template.severity === "warning" ? "partial" : "success"}>
                                {severityLabel(template.severity)}
                              </span>
                            </div>
                            <div className={styles.metaList}>
                              <span>template_id：{template.templateId}</span>
                              <span>命中次数：{template.sampleCount}</span>
                              <span>级别：{template.levels.join(", ") || "未标注"}</span>
                            </div>
                            <pre className={styles.codeBlock}>{[template.templateText, "", ...template.sampleMessages].join("\n")}</pre>
                          </div>
                        </article>
                      )) : <EmptyPanel message="当前样本量不足，尚未形成可展示的日志模板。" />}
                    </div>
                  </DetailSection>

                  <DetailSection title="操作区" description="在不离开详情的情况下完成重新入队、删除或导出原始结果。">
                    <div className={styles.buttonRow}>
                      {selectedRunDetail.run.status !== "success" ? (
                        <button className={styles.secondaryButton} disabled={queueAction === `requeue-${selectedRunDetail.run.id}`} onClick={() => void handleRequeueRun(selectedRunDetail.run.id)} type="button">
                          {queueAction === `requeue-${selectedRunDetail.run.id}` ? "重新入队中..." : "重新入队"}
                        </button>
                      ) : null}
                      {selectedRunDetail.detail.resultJsonUrl ? (
                        <Link className={styles.linkButton} href={`${apiBase}${selectedRunDetail.detail.resultJsonUrl}`} target="_blank">
                          查看 result.json
                        </Link>
                      ) : null}
                      <button className={styles.ghostDangerButton} disabled={queueAction === `delete-${selectedRunDetail.run.id}`} onClick={() => void handleDeleteRun(selectedRunDetail.run.id)} type="button">
                        {queueAction === `delete-${selectedRunDetail.run.id}` ? "删除中..." : "删除记录"}
                      </button>
                    </div>
                  </DetailSection>

                  <DetailSection title="历史区" description="以时间线形式回看本次执行的关键节点，方便后续扩展更多阶段事件。">
                    <div className={styles.timeline}>
                      <div className={styles.timelineItem}>
                        <div className={styles.timelineIndex}>1</div>
                        <div className={styles.timelineBody}>
                          <div className={styles.timelineHeader}>
                            <strong>任务入队与开始</strong>
                            <span>{formatTime(selectedRunDetail.run.startedAt)}</span>
                          </div>
                          <span>平台已将本次监测任务下发到执行链路，并开始记录 trace。</span>
                        </div>
                      </div>
                      <div className={styles.timelineItem}>
                        <div className={styles.timelineIndex}>2</div>
                        <div className={styles.timelineBody}>
                          <div className={styles.timelineHeader}>
                            <strong>执行完成</strong>
                            <span>{formatTime(selectedRunDetail.run.endedAt)}</span>
                          </div>
                          <span>{selectedRunDetail.run.failureReason ?? selectedRunDetail.run.summary ?? "执行完成，等待更多摘要。"}</span>
                        </div>
                      </div>
                    </div>
                  </DetailSection>
                </div>
              ) : (
                <EmptyPanel message="从左侧选择一条执行记录后，这里会展示指标、详情和日志摘录。" />
              )}
            </aside>
          </div>
        </section> : null}
      </section>
    </AdminFrame>
    <ConfirmDialog
      busy={confirmBusy}
      confirmLabel={confirmState?.confirmLabel}
      message={confirmState?.message ?? ""}
      onCancel={closeConfirm}
      onConfirm={() => void executeConfirm()}
      open={Boolean(confirmState)}
      title={confirmState?.title ?? ""}
      tone={confirmState?.tone ?? "default"}
    />
    </>
  );
}
