2026-10-15 13:53:04 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 巡检模块 - 前端 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;
|
2026-10-30 16:55:55 +08:00
|
|
|
|
// 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;
|
|
|
|
|
|
}>;
|
2026-10-15 13:53:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-10-19 21:11:57 +08:00
|
|
|
|
// 浏览器远程访问环境:通过 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" },
|
2026-10-30 12:12:04 +08:00
|
|
|
|
getResultsByTimeRange: { method: "GET", path: "/api/inspection/results/range", arrayField: "results" },
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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" },
|
2026-10-30 12:12:04 +08:00
|
|
|
|
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" },
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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" },
|
2026-10-30 12:12:04 +08:00
|
|
|
|
updateSysConfig: { method: "POST", path: "/api/inspection/sys-config" },
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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" },
|
2026-10-30 12:12:04 +08:00
|
|
|
|
saveReportSchedule: { method: "POST", path: "/api/report/schedule" },
|
|
|
|
|
|
toggleReportScheduler: { method: "POST", path: "/api/report/scheduler-toggle" },
|
|
|
|
|
|
reportSchedulerStatus: { method: "GET", path: "/api/report/scheduler-status" },
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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" },
|
2026-10-30 12:12:04 +08:00
|
|
|
|
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" },
|
2026-10-19 21:11:57 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 某些 API 返回数组 —— 在 REMOTE_API_MAP 里用 arrayField 标记
|
|
|
|
|
|
// 找不到映射的 API 默认返回 null,让调用方自己兜底
|
|
|
|
|
|
const ARRAY_METHODS = new Set([
|
|
|
|
|
|
"listReportSnapshots", "listDailyReports", "listRules", "getLatestAll",
|
2026-10-30 12:12:04 +08:00
|
|
|
|
"getLatestByDevice", "getRecentResults", "getResultsByTimeRange", "getActiveAlerts", "listAlerts",
|
2026-10-19 21:11:57 +08:00
|
|
|
|
"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;
|
|
|
|
|
|
}
|
2026-10-30 12:12:04 +08:00
|
|
|
|
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)}`;
|
2026-10-19 21:11:57 +08:00
|
|
|
|
if (name === "getLatestByDevice" && args[0]) {
|
|
|
|
|
|
url += `&device_id=${encodeURIComponent(args[0])}`;
|
|
|
|
|
|
}
|
2026-10-30 12:12:04 +08:00
|
|
|
|
if (name === "getResultsByTimeRange" && args[0] !== undefined && args[1] !== undefined) {
|
|
|
|
|
|
url += `&start=${encodeURIComponent(args[0])}&end=${encodeURIComponent(args[1])}`;
|
|
|
|
|
|
}
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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] || {});
|
2026-10-30 12:12:04 +08:00
|
|
|
|
} 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] || "" });
|
2026-10-19 21:11:57 +08:00
|
|
|
|
} 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;
|
2026-10-30 12:12:04 +08:00
|
|
|
|
// 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 转换)
|
2026-10-19 21:11:57 +08:00
|
|
|
|
if (mapping.arrayField && Array.isArray(data[mapping.arrayField])) {
|
|
|
|
|
|
return data[mapping.arrayField];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (data.alerts !== undefined) return data.alerts;
|
2026-10-30 12:12:04 +08:00
|
|
|
|
if (data.devices !== undefined) return data.devices;
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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;
|
2026-10-30 12:12:04 +08:00
|
|
|
|
if (data.schedule !== undefined && (name === "getReportSchedule" || name === "saveReportSchedule")) return data.schedule;
|
2026-10-19 21:11:57 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-15 13:53:04 +08:00
|
|
|
|
function isAvailable(): boolean {
|
2026-10-19 21:11:57 +08:00
|
|
|
|
return !!api || IS_REMOTE_BROWSER;
|
2026-10-15 13:53:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function invoke(name: string, ...args: any[]): Promise<any> {
|
2026-10-19 21:11:57 +08:00
|
|
|
|
if (api && typeof api[name] === "function") {
|
|
|
|
|
|
return api[name](...args);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Electron bridge 不可用 → 浏览器远程访问环境走 HTTP fallback
|
|
|
|
|
|
if (IS_REMOTE_BROWSER) {
|
|
|
|
|
|
return remoteFetch(name, ...args);
|
2026-10-15 13:53:04 +08:00
|
|
|
|
}
|
2026-10-19 21:11:57 +08:00
|
|
|
|
console.warn(`[Inspection] api.${name} 不可用`);
|
|
|
|
|
|
return Promise.resolve(null);
|
2026-10-15 13:53:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 标签 ----------
|
|
|
|
|
|
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),
|
2026-10-19 21:11:57 +08:00
|
|
|
|
batchSetEnabled: (enabled: boolean): Promise<{ ok: boolean; count: number; enabled: boolean; error?: string }> =>
|
|
|
|
|
|
invoke("batchSetInspectionEnabled", enabled),
|
2026-10-15 13:53:04 +08:00
|
|
|
|
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),
|
2026-10-30 12:12:04 +08:00
|
|
|
|
getByTimeRange: (startTime: number, endTime: number): Promise<InspectionResult[]> =>
|
|
|
|
|
|
invoke("getResultsByTimeRange", startTime, endTime),
|
2026-10-15 13:53:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 告警记录 ----------
|
|
|
|
|
|
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"),
|
2026-10-19 21:11:57 +08:00
|
|
|
|
getAutoEnabled: (): Promise<boolean> => invoke("getAutoEnabled"),
|
|
|
|
|
|
setAutoEnabled: (enabled: boolean): Promise<{ enabled: boolean }> => invoke("setAutoEnabled", enabled),
|
2026-10-15 13:53:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 单次巡检执行 ----------
|
|
|
|
|
|
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),
|
|
|
|
|
|
};
|
2026-10-30 16:55:55 +08:00
|
|
|
|
|
|
|
|
|
|
// =============== 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"),
|
|
|
|
|
|
};
|