mirror of
https://github.com/hansjone/netx.git
synced 2026-10-19 08:30:45 +08:00
Use schedule checkbox for biz-state; add compare job delete action.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
108c9da00f
commit
e9b8042ca7
5 changed files with 109 additions and 60 deletions
|
|
@ -183,12 +183,16 @@ const en = {
|
||||||
colConnect: "Connect",
|
colConnect: "Connect",
|
||||||
start: "Start schedule",
|
start: "Start schedule",
|
||||||
pause: "Pause",
|
pause: "Pause",
|
||||||
|
scheduleEnabled: "Enable schedule",
|
||||||
|
scheduleOn: "Scheduled",
|
||||||
|
scheduleOff: "Manual",
|
||||||
|
scheduleHint: "When checked, collect on interval; otherwise only Collect now.",
|
||||||
collectNow: "Collect now",
|
collectNow: "Collect now",
|
||||||
delete: "Delete",
|
delete: "Delete",
|
||||||
confirmDelete: "Delete this task and all batches?",
|
confirmDelete: "Delete this task and all batches?",
|
||||||
deleted: "Deleted",
|
deleted: "Deleted",
|
||||||
started: "Schedule started",
|
started: "Schedule enabled",
|
||||||
paused: "Paused",
|
paused: "Schedule off (manual only)",
|
||||||
collecting: "Collecting",
|
collecting: "Collecting",
|
||||||
detail: "Details",
|
detail: "Details",
|
||||||
interval: "Interval",
|
interval: "Interval",
|
||||||
|
|
@ -244,6 +248,7 @@ const en = {
|
||||||
templateSaved: "Template saved",
|
templateSaved: "Template saved",
|
||||||
templateDeleted: "Template deleted",
|
templateDeleted: "Template deleted",
|
||||||
confirmDeleteTemplate: "Delete this compare template?",
|
confirmDeleteTemplate: "Delete this compare template?",
|
||||||
|
confirmDeleteJob: "Delete this compare job and all runs?",
|
||||||
emptyTemplates: "No templates yet. Create one or use built-ins.",
|
emptyTemplates: "No templates yet. Create one or use built-ins.",
|
||||||
templateHint:
|
templateHint:
|
||||||
"A template holds multiple metric sheets. Key: row identity (no separate compare). Interface: port mapping. Compare: value checks after key match. Leave compare empty for presence-only (entry set match).",
|
"A template holds multiple metric sheets. Key: row identity (no separate compare). Interface: port mapping. Compare: value checks after key match. Leave compare empty for presence-only (entry set match).",
|
||||||
|
|
@ -296,6 +301,7 @@ const en = {
|
||||||
runNow: "Run now",
|
runNow: "Run now",
|
||||||
saveJob: "Save config",
|
saveJob: "Save config",
|
||||||
jobSaved: "Job config saved",
|
jobSaved: "Job config saved",
|
||||||
|
jobDeleted: "Compare job deleted",
|
||||||
jobList: "Compare jobs",
|
jobList: "Compare jobs",
|
||||||
detail: "Details",
|
detail: "Details",
|
||||||
edit: "Edit",
|
edit: "Edit",
|
||||||
|
|
|
||||||
|
|
@ -183,12 +183,16 @@ const zh = {
|
||||||
colConnect: "连通",
|
colConnect: "连通",
|
||||||
start: "启动周期",
|
start: "启动周期",
|
||||||
pause: "暂停",
|
pause: "暂停",
|
||||||
|
scheduleEnabled: "启用周期调度",
|
||||||
|
scheduleOn: "周期",
|
||||||
|
scheduleOff: "手动",
|
||||||
|
scheduleHint: "勾选后按周期自动采集;不勾选则仅支持「立即采集」。",
|
||||||
collectNow: "立即采集",
|
collectNow: "立即采集",
|
||||||
delete: "删除",
|
delete: "删除",
|
||||||
confirmDelete: "删除该业务监控任务及所有批次?",
|
confirmDelete: "删除该业务监控任务及所有批次?",
|
||||||
deleted: "已删除",
|
deleted: "已删除",
|
||||||
started: "已启动周期采集",
|
started: "已启用周期采集",
|
||||||
paused: "已暂停",
|
paused: "已关闭周期(仅手动采集)",
|
||||||
collecting: "采集中",
|
collecting: "采集中",
|
||||||
detail: "详情",
|
detail: "详情",
|
||||||
interval: "采集周期",
|
interval: "采集周期",
|
||||||
|
|
@ -244,6 +248,7 @@ const zh = {
|
||||||
templateSaved: "模板已保存",
|
templateSaved: "模板已保存",
|
||||||
templateDeleted: "模板已删除",
|
templateDeleted: "模板已删除",
|
||||||
confirmDeleteTemplate: "删除该比对模板?",
|
confirmDeleteTemplate: "删除该比对模板?",
|
||||||
|
confirmDeleteJob: "删除该比对任务及全部运行记录?",
|
||||||
emptyTemplates: "暂无模板。可新建或使用系统默认。",
|
emptyTemplates: "暂无模板。可新建或使用系统默认。",
|
||||||
templateHint:
|
templateHint:
|
||||||
"一个模板可包含多张监控表。Key:对齐前后条目(选中后即为行身份,无需再勾比对);接口:参与端口映射;比对:勾选具体参数做值检查。若不勾任何比对字段,则只校验条目是否一致(存在性)。",
|
"一个模板可包含多张监控表。Key:对齐前后条目(选中后即为行身份,无需再勾比对);接口:参与端口映射;比对:勾选具体参数做值检查。若不勾任何比对字段,则只校验条目是否一致(存在性)。",
|
||||||
|
|
@ -295,6 +300,7 @@ const zh = {
|
||||||
runNow: "立即比对",
|
runNow: "立即比对",
|
||||||
saveJob: "保存配置",
|
saveJob: "保存配置",
|
||||||
jobSaved: "任务配置已保存",
|
jobSaved: "任务配置已保存",
|
||||||
|
jobDeleted: "比对任务已删除",
|
||||||
jobList: "比对任务",
|
jobList: "比对任务",
|
||||||
detail: "详情",
|
detail: "详情",
|
||||||
edit: "编辑",
|
edit: "编辑",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
bizCompareCreateJob,
|
bizCompareCreateJob,
|
||||||
bizCompareCreateMapping,
|
bizCompareCreateMapping,
|
||||||
bizCompareCreateTemplate,
|
bizCompareCreateTemplate,
|
||||||
|
bizCompareDeleteJob,
|
||||||
bizCompareDeleteTemplate,
|
bizCompareDeleteTemplate,
|
||||||
bizCompareGetRun,
|
bizCompareGetRun,
|
||||||
bizCompareListJobs,
|
bizCompareListJobs,
|
||||||
|
|
@ -471,6 +472,21 @@ export function BizComparePage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeJob = async (id: string) => {
|
||||||
|
if (!window.confirm(t("bizCompare.confirmDeleteJob"))) return;
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
await bizCompareDeleteJob(id);
|
||||||
|
if (jobId === id) closeJob();
|
||||||
|
showOk(t("bizCompare.jobDeleted"));
|
||||||
|
await refresh();
|
||||||
|
} catch (e) {
|
||||||
|
showError(formatErr(e));
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const parseMapRows = () => {
|
const parseMapRows = () => {
|
||||||
const rows: { before_if: string; after_if: string }[] = [];
|
const rows: { before_if: string; after_if: string }[] = [];
|
||||||
for (const line of mapText.split(/\r?\n/)) {
|
for (const line of mapText.split(/\r?\n/)) {
|
||||||
|
|
@ -945,6 +961,14 @@ export function BizComparePage() {
|
||||||
<Button size="sm" variant="primary" onPress={() => void openJob(j.id)}>
|
<Button size="sm" variant="primary" onPress={() => void openJob(j.id)}>
|
||||||
{t("bizCompare.detail")}
|
{t("bizCompare.detail")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="danger"
|
||||||
|
isDisabled={busy}
|
||||||
|
onPress={() => void removeJob(j.id)}
|
||||||
|
>
|
||||||
|
{t("bizCompare.delete")}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -1409,6 +1433,11 @@ export function BizComparePage() {
|
||||||
<Button size="sm" variant="ghost" onPress={closeJob}>
|
<Button size="sm" variant="ghost" onPress={closeJob}>
|
||||||
{t("bizState.cancel")}
|
{t("bizState.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
{jobId ? (
|
||||||
|
<Button size="sm" variant="danger" isDisabled={busy} onPress={() => void removeJob(jobId)}>
|
||||||
|
{t("bizCompare.delete")}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</AppModalShell>
|
</AppModalShell>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -397,13 +397,12 @@ export function BizStatePage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setStatus = async (status: string) => {
|
const setScheduleEnabled = async (id: string, enabled: boolean) => {
|
||||||
if (!taskId) return;
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
await bizStatePatchTask(taskId, { status });
|
await bizStatePatchTask(id, { status: enabled ? "running" : "paused" });
|
||||||
showOk(status === "running" ? t("bizState.started") : t("bizState.paused"));
|
showOk(enabled ? t("bizState.started") : t("bizState.paused"));
|
||||||
await loadTask(taskId);
|
if (taskId === id) await loadTask(id);
|
||||||
await refreshTasks();
|
await refreshTasks();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(formatErr(e));
|
showError(formatErr(e));
|
||||||
|
|
@ -593,7 +592,7 @@ export function BizStatePage() {
|
||||||
<div className="pt-list-kpi__value">{tasks.length}</div>
|
<div className="pt-list-kpi__value">{tasks.length}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-list-kpi pt-list-kpi--live">
|
<div className="pt-list-kpi pt-list-kpi--live">
|
||||||
<div className="pt-list-kpi__label">{t("bizState.start")}</div>
|
<div className="pt-list-kpi__label">{t("bizState.scheduleOn")}</div>
|
||||||
<div className="pt-list-kpi__value">{runningCount}</div>
|
<div className="pt-list-kpi__value">{runningCount}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -613,6 +612,7 @@ export function BizStatePage() {
|
||||||
<th>{t("bizState.colNe")}</th>
|
<th>{t("bizState.colNe")}</th>
|
||||||
<th>{t("bizState.colSource")}</th>
|
<th>{t("bizState.colSource")}</th>
|
||||||
<th>{t("bizState.colInterval")}</th>
|
<th>{t("bizState.colInterval")}</th>
|
||||||
|
<th>{t("bizState.scheduleEnabled")}</th>
|
||||||
<th>{t("bizState.colStatus")}</th>
|
<th>{t("bizState.colStatus")}</th>
|
||||||
<th>{t("bizState.colLast")}</th>
|
<th>{t("bizState.colLast")}</th>
|
||||||
<th>{t("bizState.colActions")}</th>
|
<th>{t("bizState.colActions")}</th>
|
||||||
|
|
@ -641,12 +641,28 @@ export function BizStatePage() {
|
||||||
t("bizState.intervalUnitSeconds"),
|
t("bizState.intervalUnitSeconds"),
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<label className="config-sync-policy-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={row.status === "running"}
|
||||||
|
disabled={busy}
|
||||||
|
onChange={(e) => void setScheduleEnabled(row.id, e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{row.status === "running" ? t("bizState.scheduleOn") : t("bizState.scheduleOff")}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="pt-list-actions" style={{ flexWrap: "wrap", gap: 4 }}>
|
<div className="pt-list-actions" style={{ flexWrap: "wrap", gap: 4 }}>
|
||||||
<NmStatusChip color={jobChipColor(row.status)}>{row.status}</NmStatusChip>
|
|
||||||
{row.collect_running ? (
|
{row.collect_running ? (
|
||||||
<NmStatusChip color="accent">{t("bizState.collecting")}</NmStatusChip>
|
<NmStatusChip color="accent">{t("bizState.collecting")}</NmStatusChip>
|
||||||
) : null}
|
) : (
|
||||||
|
<NmStatusChip color={row.status === "running" ? "success" : "default"}>
|
||||||
|
{row.status === "running" ? t("bizState.scheduleOn") : t("bizState.scheduleOff")}
|
||||||
|
</NmStatusChip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="pt-list-time">{fmtTime(row.last_collect_ended_at)}</td>
|
<td className="pt-list-time">{fmtTime(row.last_collect_ended_at)}</td>
|
||||||
|
|
@ -658,47 +674,33 @@ export function BizStatePage() {
|
||||||
<Button size="sm" variant="ghost" onPress={() => void openTask(row.id, "batches")}>
|
<Button size="sm" variant="ghost" onPress={() => void openTask(row.id, "batches")}>
|
||||||
{t("bizState.batches")}
|
{t("bizState.batches")}
|
||||||
</Button>
|
</Button>
|
||||||
{row.status !== "running" ? (
|
<Button
|
||||||
<Button
|
size="sm"
|
||||||
size="sm"
|
variant="secondary"
|
||||||
variant="ghost"
|
isDisabled={busy || row.collect_running}
|
||||||
isDisabled={busy}
|
onPress={async () => {
|
||||||
onPress={async () => {
|
setBusy(true);
|
||||||
setBusy(true);
|
try {
|
||||||
try {
|
await bizStateCollectNow(row.id);
|
||||||
await bizStatePatchTask(row.id, { status: "running" });
|
showOk(t("bizState.collecting"));
|
||||||
showOk(t("bizState.started"));
|
for (let i = 0; i < 20; i++) {
|
||||||
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
await refreshTasks();
|
await refreshTasks();
|
||||||
} catch (e) {
|
const cur = (await bizStateListTasks()).items?.find(
|
||||||
showError(formatErr(e));
|
(x: any) => x.id === row.id,
|
||||||
} finally {
|
);
|
||||||
setBusy(false);
|
if (!cur?.collect_running) break;
|
||||||
}
|
}
|
||||||
}}
|
await refreshTasks();
|
||||||
>
|
} catch (e) {
|
||||||
{t("bizState.start")}
|
showError(formatErr(e));
|
||||||
</Button>
|
} finally {
|
||||||
) : (
|
setBusy(false);
|
||||||
<Button
|
}
|
||||||
size="sm"
|
}}
|
||||||
variant="ghost"
|
>
|
||||||
isDisabled={busy}
|
{t("bizState.collectNow")}
|
||||||
onPress={async () => {
|
</Button>
|
||||||
setBusy(true);
|
|
||||||
try {
|
|
||||||
await bizStatePatchTask(row.id, { status: "paused" });
|
|
||||||
showOk(t("bizState.paused"));
|
|
||||||
await refreshTasks();
|
|
||||||
} catch (e) {
|
|
||||||
showError(formatErr(e));
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("bizState.pause")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
|
|
@ -713,7 +715,7 @@ export function BizStatePage() {
|
||||||
))}
|
))}
|
||||||
{!filteredTasks.length ? (
|
{!filteredTasks.length ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6}>
|
<td colSpan={7}>
|
||||||
<div className="pt-list-empty">{t("bizState.empty")}</div>
|
<div className="pt-list-empty">{t("bizState.empty")}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -846,6 +848,15 @@ export function BizStatePage() {
|
||||||
<Modal.Body className="flex flex-col gap-3">
|
<Modal.Body className="flex flex-col gap-3">
|
||||||
{detail ? (
|
{detail ? (
|
||||||
<div className="config-sync-policy-row bs-schedule-row">
|
<div className="config-sync-policy-row bs-schedule-row">
|
||||||
|
<label className="config-sync-policy-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={detail.status === "running"}
|
||||||
|
disabled={busy}
|
||||||
|
onChange={(e) => void setScheduleEnabled(taskId, e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>{t("bizState.scheduleEnabled")}</span>
|
||||||
|
</label>
|
||||||
<label className="config-sync-policy-field">
|
<label className="config-sync-policy-field">
|
||||||
<span>{t("bizState.interval")}</span>
|
<span>{t("bizState.interval")}</span>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -892,7 +903,7 @@ export function BizStatePage() {
|
||||||
{t("bizState.saveSchedule")}
|
{t("bizState.saveSchedule")}
|
||||||
</Button>
|
</Button>
|
||||||
<span className="muted">
|
<span className="muted">
|
||||||
{detail.vendor || "—"} · {detail.ne_ip || "—"}
|
{detail.vendor || "—"} · {detail.ne_ip || "—"} · {t("bizState.scheduleHint")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -1075,13 +1086,7 @@ export function BizStatePage() {
|
||||||
)}
|
)}
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void setStatus("running")}>
|
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void collectNow()}>
|
||||||
{t("bizState.start")}
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="secondary" isDisabled={busy} onPress={() => void setStatus("paused")}>
|
|
||||||
{t("bizState.pause")}
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="secondary" isDisabled={busy} onPress={() => void collectNow()}>
|
|
||||||
{t("bizState.collectNow")}
|
{t("bizState.collectNow")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="danger" isDisabled={busy} onPress={() => void removeTask(taskId)}>
|
<Button size="sm" variant="danger" isDisabled={busy} onPress={() => void removeTask(taskId)}>
|
||||||
|
|
|
||||||
|
|
@ -1895,6 +1895,9 @@ export const bizCompareUpdateJob = (jobId: string, body: Record<string, unknown>
|
||||||
body,
|
body,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const bizCompareDeleteJob = (jobId: string) =>
|
||||||
|
apiDelete<{ ok: boolean }>(`/v1/biz-state/compare/jobs/${encodeURIComponent(jobId)}`);
|
||||||
|
|
||||||
export const bizCompareRunJob = (jobId: string) =>
|
export const bizCompareRunJob = (jobId: string) =>
|
||||||
apiPost<Record<string, unknown>>(
|
apiPost<Record<string, unknown>>(
|
||||||
`/v1/biz-state/compare/jobs/${encodeURIComponent(jobId)}/run`,
|
`/v1/biz-state/compare/jobs/${encodeURIComponent(jobId)}/run`,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue