/** * 巡检模块 - 前端 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; device_tiers: Record; inspection_status: Record; alert_record_status: Record; operators: Record; device_catalog: Record; metrics: Record; } 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; 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; } 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; function isAvailable(): boolean { return !!api; } function invoke(name: string, ...args: any[]): Promise { if (!api || typeof api[name] !== "function") { console.warn(`[Inspection] api.${name} 不可用`); return Promise.resolve(null); } return api[name](...args); } // ---------- 标签 ---------- export const inspectionLabelsApi = { isAvailable, getLabels: (): Promise => invoke("getLabels"), }; // ---------- 系统配置 ---------- export const inspectionSysConfigApi = { get: (): Promise> => invoke("getSysConfig"), update: (updates: Record) => invoke("updateSysConfig", updates), }; // ---------- 设备类型 ---------- export const inspectionCatalogApi = { list: (): Promise => invoke("getCatalog"), getTiers: (): Promise => invoke("getTiers"), getAdapters: (): Promise => invoke("getAdapters"), }; // ---------- 设备扩展 ---------- export const inspectionDeviceApi = { getAll: (): Promise> => invoke("getAllExtensions"), get: (hostId: string): Promise => invoke("getExtension", hostId), upsert: (hostId: string, data: Partial): Promise => invoke("upsertExtension", hostId, data), remove: (hostId: string): Promise => invoke("deleteExtension", hostId), getEffectiveInterval: (hostId: string): Promise => invoke("getEffectiveInterval", hostId), getEnabled: (): Promise => invoke("getEnabledDevices"), }; // ---------- 告警规则 ---------- export const inspectionAlertRuleApi = { list: (): Promise => invoke("listRules"), get: (ruleId: string): Promise => invoke("getRule", ruleId), create: (data: Partial): Promise => invoke("createRule", data), update: (ruleId: string, updates: Partial): Promise => invoke("updateRule", ruleId, updates), remove: (ruleId: string): Promise => invoke("deleteRule", ruleId), toggle: (ruleId: string, enabled: boolean): Promise => invoke("toggleRule", ruleId, enabled), }; // ---------- 巡检结果 ---------- export const inspectionResultApi = { getLatestAll: (): Promise => invoke("getLatestAll"), getLatestByDevice: (hostId: string, limit = 20): Promise => invoke("getLatestByDevice", hostId, limit), getRecent: (limit = 50): Promise => invoke("getRecentResults", limit), }; // ---------- 告警记录 ---------- export const inspectionAlertApi = { list: (options?: { status?: AlertRecordStatus; level?: AlertLevel; device_id?: string; limit?: number; }): Promise => invoke("listAlerts", options), getActive: (): Promise => invoke("getActiveAlerts"), getStats: (): Promise => invoke("getAlertStats"), acknowledge: (alertId: string, operator = "system"): Promise => invoke("acknowledgeAlert", alertId, operator), resolve: ( alertId: string, reason = "manual", operator = "system", ): Promise => invoke("resolveAlert", alertId, reason, operator), close: (alertId: string, note = ""): Promise => invoke("closeAlert", alertId, note), updateNote: (alertId: string, note: string): Promise => invoke("updateAlertNote", alertId, note), }; // ---------- 调度器 ---------- // SSHConfig 是用于 SSH 连接的主机配置(hostname/port/username/password/privateKey 等) export type SshHostConfig = Record; export const inspectionSchedulerApi = { start: (hostConfigs?: Record): Promise => invoke("schedulerStart", hostConfigs ? { hostConfigs } : {}), stop: (): Promise => invoke("schedulerStop"), status: (): Promise => invoke("schedulerStatus"), scanNow: (hostId: string): Promise => invoke("scanNow", hostId), scanAllNow: (): Promise => invoke("scanAllNow"), }; // ---------- 单次巡检执行 ---------- export interface RunInspectionResult { success: boolean; status: string; metrics: Record; 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 => 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 => invoke("runReportSnapshot", { deviceTargets, trigger }), /** 查询主进程当前是否有快照正在执行(页面切换后查状态用) */ isSnapshotRunning: (): Promise<{ running: boolean }> => invoke("isReportSnapshotRunning"), /** * 列出最近 N 条快照(新的在前) */ listSnapshots: (limit = 50): Promise => invoke("listReportSnapshots", limit), /** * 删除一条快照 */ deleteSnapshot: (id: string): Promise => 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; aiSummaryEnabled: boolean; } export const inspectionReportScheduleApi = { get: (): Promise => invoke("getReportSchedule"), save: (config: Partial): Promise => 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; 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 => 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 => 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), };