Files
SenMeshNet/infrastructure/inspection/inspectionApi.ts
zhaolei a5744e720b
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
feat(plan-management): implement alert-remediation-plan system v1.3
- 8 new store modules + matcher + initialResponse + aggregation + remediation + AlertStateMonitor + planGenerator

- alert chain auto-integrated after runInspection: freeze snapshot + generate response + real-time agg + auto-start monitor

- AlertAIPanel component + MonitoringDetailPanel expandable alert list

- PlanManagementPage: full page with list/edit/AI generate/templates/commands
2026-10-30 16:55:55 +08:00

825 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 巡检模块 - 前端 API 层
*
* 通过 window.electronAPI.inspection 调用主进程的巡检 IPC handlers。
*/
// ---------- 类型定义 ----------
export type AlertLevel = "critical" | "warning" | "info";
export type DeviceTier = "core" | "aggregation" | "access";
export type InspectionStatus = "success" | "failed" | "timeout";
export type AlertRecordStatus = "active" | "acknowledged" | "resolved" | "closed";
export type Operator = ">" | "<" | "==" | "!=" | ">=" | "<=";
export interface InspectionLabels {
alert_levels: Record<AlertLevel, string>;
device_tiers: Record<DeviceTier, string>;
inspection_status: Record<InspectionStatus, string>;
alert_record_status: Record<AlertRecordStatus, string>;
operators: Record<Operator, string>;
device_catalog: Record<string, string>;
metrics: Record<string, string>;
}
export interface DeviceCatalog {
id: string;
type_key: string;
name: string;
vendor: string;
adapter_class: string;
supported_metrics: string[];
default_thresholds: AlertRule[];
default_interval: number;
description: string;
preset: boolean;
}
export interface DeviceTierInfo {
key: DeviceTier;
label: string;
default_interval: number;
}
export interface DeviceExtension {
host_id: string;
/** 主机名(IP 或域名),用于调度器匹配 vault SSH 配置 */
hostname?: string | null;
catalog_id: string;
tier: DeviceTier | null;
inspection_interval: number | null;
inspection_enabled: boolean;
custom_thresholds: AlertRule[];
inspection_tags: string[];
last_scan_at: number | null;
last_scan_status: InspectionStatus | null;
last_scan_error: string | null;
created_at: number;
updated_at: number;
effective_interval?: number;
}
export interface AlertRule {
id: string;
name: string;
metric: string;
operator: Operator;
threshold: string;
level: AlertLevel;
duration: number;
scope_type: "all" | "tier" | "device" | "catalog";
scope_value: string | string[] | null;
enabled: boolean;
preset?: boolean;
created_at: number;
updated_at: number;
}
export interface InspectionResult {
id: string;
job_id: string | null;
device_id: string;
timestamp: number;
online: boolean;
status: InspectionStatus;
duration_ms: number;
metrics: Record<string, any>;
alerts: any[];
error_message: string | null;
}
export interface AlertRecord {
id: string;
rule_id: string;
rule_name: string;
device_id: string;
device_name: string;
metric: string;
metric_value: string;
threshold: string;
level: AlertLevel;
message: string;
triggered_at: number;
status: AlertRecordStatus;
acknowledged_by: string | null;
acknowledged_at: number | null;
resolved_at: number | null;
resolved_reason: string | null;
note: string | null;
// v1.3 预案管理扩展字段
alert_type?: string;
alert_catalog?: string;
matched_plan_id?: string | null;
matched_template_id?: string | null;
matched_plan_snapshot?: DevicePlan | null; // D9 匹配时刻冻结
frozen_at?: number | null;
initial_response?: InitialResponse | null;
remediation?: RemediationRun | null;
escalation?: {
trigger: "aggregation" | "escalation_fallback_timeout";
source_alert_ids: string[];
target_level: string;
escalated_at: number;
} | null;
audit_log?: Array<{
ts: number;
actor: string;
action: string;
detail?: string | null;
}>;
}
// ---------- v1.3 预案管理类型 ----------
export interface DevicePlan {
id: string;
template_id: string | null;
device_type: string;
level: AlertLevel;
enabled: boolean;
builtin: boolean;
status: "active" | "draft" | "disabled";
source: "manual" | "ai";
ai_provider: string | null;
ai_prompt_version: string | null;
reject_reason: string | null;
review_log: Array<{ ts: number; actor: string; action: "approve" | "reject" | "ai_generate"; reason: string | null }>;
description: string;
match: {
metric_rules: Array<{ metric_key: string; op: string; value: string | number }>;
alert_types: string[];
device_tiers: string[];
device_tags: string[];
};
scope: { host_ids: string[]; host_groups: string[] };
human_action_guide: {
receiving_alerts: string[];
info_gathering: Array<{ priority: number; action: string; purpose: string }>;
notification_chain: Array<{ role: string; channel: string; trigger: string; contact: string; auto: boolean }>;
escalation_triggers: Array<{ condition: string; target_level: string }>;
pre_remediation: string[];
post_remediation: string[];
};
remediation: {
steps: Array<{
order: number;
action: "command" | "checklist";
command_id?: string;
command_params?: Record<string, any>;
timeout_sec?: number;
execution: "auto" | "confirm" | "manual_only";
on_success: "next" | "stop" | "skip";
on_failure: "stop" | "escalate" | string;
purpose?: string;
}>;
};
security: {
allowed_command_ids: string[];
allowed_commands: string[];
blocked_patterns: string[];
};
required_role: string;
escalation_fallback: { auto_escalate_minutes: number; escalation_target_level: string } | null;
created_at: number;
updated_at: number;
}
export interface InitialResponse {
alert_id: string;
generated_at: number;
generator: "ai" | "fallback";
status: string;
quick_checklist: Array<{ priority: number; action: string; purpose: string }>;
possible_causes?: Array<{ probability: string; label: string; hint: string }>;
matched_plan: { name: string; level: string };
auto_notifications_sent: Array<{ role: string; channel: string; status: string }>;
pending_notifications: Array<Record<string, any>>;
warnings: string[];
}
export interface RemediationRun {
status: "pending" | "running" | "completed" | "partial" | "failed" | "cancelled" | "paused";
started_at: number;
completed_at?: number;
steps: Array<{
order: number;
status: "pending" | "running" | "success" | "failed" | "skipped" | "pending_manual";
command_id?: string;
execution: "auto" | "confirm" | "manual_only";
stdout_preview?: string | null;
exit_code?: number | null;
error?: string | null;
confirmed_by?: string | null;
confirmed_at?: number | null;
started_at?: number | null;
finished_at?: number | null;
}>;
}
export interface AlertStats {
total: number;
active: number;
active_critical: number;
active_warning: number;
active_info: number;
acknowledged: number;
resolved: number;
closed: number;
}
export interface SchedulerStatus {
running: boolean;
queue_size: number;
active_scans: number;
concurrency: number;
}
// ---------- API 调用 ----------
const api = (window as any).netcatty?.inspection;
// 浏览器远程访问环境:通过 HTTP API fallback
const IS_REMOTE_BROWSER = typeof window !== "undefined" && !!(window as any).__REMOTE_TOKEN__;
const REMOTE_TOKEN = (window as any).__REMOTE_TOKEN__ || "";
const REMOTE_API_MAP: Record<string, { method: string; path: string; arrayField?: string }> = {
getLatestAll: { method: "GET", path: "/api/inspection/results", arrayField: "results" },
getLatestByDevice: { method: "GET", path: "/api/inspection/results", arrayField: "results" },
getRecentResults: { method: "GET", path: "/api/inspection/results/recent", arrayField: "results" },
getResultsByTimeRange: { method: "GET", path: "/api/inspection/results/range", arrayField: "results" },
getActiveAlerts: { method: "GET", path: "/api/alerts", arrayField: "alerts" },
listAlerts: { method: "GET", path: "/api/alerts", arrayField: "alerts" },
getAlertStats: { method: "GET", path: "/api/alert-stats" },
listRules: { method: "GET", path: "/api/alert-rules", arrayField: "rules" },
getRule: { method: "GET", path: "/api/alert-rules", arrayField: "rules" },
createRule: { method: "POST", path: "/api/alert-rule" },
updateRule: { method: "POST", path: "/api/alert-rule" },
deleteRule: { method: "POST", path: "/api/alert-rule" },
toggleRule: { method: "POST", path: "/api/alert-rule" },
getAllExtensions: { method: "GET", path: "/api/devices", arrayField: "devices" },
getEnabledDevices: { method: "GET", path: "/api/devices/enabled", arrayField: "devices" },
schedulerStatus: { method: "GET", path: "/api/inspection/scheduler-status" },
getCatalog: { method: "GET", path: "/api/inspection/catalog", arrayField: "catalog" },
getSysConfig: { method: "GET", path: "/api/inspection/sys-config" },
updateSysConfig: { method: "POST", path: "/api/inspection/sys-config" },
getLabels: { method: "GET", path: "/api/inspection/labels" },
listReportSnapshots: { method: "GET", path: "/api/report/snapshots", arrayField: "snapshots" },
isReportSnapshotRunning: { method: "GET", path: "/api/report/snapshot-status" },
getReportSchedule: { method: "GET", path: "/api/report/schedule" },
saveReportSchedule: { method: "POST", path: "/api/report/schedule" },
toggleReportScheduler: { method: "POST", path: "/api/report/scheduler-toggle" },
reportSchedulerStatus: { method: "GET", path: "/api/report/scheduler-status" },
listDailyReports: { method: "GET", path: "/api/daily-reports", arrayField: "reports" },
batchSetInspectionEnabled: { method: "POST", path: "/api/devices/batch-enable" },
runReportAiSummary: { method: "POST", path: "/api/run-report-ai-summary" },
generateDailyReport: { method: "POST", path: "/api/generate-daily-report" },
acknowledgeAlert: { method: "POST", path: "/api/alert/__ID__/acknowledge" },
resolveAlert: { method: "POST", path: "/api/alert/__ID__/resolve" },
closeAlert: { method: "POST", path: "/api/alert/__ID__/close" },
updateAlertNote: { method: "POST", path: "/api/alert/__ID__/note" },
};
// 某些 API 返回数组 —— 在 REMOTE_API_MAP 里用 arrayField 标记
// 找不到映射的 API 默认返回 null,让调用方自己兜底
const ARRAY_METHODS = new Set([
"listReportSnapshots", "listDailyReports", "listRules", "getLatestAll",
"getLatestByDevice", "getRecentResults", "getResultsByTimeRange", "getActiveAlerts", "listAlerts",
"getAllExtensions", "getEnabledDevices", "getCatalog",
]);
async function remoteFetch(name: string, ...args: any[]): Promise<any> {
const mapping = REMOTE_API_MAP[name];
if (!mapping) {
// 没有映射的方法:读场景返回空数组/空对象兜底,避免 .find 崩溃
console.warn(`[Inspection] remote fallback: no mapping for ${name}, returning safe default`);
if (ARRAY_METHODS.has(name)) return [];
if (name === "getSchedule" || name === "getReportSchedule") return {};
if (name === "getSysConfig") return {};
if (name === "getLabels") return { alert_levels: {}, device_tiers: {}, inspection_status: {}, alert_record_status: {}, operators: {}, device_catalog: {}, metrics: {} };
if (name === "schedulerStatus" || name === "isReportSnapshotRunning") return { running: false };
return null;
}
let url = mapping.path;
// 告警状态变更:把 __ID__ 替换成实际 alertId
if (url.includes("__ID__") && args[0]) {
url = url.replace("__ID__", encodeURIComponent(args[0]));
args = args.slice(1); // alertId 从参数里移除,剩下的是 payload
}
url += `?token=${encodeURIComponent(REMOTE_TOKEN)}`;
if (name === "getLatestByDevice" && args[0]) {
url += `&device_id=${encodeURIComponent(args[0])}`;
}
if (name === "getResultsByTimeRange" && args[0] !== undefined && args[1] !== undefined) {
url += `&start=${encodeURIComponent(args[0])}&end=${encodeURIComponent(args[1])}`;
}
if ((name === "listAlerts" || name === "listReportSnapshots") && args[0] !== undefined) {
const opts = typeof args[0] === "object" ? args[0] : { limit: args[0] };
if (opts.status) url += `&status=${opts.status}`;
if (opts.level) url += `&level=${opts.level}`;
if (opts.device_id) url += `&device_id=${opts.device_id}`;
if (opts.limit) url += `&limit=${opts.limit}`;
} else if (args[0] !== undefined && name !== "getRule") {
url += `&limit=${encodeURIComponent(args[0])}`;
}
try {
const fetchOpts: RequestInit = {};
if (mapping.method === "POST") {
fetchOpts.method = "POST";
fetchOpts.headers = { "Content-Type": "application/json" };
if (name === "batchSetInspectionEnabled") {
fetchOpts.body = JSON.stringify({ enabled: !!args[0] });
} else if (name === "runReportAiSummary") {
// invoke("runReportAiSummary", snapshotId, providerId)
fetchOpts.body = JSON.stringify({ snapshotId: args[0], providerId: args[1] });
} else if (name === "generateDailyReport") {
// invoke("generateDailyReport", options)
fetchOpts.body = JSON.stringify(args[0] || {});
} else if (name === "createRule") {
fetchOpts.body = JSON.stringify({ action: "create", data: args[0] || {} });
} else if (name === "updateRule") {
fetchOpts.body = JSON.stringify({ action: "update", id: args[0], updates: args[1] || {} });
} else if (name === "deleteRule") {
fetchOpts.body = JSON.stringify({ action: "delete", id: args[0] });
} else if (name === "toggleRule") {
fetchOpts.body = JSON.stringify({ action: "toggle", id: args[0], enabled: !!args[1] });
} else if (name === "toggleReportScheduler") {
fetchOpts.body = JSON.stringify({ enabled: !!args[0] });
} else if (name === "acknowledgeAlert") {
fetchOpts.body = JSON.stringify({ operator: args[0] || "remote" });
} else if (name === "resolveAlert") {
fetchOpts.body = JSON.stringify({ reason: args[0] || "manual", operator: args[1] || "remote" });
} else if (name === "closeAlert") {
fetchOpts.body = JSON.stringify({ note: args[0] || "" });
} else if (name === "updateAlertNote") {
fetchOpts.body = JSON.stringify({ note: args[0] || "" });
} else {
fetchOpts.body = JSON.stringify(args[0] !== undefined ? args[0] : {});
}
}
const res = await fetch(url, fetchOpts);
const data = await res.json();
// 适配不同返回结构
if (data && typeof data === "object") {
if (Array.isArray(data)) return data;
// getAllExtensions:必须把 devices 数组转成 Record<host_id, ext>
if (name === "getAllExtensions" && Array.isArray(data.devices)) {
const map: Record<string, any> = {};
for (const d of data.devices) {
if (d?.host_id) map[d.host_id] = d;
}
return map;
}
// reportSchedulerStatus: { running, schedule }
if (name === "reportSchedulerStatus" && "running" in data) return data;
// 优先用 arrayField 约定的字段(getAllExtensions 已在上文处理 Record 转换)
if (mapping.arrayField && Array.isArray(data[mapping.arrayField])) {
return data[mapping.arrayField];
}
if (data.alerts !== undefined) return data.alerts;
if (data.devices !== undefined) return data.devices;
if (data.rules !== undefined) return data.rules;
if (data.results !== undefined) return data.results;
if (data.snapshots !== undefined) return data.snapshots;
if (data.reports !== undefined) return data.reports;
if (data.schedule !== undefined && (name === "getReportSchedule" || name === "saveReportSchedule")) return data.schedule;
if (data.ok === true && data.data !== undefined) return data.data;
if ("running" in data) return data; // schedulerStatus / snapshot-status
return data;
}
return null;
} catch (e) {
console.warn(`[Inspection] remote fetch ${name} failed:`, e);
// fetch 失败也返回安全默认值,不要让 .find 崩
if (ARRAY_METHODS.has(name)) return [];
return null;
}
}
function isAvailable(): boolean {
return !!api || IS_REMOTE_BROWSER;
}
function invoke(name: string, ...args: any[]): Promise<any> {
if (api && typeof api[name] === "function") {
return api[name](...args);
}
// Electron bridge 不可用 → 浏览器远程访问环境走 HTTP fallback
if (IS_REMOTE_BROWSER) {
return remoteFetch(name, ...args);
}
console.warn(`[Inspection] api.${name} 不可用`);
return Promise.resolve(null);
}
// ---------- 标签 ----------
export const inspectionLabelsApi = {
isAvailable,
getLabels: (): Promise<InspectionLabels> => invoke("getLabels"),
};
// ---------- 系统配置 ----------
export const inspectionSysConfigApi = {
get: (): Promise<Record<string, any>> => invoke("getSysConfig"),
update: (updates: Record<string, any>) => invoke("updateSysConfig", updates),
};
// ---------- 设备类型 ----------
export const inspectionCatalogApi = {
list: (): Promise<DeviceCatalog[]> => invoke("getCatalog"),
getTiers: (): Promise<DeviceTierInfo[]> => invoke("getTiers"),
getAdapters: (): Promise<string[]> => invoke("getAdapters"),
};
// ---------- 设备扩展 ----------
export const inspectionDeviceApi = {
getAll: (): Promise<Record<string, DeviceExtension>> => invoke("getAllExtensions"),
get: (hostId: string): Promise<DeviceExtension | null> => invoke("getExtension", hostId),
upsert: (hostId: string, data: Partial<DeviceExtension>): Promise<DeviceExtension> =>
invoke("upsertExtension", hostId, data),
batchSetEnabled: (enabled: boolean): Promise<{ ok: boolean; count: number; enabled: boolean; error?: string }> =>
invoke("batchSetInspectionEnabled", enabled),
remove: (hostId: string): Promise<boolean> => invoke("deleteExtension", hostId),
getEffectiveInterval: (hostId: string): Promise<number> =>
invoke("getEffectiveInterval", hostId),
getEnabled: (): Promise<DeviceExtension[]> => invoke("getEnabledDevices"),
};
// ---------- 告警规则 ----------
export const inspectionAlertRuleApi = {
list: (): Promise<AlertRule[]> => invoke("listRules"),
get: (ruleId: string): Promise<AlertRule | null> => invoke("getRule", ruleId),
create: (data: Partial<AlertRule>): Promise<AlertRule> => invoke("createRule", data),
update: (ruleId: string, updates: Partial<AlertRule>): Promise<AlertRule | null> =>
invoke("updateRule", ruleId, updates),
remove: (ruleId: string): Promise<boolean> => invoke("deleteRule", ruleId),
toggle: (ruleId: string, enabled: boolean): Promise<AlertRule | null> =>
invoke("toggleRule", ruleId, enabled),
};
// ---------- 巡检结果 ----------
export const inspectionResultApi = {
getLatestAll: (): Promise<InspectionResult[]> => invoke("getLatestAll"),
getLatestByDevice: (hostId: string, limit = 20): Promise<InspectionResult[]> =>
invoke("getLatestByDevice", hostId, limit),
getRecent: (limit = 50): Promise<InspectionResult[]> =>
invoke("getRecentResults", limit),
getByTimeRange: (startTime: number, endTime: number): Promise<InspectionResult[]> =>
invoke("getResultsByTimeRange", startTime, endTime),
};
// ---------- 告警记录 ----------
export const inspectionAlertApi = {
list: (options?: {
status?: AlertRecordStatus;
level?: AlertLevel;
device_id?: string;
limit?: number;
}): Promise<AlertRecord[]> => invoke("listAlerts", options),
getActive: (): Promise<AlertRecord[]> => invoke("getActiveAlerts"),
getStats: (): Promise<AlertStats> => invoke("getAlertStats"),
acknowledge: (alertId: string, operator = "system"): Promise<AlertRecord | null> =>
invoke("acknowledgeAlert", alertId, operator),
resolve: (
alertId: string,
reason = "manual",
operator = "system",
): Promise<AlertRecord | null> =>
invoke("resolveAlert", alertId, reason, operator),
close: (alertId: string, note = ""): Promise<AlertRecord | null> =>
invoke("closeAlert", alertId, note),
updateNote: (alertId: string, note: string): Promise<AlertRecord | null> =>
invoke("updateAlertNote", alertId, note),
};
// ---------- 调度器 ----------
// SSHConfig 是用于 SSH 连接的主机配置(hostname/port/username/password/privateKey 等)
export type SshHostConfig = Record<string, unknown>;
export const inspectionSchedulerApi = {
start: (hostConfigs?: Record<string, SshHostConfig>): Promise<SchedulerStatus> =>
invoke("schedulerStart", hostConfigs ? { hostConfigs } : {}),
stop: (): Promise<SchedulerStatus> => invoke("schedulerStop"),
status: (): Promise<SchedulerStatus> => invoke("schedulerStatus"),
scanNow: (hostId: string): Promise<any> => invoke("scanNow", hostId),
scanAllNow: (): Promise<any> => invoke("scanAllNow"),
getAutoEnabled: (): Promise<boolean> => invoke("getAutoEnabled"),
setAutoEnabled: (enabled: boolean): Promise<{ enabled: boolean }> => invoke("setAutoEnabled", enabled),
};
// ---------- 单次巡检执行 ----------
export interface RunInspectionResult {
success: boolean;
status: string;
metrics: Record<string, any>;
error: string | null;
duration: number;
alerts: any[];
}
export interface HostConfigForInspection {
id: string;
label?: string;
hostname: string;
port?: number;
username: string;
password?: string;
privateKeyContent?: string;
passphrase?: string;
}
export const inspectionExecApi = {
/**
* 执行一次完整巡检(前端传入完整主机配置)
* @param hostId 主机 ID
* @param hostConfig 主机连接配置
* @param catalogId 设备类型 ID(采集器 ID)
*/
runInspection: (
hostId: string,
hostConfig: HostConfigForInspection,
catalogId: string
): Promise<RunInspectionResult> =>
invoke("runInspection", { hostId, hostConfig, catalogId }),
};
// =============== 巡检报告快照 ===============
export interface ReportCmdResult {
cmd: string;
purpose: string;
ok: boolean;
duration_ms: number;
stdout: string;
stderr: string;
}
export interface ReportDeviceResult {
device_id: string;
hostname: string;
catalog_id: string;
ok: boolean;
duration_ms: number;
commands: ReportCmdResult[];
}
export interface ReportSnapshot {
id: string;
timestamp: number;
duration_ms: number;
trigger: "manual" | "scheduled";
devices: ReportDeviceResult[];
/** AI 汇总结果(主进程自动生成后持久化到 JSON) */
ai_summary?: any;
/** AI 汇总生成状态:running / done / failed */
ai_summary_status?: "running" | "done" | "failed";
/** AI 汇总失败时的错误信息 */
ai_summary_error?: string;
/** AI 汇总完成时间戳 */
ai_summary_updated_at?: number;
}
export interface ReportDeviceTarget {
device_id: string;
hostname: string;
port?: number;
username: string;
password?: string;
privateKeyContent?: string;
passphrase?: string;
catalog_id?: string;
}
export interface RunReportSnapshotResult {
ok: boolean;
snapshot?: ReportSnapshot;
error?: string;
}
/** 从 renderer localStorage 读报告产品名(优先报告专用 key,fallback 通用产品名)
* 注意:useStoredString 的 writeString 直接存纯字符串,不 JSON.stringify,
* 所以这里不能 JSON.parse,直接判断值类型即可。 */
function _getReportSystemName(): string | undefined {
try {
const reportRaw = localStorage.getItem("netcatty_report_product_name_v1");
if (typeof reportRaw === "string" && reportRaw.trim()) return reportRaw.trim();
const productRaw = localStorage.getItem("netcatty_product_name_v1");
if (typeof productRaw === "string" && productRaw.trim()) return productRaw.trim();
} catch (_) {}
return undefined;
}
export const inspectionReportApi = {
/**
* 跑一次巡检报告快照 —— 所有 deviceTargets 独立 SSH 连设备执行各自 catalog 的命令组
*/
runSnapshot: (
deviceTargets: ReportDeviceTarget[],
trigger: "manual" | "scheduled" = "manual"
): Promise<RunReportSnapshotResult> =>
invoke("runReportSnapshot", { deviceTargets, trigger }),
/** 查询主进程当前是否有快照正在执行(页面切换后查状态用) */
isSnapshotRunning: (): Promise<{ running: boolean }> =>
invoke("isReportSnapshotRunning"),
/**
* 列出最近 N 条快照(新的在前)
*/
listSnapshots: (limit = 50): Promise<ReportSnapshot[]> =>
invoke("listReportSnapshots", limit),
/**
* 删除一条快照
*/
deleteSnapshot: (id: string): Promise<boolean> =>
invoke("deleteReportSnapshot", id),
/**
* 清空所有快照
*/
clearSnapshots: (): Promise<{ ok: boolean }> =>
invoke("clearReportSnapshots"),
/**
* 导出快照 PDF
* @param options.snapshotId 快照 ID
* @param options.aiSummary 可选 AI 汇总(有则 PDF 含 AI 分析)
* @param options.customPath 可选用户指定保存路径(下载按钮用)
*/
exportSnapshotPdf: (options: {
snapshotId: string;
aiSummary?: any;
customPath?: string;
}): Promise<{ ok: boolean; filePath?: string; error?: string }> =>
invoke("exportReportSnapshotPdf", { ...options, systemName: _getReportSystemName() }),
/** 内嵌预览(Electron BrowserWindow + 内置 PDF viewer) */
previewSnapshotPdf: (snapshotId: string): Promise<{ ok: boolean; filePath?: string; error?: string }> =>
invoke("previewReportSnapshotPdf", { snapshotId, systemName: _getReportSystemName() }),
/** 保存 AI 汇总到快照(持久化,切换页面不丢) */
saveAiSummary: (snapshotId: string, aiSummary: any): Promise<{ ok: boolean }> =>
invoke("saveReportAiSummary", { snapshotId, aiSummary }),
/** 清空快照的 AI 汇总(用于重试前) */
clearAiSummary: (snapshotId: string): Promise<{ ok: boolean }> =>
invoke("clearReportAiSummary", { snapshotId }),
/** 主进程执行 AI 汇总 —— 独立进程,页面切换不影响 */
runAiSummary: (snapshotId: string, providerId?: string): Promise<{ ok: boolean }> =>
invoke("runReportAiSummary", snapshotId, providerId),
};
// =============== 巡检报告调度配置 ===============
export interface ReportSchedule {
enabled: boolean;
defaultIntervalHours: number;
dailyReportTime: string; // "HH:MM"
enabledDeviceIds: string[] | null; // null=全部;[]=一个都不选;[id1]=指定
perDeviceInterval: Record<string, number>;
aiSummaryEnabled: boolean;
}
export const inspectionReportScheduleApi = {
get: (): Promise<ReportSchedule> => invoke("getReportSchedule"),
save: (config: Partial<ReportSchedule>): Promise<ReportSchedule> =>
invoke("saveReportSchedule", config),
toggleScheduler: (enabled: boolean): Promise<{ ok: boolean; running: boolean }> =>
invoke("toggleReportScheduler", enabled),
status: (): Promise<{ running: boolean; schedule: ReportSchedule }> =>
invoke("reportSchedulerStatus"),
};
// =============== 日运行报告 ===============
export interface DailyReportSummary {
snapshotCount: number;
deviceCount: number;
totalCmds: number;
okCmds: number;
failCmds: number;
failRate: number;
deviceSuccessRate: Record<string, number>;
topFailedCommands: Array<{ device_id: string; cmd: string; count: number }>;
}
export interface DailyReport {
id: string;
date: string; // "YYYY-MM-DD"
mode: "provisional" | "finalized";
timestamp: number;
trigger: "manual" | "scheduled";
period: { start_ts: number; end_ts: number };
summary: DailyReportSummary;
aiSummary: any | null;
}
export const inspectionDailyReportApi = {
list: (limit = 30): Promise<DailyReport[]> => invoke("listDailyReports", limit),
generate: (options?: {
mode?: "provisional" | "finalized";
trigger?: "manual" | "scheduled";
}): Promise<{ ok: boolean; report?: DailyReport; error?: string }> =>
invoke("generateDailyReport", options || {}),
delete: (id: string): Promise<boolean> => invoke("deleteDailyReport", id),
clear: (): Promise<{ ok: boolean }> => invoke("clearDailyReports"),
exportPdf: (reportId: string): Promise<{ ok: boolean; filePath?: string; error?: string }> =>
invoke("exportDailyReportPdf", { reportId, systemName: _getReportSystemName() }),
/** 内嵌预览(Electron BrowserWindow + 内置 PDF viewer) */
previewPdf: (reportId: string): Promise<{ ok: boolean; filePath?: string; error?: string }> =>
invoke("previewDailyReportPdf", { reportId, systemName: _getReportSystemName() }),
};
export const inspectionSnapshotExportApi = {
exportPdf: (snapshotId: string, aiSummary?: any): Promise<{ ok: boolean; filePath?: string; error?: string }> =>
invoke("exportReportSnapshotPdf", aiSummary ? { snapshotId, aiSummary } : snapshotId),
};
// =============== v1.3 预案管理 ===============
export interface PlanTemplate {
id: string;
name: string;
level: AlertLevel;
builtin: boolean;
description: string;
display: { color: string; icon: string; blink: boolean; sound: string | null };
notification: { channels: string[]; recipients: any[]; repeat_interval_minutes: number; max_repeats: number };
escalation_fallback: { auto_escalate_minutes: number; escalation_target_level: string } | null;
remediation_defaults: { require_confirmation: boolean; auto_close_after_remediation_recovery: boolean; execution_timeout_sec: number };
created_at: number;
updated_at: number;
}
export interface CommandCatalogItem {
id: string;
name: string;
command: string;
device_types: string[];
risk_level: "low" | "medium" | "high";
execution: "auto" | "confirm" | "manual_only";
category: "diagnostic" | "config" | "security" | "backup" | "recovery";
description: string;
builtin: boolean;
created_at: number;
updated_at: number;
}
export const planManagementApi = {
// 预案模板
listTemplates: (): Promise<PlanTemplate[]> => invoke("listPlanTemplates"),
getTemplate: (id: string): Promise<PlanTemplate | null> => invoke("getPlanTemplate", id),
createTemplate: (data: Partial<PlanTemplate>): Promise<PlanTemplate> => invoke("createPlanTemplate", data),
updateTemplate: (id: string, updates: Partial<PlanTemplate>): Promise<PlanTemplate | null> =>
invoke("updatePlanTemplate", id, updates),
deleteTemplate: (id: string): Promise<boolean> => invoke("deletePlanTemplate", id),
// 设备预案
listPlans: (filters?: { status?: string; device_type?: string; level?: string; enabled?: boolean }): Promise<DevicePlan[]> =>
invoke("listDevicePlans", filters || {}),
getPlan: (id: string): Promise<DevicePlan | null> => invoke("getDevicePlan", id),
createPlan: (data: Partial<DevicePlan>): Promise<DevicePlan> => invoke("createDevicePlan", data),
updatePlan: (id: string, updates: Partial<DevicePlan>): Promise<DevicePlan | null> =>
invoke("updateDevicePlan", id, updates),
deletePlan: (id: string): Promise<boolean> => invoke("deleteDevicePlan", id),
approvePlan: (id: string, actor = "admin"): Promise<DevicePlan | null> =>
invoke("approveDevicePlan", id, actor),
rejectPlan: (id: string, reason: string, actor = "admin"): Promise<DevicePlan | null> =>
invoke("rejectDevicePlan", id, reason, actor),
matchPlan: (alert: any, deviceProfile: any): Promise<{ plan: DevicePlan; score: number } | null> =>
invoke("matchPlanForAlert", alert, deviceProfile),
// 聚合规则
listAggregationRules: (): Promise<any[]> => invoke("listAggregationRules"),
createAggregationRule: (data: any): Promise<any> => invoke("createAggregationRule", data),
updateAggregationRule: (id: string, updates: any): Promise<any | null> =>
invoke("updateAggregationRule", id, updates),
deleteAggregationRule: (id: string): Promise<boolean> => invoke("deleteAggregationRule", id),
// 命令目录
listCommandCatalog: (filters?: { device_type?: string; execution?: string; risk_level?: string }): Promise<CommandCatalogItem[]> =>
invoke("listCommandCatalog", filters || {}),
getCommandCatalog: (id: string): Promise<CommandCatalogItem | null> => invoke("getCommandCatalog", id),
// AI 预案生成
generatePlan: (description: string): Promise<{ ok: boolean; plan?: DevicePlan; warnings?: string[]; validation?: any; error?: string }> =>
invoke("generatePlan", description),
// 初始响应包
regenerateInitialResponse: (alertId: string): Promise<{ ok: boolean; response?: any; error?: string }> =>
invoke("regenerateInitialResponse", alertId),
// 处置执行
startRemediation: (alertId: string): Promise<{ ok: boolean; result?: any; error?: string }> =>
invoke("startRemediation", alertId),
pauseRemediation: (alertId: string): Promise<{ ok: boolean }> => invoke("pauseRemediation", alertId),
resumeRemediation: (alertId: string): Promise<{ ok: boolean }> => invoke("resumeRemediation", alertId),
cancelRemediation: (alertId: string): Promise<{ ok: boolean }> => invoke("cancelRemediation", alertId),
getRemediationStatus: (alertId: string): Promise<any | null> => invoke("getRemediationStatus", alertId),
// AlertStateMonitor
getAlertStateMonitorStatus: (): Promise<{ running: boolean; interval_minutes: number; remediation_timeout_minutes: number }> =>
invoke("getAlertStateMonitorStatus"),
};