netx/web/src/pages/IngestPage.tsx
oliver f49c0b5bad feat: initialize netx ops tool repository
Set up netx as a standalone git-managed project with isolated Python dependencies, alarm ingestion/query APIs, oclaw bridge integration, and ops-focused UI enhancements including history and raw field access.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 23:43:21 +08:00

119 lines
3.7 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.

import { useMemo, useState } from "react";
import type { ImportHistoryItem } from "../types";
import { formatSystemTime } from "../utils/time";
type Props = {
isImportingAlarm: boolean;
alarmImportStatus: string;
isImportingLogs: boolean;
logsImportStatus: string;
history: ImportHistoryItem[];
onImportAlarmExcel: (file: File | null) => void;
onImportLogs: (file: File | null) => void;
onOpenBatch: (batchId: string) => void;
};
export function IngestPage({
isImportingAlarm,
alarmImportStatus,
isImportingLogs,
logsImportStatus,
history,
onImportAlarmExcel,
onImportLogs,
onOpenBatch,
}: Props) {
const [alarmFileName, setAlarmFileName] = useState("");
const [logFileName, setLogFileName] = useState("");
const items = useMemo(() => history.slice().sort((a, b) => b.ts_ms - a.ts_ms).slice(0, 10), [history]);
return (
<>
<section className="panel">
<h2>数据接入中心</h2>
<p className="muted">这里是 netx 的主页入口:导入告警 / 日志 / 配置等数据,后续扩展作业化能力。</p>
</section>
<section className="cards">
<article className="card">
<h3>告警导入(Excel)</h3>
<input
type="file"
accept=".xlsx,.xls"
disabled={isImportingAlarm}
onChange={(e) => {
const f = e.target.files?.[0] || null;
setAlarmFileName(f?.name || "");
onImportAlarmExcel(f);
}}
/>
<div className="muted">{alarmFileName ? `已选择:${alarmFileName}` : "选择 Excel 后自动上传导入"}</div>
<div className="muted">{alarmImportStatus}</div>
</article>
<article className="card">
<h3>日志导入(预留)</h3>
<input
type="file"
disabled={isImportingLogs}
onChange={(e) => {
const f = e.target.files?.[0] || null;
setLogFileName(f?.name || "");
onImportLogs(f);
}}
/>
<div className="muted">{logFileName ? `已选择:${logFileName}` : "选择日志文件后上传(当前仅占位接口)"}</div>
<div className="muted">{logsImportStatus}</div>
</article>
<article className="card">
<h3>配置导入(预留)</h3>
<button disabled={true} title="后续支持:配置文件导入、解析、对比与风险评估">
配置导入(规划中)
</button>
<div className="muted">后续接入:配置采集/割接对比/配置诊断。</div>
</article>
</section>
<section className="panel">
<h2>最近导入</h2>
<table>
<thead>
<tr>
<th>time</th>
<th>kind</th>
<th>file</th>
<th>result</th>
<th>batch</th>
</tr>
</thead>
<tbody>
{items.map((x) => (
<tr key={`${x.kind}-${x.ts_ms}-${x.file_name}`}>
<td>{formatSystemTime(x.ts_ms)}</td>
<td>{x.kind}</td>
<td>{x.file_name}</td>
<td>{x.ok ? "ok" : "error"}</td>
<td>
{x.batch_id ? (
<button className="link-btn" onClick={() => onOpenBatch(x.batch_id!)}>
{x.batch_id}
</button>
) : (
"-"
)}
</td>
</tr>
))}
{items.length === 0 && (
<tr>
<td colSpan={5}>暂无导入记录</td>
</tr>
)}
</tbody>
</table>
</section>
</>
);
}