mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 04:20:45 +08:00
Improve biz-compare UX with guided create, sheet toggles, and per-run delete.
Also sticky template field headers and biz-state pause/start endpoints. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9b3e98ede5
commit
3d2489041a
12 changed files with 669 additions and 155 deletions
|
|
@ -189,6 +189,7 @@ const en = {
|
|||
colConnect: "Connect",
|
||||
start: "Start schedule",
|
||||
pause: "Pause",
|
||||
statusPaused: "Paused",
|
||||
scheduleEnabled: "Enable schedule",
|
||||
scheduleOn: "Scheduled",
|
||||
scheduleOff: "Manual",
|
||||
|
|
@ -198,7 +199,7 @@ const en = {
|
|||
confirmDelete: "Delete this task and all batches?",
|
||||
deleted: "Deleted",
|
||||
started: "Schedule enabled",
|
||||
paused: "Schedule off (manual only)",
|
||||
paused: "Schedule paused (manual only)",
|
||||
collecting: "Collecting",
|
||||
detail: "Details",
|
||||
interval: "Interval",
|
||||
|
|
@ -293,6 +294,10 @@ const en = {
|
|||
templateDeleted: "Template deleted",
|
||||
confirmDeleteTemplate: "Delete this compare template?",
|
||||
confirmDeleteJob: "Delete this compare job and all runs?",
|
||||
confirmDeleteRun: "Delete this compare run? Job config is kept.",
|
||||
deleteJob: "Delete job",
|
||||
deleteRun: "Delete run",
|
||||
runDeleted: "Compare run deleted",
|
||||
emptyTemplates: "No templates yet. Create one or import a JSON file.",
|
||||
exportTemplate: "Export",
|
||||
importTemplate: "Import",
|
||||
|
|
@ -406,6 +411,21 @@ const en = {
|
|||
sameAsBefore: "Same as before / select…",
|
||||
autoHint: "auto: new after-task batches trigger compare",
|
||||
createCompare: "Create job",
|
||||
createSteps: "Create steps",
|
||||
stepBasics: "Basics",
|
||||
stepSheets: "Sheets",
|
||||
stepBatches: "Batches",
|
||||
stepMapping: "Port mapping",
|
||||
next: "Next",
|
||||
back: "Back",
|
||||
enabledSheets: "Compare sheets",
|
||||
enabledSheetsHint: "Toggle sheets; all on by default. Off sheets are skipped.",
|
||||
noTemplateSheets: "This template has no sheets",
|
||||
needJobName: "Enter a job name",
|
||||
needTemplate: "Select a compare template",
|
||||
needSheets: "Enable at least one sheet",
|
||||
needBeforeBatch: "Select before task and batch",
|
||||
needAfterBatch: "Select after batch",
|
||||
runNow: "Run now",
|
||||
saveJob: "Save config",
|
||||
jobSaved: "Job config saved",
|
||||
|
|
@ -414,13 +434,14 @@ const en = {
|
|||
detail: "Details",
|
||||
edit: "Edit",
|
||||
delete: "Delete",
|
||||
tabConfig: "Config / batches",
|
||||
tabConfig: "Job config",
|
||||
tabResult: "Result",
|
||||
runs: "Run history",
|
||||
noRuns: "No runs yet — run a compare first",
|
||||
result: "Result",
|
||||
pickBatchRun: "Select compare batch",
|
||||
pickRun: "Select batch…",
|
||||
pickBatchRun: "Select compare run",
|
||||
pickRun: "Select run…",
|
||||
runCount: "{{n}} runs",
|
||||
resultEmpty: "No matching diff rows",
|
||||
resultFilterPh: "Filter key / values…",
|
||||
kindAll: "All",
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ const zh = {
|
|||
colConnect: "连通",
|
||||
start: "启动周期",
|
||||
pause: "暂停",
|
||||
statusPaused: "已暂停",
|
||||
scheduleEnabled: "启用周期调度",
|
||||
scheduleOn: "周期",
|
||||
scheduleOff: "手动",
|
||||
|
|
@ -198,7 +199,7 @@ const zh = {
|
|||
confirmDelete: "删除该业务监控任务及所有批次?",
|
||||
deleted: "已删除",
|
||||
started: "已启用周期采集",
|
||||
paused: "已关闭周期(仅手动采集)",
|
||||
paused: "已暂停周期(仅手动采集)",
|
||||
collecting: "采集中",
|
||||
detail: "详情",
|
||||
interval: "采集周期",
|
||||
|
|
@ -292,7 +293,11 @@ const zh = {
|
|||
templateSaved: "模板已保存",
|
||||
templateDeleted: "模板已删除",
|
||||
confirmDeleteTemplate: "删除该比对模板?",
|
||||
confirmDeleteJob: "删除该比对任务及全部运行记录?",
|
||||
confirmDeleteJob: "删除该比对任务及全部比对记录?",
|
||||
confirmDeleteRun: "删除本次比对结果?任务配置将保留。",
|
||||
deleteJob: "删除任务",
|
||||
deleteRun: "删除本次",
|
||||
runDeleted: "比对记录已删除",
|
||||
emptyTemplates: "暂无模板。可新建,或从 JSON 文件导入。",
|
||||
exportTemplate: "导出",
|
||||
importTemplate: "导入",
|
||||
|
|
@ -405,6 +410,21 @@ const zh = {
|
|||
sameAsBefore: "同操作前 / 选择…",
|
||||
autoHint: "auto:操作后任务新批次到达会自动跑比对",
|
||||
createCompare: "创建比对任务",
|
||||
createSteps: "创建步骤",
|
||||
stepBasics: "基本信息",
|
||||
stepSheets: "对比项",
|
||||
stepBatches: "批次",
|
||||
stepMapping: "端口映射",
|
||||
next: "下一步",
|
||||
back: "上一步",
|
||||
enabledSheets: "对比项",
|
||||
enabledSheetsHint: "点击开关;默认全部开启。关闭的项不参与本次比对。",
|
||||
noTemplateSheets: "该模板暂无对比表",
|
||||
needJobName: "请填写任务名称",
|
||||
needTemplate: "请选择比对模板",
|
||||
needSheets: "请至少开启一项对比",
|
||||
needBeforeBatch: "请选择操作前任务与批次",
|
||||
needAfterBatch: "请选择操作后批次",
|
||||
runNow: "立即比对",
|
||||
saveJob: "保存配置",
|
||||
jobSaved: "任务配置已保存",
|
||||
|
|
@ -413,13 +433,14 @@ const zh = {
|
|||
detail: "详情",
|
||||
edit: "编辑",
|
||||
delete: "删除",
|
||||
tabConfig: "配置 / 批次",
|
||||
tabConfig: "任务配置",
|
||||
tabResult: "比对结果",
|
||||
runs: "历史 Run",
|
||||
runs: "历史比对",
|
||||
noRuns: "尚无比对记录,请先执行比对",
|
||||
result: "比对结果",
|
||||
pickBatchRun: "选择比对批次",
|
||||
pickRun: "选择批次…",
|
||||
pickBatchRun: "选择比对记录",
|
||||
pickRun: "选择比对记录…",
|
||||
runCount: "{{n}} 次",
|
||||
resultEmpty: "无匹配失败/结果行",
|
||||
resultFilterPh: "筛选身份 / 字段值…",
|
||||
kindAll: "全部",
|
||||
|
|
|
|||
|
|
@ -10821,6 +10821,17 @@ html.login-page--paused .login-page__flare {
|
|||
max-height: min(55vh, 520px);
|
||||
overflow: auto;
|
||||
}
|
||||
.ct-field-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
.ct-field-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background: rgba(15, 23, 42, 0.98) !important;
|
||||
box-shadow: 0 1px 0 rgba(148, 163, 184, 0.22);
|
||||
}
|
||||
.ct-field-table th,
|
||||
.ct-field-table td {
|
||||
vertical-align: middle;
|
||||
|
|
@ -11314,13 +11325,19 @@ html.login-page--paused .login-page__flare {
|
|||
}
|
||||
|
||||
.bs-cmp-board__run-select {
|
||||
flex: 0 1 280px;
|
||||
flex: 0 1 360px;
|
||||
min-width: 160px;
|
||||
max-width: 320px;
|
||||
max-width: 420px;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bs-cmp-board__run-count {
|
||||
flex: 0 0 auto;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bs-cmp-board__actions {
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
bizCompareCreateMapping,
|
||||
bizCompareCreateTemplate,
|
||||
bizCompareDeleteJob,
|
||||
bizCompareDeleteRun,
|
||||
bizCompareDeleteTemplate,
|
||||
bizCompareDownloadRun,
|
||||
bizCompareGetRun,
|
||||
|
|
@ -35,6 +36,8 @@ import { jobChipColor, NmStatusChip } from "./nmChips";
|
|||
type PageTab = "templates" | "jobs";
|
||||
type JobDetailTab = "config" | "result";
|
||||
type KindFilter = "diff" | "all" | "added" | "removed" | "changed" | "unchanged";
|
||||
type CreateJobStep = 0 | 1 | 2 | 3;
|
||||
const CREATE_JOB_STEPS = 4;
|
||||
|
||||
type TaskOpt = { id: string; ne_name: string; ne_ip: string; vendor: string };
|
||||
type BatchOpt = { id: string; status: string; row_count: number; started_at?: string | null };
|
||||
|
|
@ -101,6 +104,7 @@ type Job = {
|
|||
after_batch_id: string;
|
||||
mode: string;
|
||||
status: string;
|
||||
enabled_sheet_ids?: string[];
|
||||
note?: string;
|
||||
};
|
||||
|
||||
|
|
@ -774,10 +778,12 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
|
||||
// job create / detail
|
||||
const [jobCreateOpen, setJobCreateOpen] = useState(false);
|
||||
const [createStep, setCreateStep] = useState<CreateJobStep>(0);
|
||||
const [jobId, setJobId] = useState("");
|
||||
const [jobDetailTab, setJobDetailTab] = useState<JobDetailTab>("config");
|
||||
const [name, setName] = useState("");
|
||||
const [templateId, setTemplateId] = useState("");
|
||||
const [enabledSheetIds, setEnabledSheetIds] = useState<string[]>([]);
|
||||
const [mappingId, setMappingId] = useState("");
|
||||
const [beforeTaskId, setBeforeTaskId] = useState("");
|
||||
const [afterTaskId, setAfterTaskId] = useState("");
|
||||
|
|
@ -873,6 +879,66 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
});
|
||||
}, [templates, debouncedListKw]);
|
||||
|
||||
const selectedJobTemplate = useMemo(
|
||||
() => templates.find((x) => x.id === templateId) || null,
|
||||
[templates, templateId],
|
||||
);
|
||||
|
||||
const jobTemplateSheets = useMemo(
|
||||
() => (selectedJobTemplate ? templateSheets(selectedJobTemplate) : []),
|
||||
[selectedJobTemplate],
|
||||
);
|
||||
|
||||
const jobSheetAllIds = useMemo(
|
||||
() => jobTemplateSheets.map((s) => sheetIdentity(s)).filter(Boolean),
|
||||
[jobTemplateSheets],
|
||||
);
|
||||
|
||||
const isJobSheetOn = useCallback(
|
||||
(sid: string) => {
|
||||
if (!enabledSheetIds.length) return true;
|
||||
return enabledSheetIds.includes(sid);
|
||||
},
|
||||
[enabledSheetIds],
|
||||
);
|
||||
|
||||
const enabledJobSheetCount = useMemo(() => {
|
||||
if (!jobSheetAllIds.length) return 0;
|
||||
if (!enabledSheetIds.length) return jobSheetAllIds.length;
|
||||
return jobSheetAllIds.filter((id) => enabledSheetIds.includes(id)).length;
|
||||
}, [jobSheetAllIds, enabledSheetIds]);
|
||||
|
||||
const toggleJobSheet = useCallback(
|
||||
(sid: string) => {
|
||||
const all = jobSheetAllIds;
|
||||
if (!all.length) return;
|
||||
const currentlyOn = !enabledSheetIds.length
|
||||
? [...all]
|
||||
: enabledSheetIds.filter((id) => all.includes(id));
|
||||
const next = currentlyOn.includes(sid)
|
||||
? currentlyOn.filter((id) => id !== sid)
|
||||
: [...currentlyOn, sid];
|
||||
// Empty list means "all on" (new template sheets auto-included)
|
||||
setEnabledSheetIds(next.length === all.length ? [] : next);
|
||||
},
|
||||
[jobSheetAllIds, enabledSheetIds],
|
||||
);
|
||||
|
||||
const setJobTemplateAndSheets = useCallback(
|
||||
(nextTplId: string, presetIds?: string[] | null) => {
|
||||
setTemplateId(nextTplId);
|
||||
const tpl = templates.find((x) => x.id === nextTplId);
|
||||
const all = tpl ? templateSheets(tpl).map((s) => sheetIdentity(s)).filter(Boolean) : [];
|
||||
if (presetIds && presetIds.length) {
|
||||
const kept = presetIds.filter((id) => all.includes(id));
|
||||
setEnabledSheetIds(kept.length === all.length ? [] : kept);
|
||||
} else {
|
||||
setEnabledSheetIds([]);
|
||||
}
|
||||
},
|
||||
[templates],
|
||||
);
|
||||
|
||||
const activeTplSheet = tplSheets[tplSheetIdx] || null;
|
||||
const activeTplFields = useMemo(() => {
|
||||
if (!activeTplSheet) return [];
|
||||
|
|
@ -1444,8 +1510,18 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
};
|
||||
|
||||
const resetJobForm = (preset?: Partial<Job>) => {
|
||||
const tplId = preset?.template_id || templates[0]?.id || "";
|
||||
setName(preset?.name || t("bizCompare.defaultJobName"));
|
||||
setTemplateId(preset?.template_id || templates[0]?.id || "");
|
||||
setTemplateId(tplId);
|
||||
const tpl = templates.find((x) => x.id === tplId);
|
||||
const all = tpl ? templateSheets(tpl).map((s) => sheetIdentity(s)).filter(Boolean) : [];
|
||||
const presetIds = (preset?.enabled_sheet_ids || []).map(String).filter(Boolean);
|
||||
if (presetIds.length) {
|
||||
const kept = presetIds.filter((id) => all.includes(id));
|
||||
setEnabledSheetIds(kept.length === all.length ? [] : kept);
|
||||
} else {
|
||||
setEnabledSheetIds([]);
|
||||
}
|
||||
setMappingId(preset?.mapping_id || "");
|
||||
setBeforeTaskId(preset?.before_task_id || "");
|
||||
setAfterTaskId(preset?.after_task_id || "");
|
||||
|
|
@ -1460,26 +1536,85 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
}
|
||||
};
|
||||
|
||||
const jobConfigBody = () => ({
|
||||
name,
|
||||
template_id: templateId,
|
||||
mapping_id: mappingId,
|
||||
before_task_id: beforeTaskId,
|
||||
after_task_id: afterTaskId || beforeTaskId,
|
||||
before_batch_id: beforeBatchId,
|
||||
after_batch_id: mode === "manual" ? afterBatchId : "",
|
||||
mode,
|
||||
enabled_sheet_ids: enabledSheetIds,
|
||||
});
|
||||
|
||||
const closeCreateJob = () => {
|
||||
setJobCreateOpen(false);
|
||||
setCreateStep(0);
|
||||
};
|
||||
|
||||
const openCreateJob = () => {
|
||||
resetJobForm();
|
||||
setCreateStep(0);
|
||||
setJobCreateOpen(true);
|
||||
};
|
||||
|
||||
const canAdvanceCreateStep = (step: CreateJobStep): boolean => {
|
||||
if (step === 0) return Boolean(name.trim() && templateId);
|
||||
if (step === 1) return enabledJobSheetCount > 0;
|
||||
if (step === 2) {
|
||||
if (!beforeTaskId || !beforeBatchId) return false;
|
||||
if (mode === "manual" && !afterBatchId) return false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const createStepBlockReason = (step: CreateJobStep): string | null => {
|
||||
if (step === 0) {
|
||||
if (!name.trim()) return t("bizCompare.needJobName");
|
||||
if (!templateId) return t("bizCompare.needTemplate");
|
||||
return null;
|
||||
}
|
||||
if (step === 1) {
|
||||
if (!enabledJobSheetCount) return t("bizCompare.needSheets");
|
||||
return null;
|
||||
}
|
||||
if (step === 2) {
|
||||
if (!beforeTaskId || !beforeBatchId) return t("bizCompare.needBeforeBatch");
|
||||
if (mode === "manual" && !afterBatchId) return t("bizCompare.needAfterBatch");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const onCreateNext = () => {
|
||||
const reason = createStepBlockReason(createStep);
|
||||
if (reason) {
|
||||
showError(reason);
|
||||
return;
|
||||
}
|
||||
setCreateStep((s) => Math.min(CREATE_JOB_STEPS - 1, (s + 1) as CreateJobStep) as CreateJobStep);
|
||||
};
|
||||
|
||||
const onCreateBack = () => {
|
||||
setCreateStep((s) => Math.max(0, s - 1) as CreateJobStep);
|
||||
};
|
||||
|
||||
const createJob = async () => {
|
||||
const reason =
|
||||
createStepBlockReason(0) ||
|
||||
createStepBlockReason(1) ||
|
||||
createStepBlockReason(2);
|
||||
if (reason) {
|
||||
showError(reason);
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
const j = await bizCompareCreateJob({
|
||||
name,
|
||||
template_id: templateId,
|
||||
mapping_id: mappingId,
|
||||
before_task_id: beforeTaskId,
|
||||
after_task_id: afterTaskId || beforeTaskId,
|
||||
before_batch_id: beforeBatchId,
|
||||
after_batch_id: mode === "manual" ? afterBatchId : "",
|
||||
mode,
|
||||
});
|
||||
const j = await bizCompareCreateJob(jobConfigBody());
|
||||
showOk(t("bizCompare.created"));
|
||||
setJobCreateOpen(false);
|
||||
closeCreateJob();
|
||||
await refresh();
|
||||
await openJob(String(j.id));
|
||||
} catch (e) {
|
||||
|
|
@ -1520,18 +1655,13 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
|
||||
const saveJobConfig = async () => {
|
||||
if (!jobId) return;
|
||||
if (!enabledJobSheetCount) {
|
||||
showError(t("bizCompare.needSheets"));
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
await bizCompareUpdateJob(jobId, {
|
||||
name,
|
||||
template_id: templateId,
|
||||
mapping_id: mappingId,
|
||||
before_task_id: beforeTaskId,
|
||||
after_task_id: afterTaskId || beforeTaskId,
|
||||
before_batch_id: beforeBatchId,
|
||||
after_batch_id: mode === "manual" ? afterBatchId : "",
|
||||
mode,
|
||||
});
|
||||
await bizCompareUpdateJob(jobId, jobConfigBody());
|
||||
showOk(t("bizCompare.jobSaved"));
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
|
|
@ -1543,15 +1673,13 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
|
||||
const runNow = async () => {
|
||||
if (!jobId) return;
|
||||
if (!enabledJobSheetCount) {
|
||||
showError(t("bizCompare.needSheets"));
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
await bizCompareUpdateJob(jobId, {
|
||||
before_batch_id: beforeBatchId,
|
||||
after_batch_id: mode === "manual" ? afterBatchId : "",
|
||||
mode,
|
||||
mapping_id: mappingId,
|
||||
template_id: templateId,
|
||||
});
|
||||
await bizCompareUpdateJob(jobId, jobConfigBody());
|
||||
const run = await bizCompareRunJob(jobId);
|
||||
setRunDetail(run);
|
||||
setJobDetailTab("result");
|
||||
|
|
@ -1578,36 +1706,120 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
}
|
||||
};
|
||||
|
||||
const renderJobForm = (compact = false) => (
|
||||
<div className="bs-cmp-form" style={{ display: "grid", gap: 8 }}>
|
||||
<label className="ui-field ui-field--full">
|
||||
<span className="ui-field__label">{t("bizCompare.jobName")}</span>
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</label>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.template")}
|
||||
value={templateId}
|
||||
onChange={(e) => setTemplateId(e.target.value)}
|
||||
fullWidth
|
||||
>
|
||||
{templates.map((tpl) => {
|
||||
const mids = tpl.metric_ids || templateSheets(tpl).map((s) => s.metric_id);
|
||||
const removeRun = async (runId: string) => {
|
||||
if (!runId) return;
|
||||
if (!window.confirm(t("bizCompare.confirmDeleteRun"))) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
const wasCurrent = String(runDetail?.id || "") === runId;
|
||||
await bizCompareDeleteRun(runId);
|
||||
let nextRuns: typeof runs = [];
|
||||
if (jobId) {
|
||||
const r = await bizCompareListRuns(jobId);
|
||||
nextRuns = r.items || [];
|
||||
} else {
|
||||
nextRuns = (runs || []).filter((r) => String(r.id) !== runId);
|
||||
}
|
||||
setRuns(nextRuns);
|
||||
if (wasCurrent) {
|
||||
if (nextRuns.length) {
|
||||
await loadRun(String(nextRuns[0].id));
|
||||
} else {
|
||||
setRunDetail(null);
|
||||
setResultSheetId("");
|
||||
}
|
||||
}
|
||||
showOk(t("bizCompare.runDeleted"));
|
||||
} catch (e) {
|
||||
showError(formatErr(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderSheetChips = () => (
|
||||
<div className="bm-create__pane" style={{ padding: 0, border: "none", background: "transparent" }}>
|
||||
<div className="bm-mapping__label">
|
||||
{t("bizCompare.enabledSheets")}
|
||||
<span className="muted" style={{ fontWeight: 400, marginLeft: 8 }}>
|
||||
{enabledJobSheetCount}/{jobSheetAllIds.length || 0}
|
||||
</span>
|
||||
</div>
|
||||
<p className="muted bm-hint">{t("bizCompare.enabledSheetsHint")}</p>
|
||||
<div className="bm-metric-chips">
|
||||
{jobTemplateSheets.map((s) => {
|
||||
const sid = sheetIdentity(s);
|
||||
const on = isJobSheetOn(sid);
|
||||
return (
|
||||
<option key={tpl.id} value={tpl.id}>
|
||||
{tpl.name} ({mids.length} {t("bizCompare.sheetsUnit")})
|
||||
</option>
|
||||
<button
|
||||
key={sid}
|
||||
type="button"
|
||||
className={`bm-metric-chip${on ? " is-on" : ""}`}
|
||||
onClick={() => toggleJobSheet(sid)}
|
||||
title={s.metric_id !== sid ? s.metric_id : undefined}
|
||||
>
|
||||
{sheetLabel(s)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</FieldSelect>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.mode")}
|
||||
value={mode}
|
||||
onChange={(e) => setMode(e.target.value as "manual" | "auto")}
|
||||
fullWidth
|
||||
>
|
||||
<option value="manual">{t("bizCompare.modeManual")}</option>
|
||||
<option value="auto">{t("bizCompare.modeAuto")}</option>
|
||||
</FieldSelect>
|
||||
</div>
|
||||
{!jobTemplateSheets.length ? (
|
||||
<p className="muted bm-hint">{t("bizCompare.noTemplateSheets")}</p>
|
||||
) : null}
|
||||
{jobTemplateSheets.length && !enabledJobSheetCount ? (
|
||||
<p className="form-error bm-hint">{t("bizCompare.needSheets")}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderMappingBlock = () => (
|
||||
<div className="bs-cmp-mapping">
|
||||
<h4 style={{ margin: "8px 0" }}>{t("bizCompare.mapping")}</h4>
|
||||
<p className="muted">{t("bizCompare.mappingOptionalHint")}</p>
|
||||
<div className="filter-inline" style={{ marginBottom: 8 }}>
|
||||
<FieldSelect
|
||||
value={mappingId}
|
||||
onChange={(e) => {
|
||||
const id = e.target.value;
|
||||
if (id) loadMappingText(id);
|
||||
else {
|
||||
setMappingId("");
|
||||
setMapText("");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="">{t("bizCompare.newMapping")}</option>
|
||||
{mappings.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name}
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
<Input value={mapName} placeholder={t("bizCompare.mapName")} onChange={(e) => setMapName(e.target.value)} />
|
||||
<Button size="sm" variant="secondary" isDisabled={busy} onPress={() => void saveMapping()}>
|
||||
{t("bizCompare.saveMapping")}
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" isDisabled={busy} onPress={() => void doValidate()}>
|
||||
{t("bizCompare.validateMapping")}
|
||||
</Button>
|
||||
</div>
|
||||
<textarea
|
||||
value={mapText}
|
||||
onChange={(e) => setMapText(e.target.value)}
|
||||
placeholder={t("bizCompare.mapHint")}
|
||||
rows={5}
|
||||
style={{ width: "100%", fontFamily: "ui-monospace, monospace" }}
|
||||
/>
|
||||
{validateOut ? (
|
||||
<pre className="muted" style={{ fontSize: 12, maxHeight: 120, overflow: "auto" }}>
|
||||
{JSON.stringify(validateOut, null, 2)}
|
||||
</pre>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderBatchFields = () => (
|
||||
<>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.beforeTask")}
|
||||
value={beforeTaskId}
|
||||
|
|
@ -1664,52 +1876,42 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
) : (
|
||||
<p className="muted">{t("bizCompare.autoHint")}</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
{!compact ? (
|
||||
<div className="bs-cmp-mapping">
|
||||
<h4 style={{ margin: "8px 0" }}>{t("bizCompare.mapping")}</h4>
|
||||
<p className="muted">{t("bizCompare.mappingOptionalHint")}</p>
|
||||
<div className="filter-inline" style={{ marginBottom: 8 }}>
|
||||
<FieldSelect
|
||||
value={mappingId}
|
||||
onChange={(e) => {
|
||||
const id = e.target.value;
|
||||
if (id) loadMappingText(id);
|
||||
else {
|
||||
setMappingId("");
|
||||
setMapText("");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="">{t("bizCompare.newMapping")}</option>
|
||||
{mappings.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name}
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
<Input value={mapName} placeholder={t("bizCompare.mapName")} onChange={(e) => setMapName(e.target.value)} />
|
||||
<Button size="sm" variant="secondary" isDisabled={busy} onPress={() => void saveMapping()}>
|
||||
{t("bizCompare.saveMapping")}
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" isDisabled={busy} onPress={() => void doValidate()}>
|
||||
{t("bizCompare.validateMapping")}
|
||||
</Button>
|
||||
</div>
|
||||
<textarea
|
||||
value={mapText}
|
||||
onChange={(e) => setMapText(e.target.value)}
|
||||
placeholder={t("bizCompare.mapHint")}
|
||||
rows={5}
|
||||
style={{ width: "100%", fontFamily: "ui-monospace, monospace" }}
|
||||
/>
|
||||
{validateOut ? (
|
||||
<pre className="muted" style={{ fontSize: 12, maxHeight: 120, overflow: "auto" }}>
|
||||
{JSON.stringify(validateOut, null, 2)}
|
||||
</pre>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
const renderJobForm = () => (
|
||||
<div className="bs-cmp-form" style={{ display: "grid", gap: 8 }}>
|
||||
<label className="ui-field ui-field--full">
|
||||
<span className="ui-field__label">{t("bizCompare.jobName")}</span>
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</label>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.template")}
|
||||
value={templateId}
|
||||
onChange={(e) => setJobTemplateAndSheets(e.target.value)}
|
||||
fullWidth
|
||||
>
|
||||
{templates.map((tpl) => {
|
||||
const n = templateSheets(tpl).length;
|
||||
return (
|
||||
<option key={tpl.id} value={tpl.id}>
|
||||
{tpl.name} ({n} {t("bizCompare.sheetsUnit")})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</FieldSelect>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.mode")}
|
||||
value={mode}
|
||||
onChange={(e) => setMode(e.target.value as "manual" | "auto")}
|
||||
fullWidth
|
||||
>
|
||||
<option value="manual">{t("bizCompare.modeManual")}</option>
|
||||
<option value="auto">{t("bizCompare.modeAuto")}</option>
|
||||
</FieldSelect>
|
||||
{renderSheetChips()}
|
||||
{renderBatchFields()}
|
||||
{renderMappingBlock()}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -1896,7 +2098,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
isDisabled={busy}
|
||||
onPress={() => void removeJob(j.id)}
|
||||
>
|
||||
{t("bizCompare.delete")}
|
||||
{t("bizCompare.deleteJob")}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -2101,21 +2303,22 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
onChange={(next) => updateActiveSheet({ row_filters: fromOrGroups(next) })}
|
||||
/>
|
||||
) : (
|
||||
<div className="pt-list-table-wrap ct-field-table-wrap">
|
||||
<>
|
||||
<p className="muted ct-field-hint">{t("bizCompare.fieldsHintShort")}</p>
|
||||
<table className="data-table pt-list-table ct-field-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("bizCompare.field")}</th>
|
||||
<th title={t("bizCompare.keyFields")}>{t("bizCompare.keyFields")}</th>
|
||||
<th title={t("bizCompare.ifaceFields")}>{t("bizCompare.ifaceFields")}</th>
|
||||
<th title={t("bizCompare.compareFields")}>{t("bizCompare.compareFields")}</th>
|
||||
<th>{t("bizCompare.compareMode")}</th>
|
||||
<th>{t("bizCompare.tolerance")}</th>
|
||||
<th>{t("bizCompare.displayField")}</th>
|
||||
<th>{t("bizCompare.normalizeField")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<div className="pt-list-table-wrap ct-field-table-wrap">
|
||||
<table className="data-table pt-list-table ct-field-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("bizCompare.field")}</th>
|
||||
<th title={t("bizCompare.keyFields")}>{t("bizCompare.keyFields")}</th>
|
||||
<th title={t("bizCompare.ifaceFields")}>{t("bizCompare.ifaceFields")}</th>
|
||||
<th title={t("bizCompare.compareFields")}>{t("bizCompare.compareFields")}</th>
|
||||
<th>{t("bizCompare.compareMode")}</th>
|
||||
<th>{t("bizCompare.tolerance")}</th>
|
||||
<th>{t("bizCompare.displayField")}</th>
|
||||
<th>{t("bizCompare.normalizeField")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{activeTplFields.map((f) => {
|
||||
const isKey = activeTplSheet.key_fields.includes(f.name);
|
||||
|
|
@ -2266,16 +2469,17 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
</tr>
|
||||
);
|
||||
})}
|
||||
{!activeTplFields.length ? (
|
||||
<tr>
|
||||
<td colSpan={8}>
|
||||
<div className="pt-list-empty">{t("bizCompare.noMetricFields")}</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!activeTplFields.length ? (
|
||||
<tr>
|
||||
<td colSpan={8}>
|
||||
<div className="pt-list-empty">{t("bizCompare.noMetricFields")}</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -2292,20 +2496,118 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
</Modal.Footer>
|
||||
</AppModalShell>
|
||||
|
||||
{/* Create job */}
|
||||
<AppModalShell open={jobCreateOpen} onClose={() => setJobCreateOpen(false)} size="lg">
|
||||
{/* Create job — guided steps */}
|
||||
<AppModalShell
|
||||
open={jobCreateOpen}
|
||||
onClose={closeCreateJob}
|
||||
size="lg"
|
||||
className="bm-create-modal"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Heading>{t("bizCompare.createCompare")}</Modal.Heading>
|
||||
<Modal.CloseTrigger />
|
||||
</Modal.Header>
|
||||
<Modal.Body className="flex flex-col gap-3">{renderJobForm(true)}</Modal.Body>
|
||||
<Modal.Body className="flex flex-col gap-3 bm-create">
|
||||
<nav className="bm-steps" aria-label={t("bizCompare.createSteps")}>
|
||||
{(
|
||||
[
|
||||
t("bizCompare.stepBasics"),
|
||||
t("bizCompare.stepSheets"),
|
||||
t("bizCompare.stepBatches"),
|
||||
t("bizCompare.stepMapping"),
|
||||
] as const
|
||||
).map((label, i) => {
|
||||
const done = i < createStep;
|
||||
const active = i === createStep;
|
||||
return (
|
||||
<button
|
||||
key={label}
|
||||
type="button"
|
||||
className={`bm-steps__item${active ? " is-active" : ""}${done ? " is-done" : ""}`}
|
||||
onClick={() => {
|
||||
if (i <= createStep) {
|
||||
setCreateStep(i as CreateJobStep);
|
||||
return;
|
||||
}
|
||||
let ok = true;
|
||||
for (let s = 0; s < i; s++) {
|
||||
if (!canAdvanceCreateStep(s as CreateJobStep)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) setCreateStep(i as CreateJobStep);
|
||||
}}
|
||||
>
|
||||
<span className="bm-steps__num">{done ? "✓" : i + 1}</span>
|
||||
<span className="bm-steps__label">{label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{createStep === 0 ? (
|
||||
<div className="bm-create__pane bs-cmp-form" style={{ display: "grid", gap: 8 }}>
|
||||
<label className="ui-field ui-field--full">
|
||||
<span className="ui-field__label">{t("bizCompare.jobName")}</span>
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</label>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.template")}
|
||||
value={templateId}
|
||||
onChange={(e) => setJobTemplateAndSheets(e.target.value)}
|
||||
fullWidth
|
||||
>
|
||||
{templates.map((tpl) => {
|
||||
const n = templateSheets(tpl).length;
|
||||
return (
|
||||
<option key={tpl.id} value={tpl.id}>
|
||||
{tpl.name} ({n} {t("bizCompare.sheetsUnit")})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</FieldSelect>
|
||||
<FieldSelect
|
||||
label={t("bizCompare.mode")}
|
||||
value={mode}
|
||||
onChange={(e) => setMode(e.target.value as "manual" | "auto")}
|
||||
fullWidth
|
||||
>
|
||||
<option value="manual">{t("bizCompare.modeManual")}</option>
|
||||
<option value="auto">{t("bizCompare.modeAuto")}</option>
|
||||
</FieldSelect>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{createStep === 1 ? <div className="bm-create__pane">{renderSheetChips()}</div> : null}
|
||||
|
||||
{createStep === 2 ? (
|
||||
<div className="bm-create__pane bs-cmp-form" style={{ display: "grid", gap: 8 }}>
|
||||
{renderBatchFields()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{createStep === 3 ? <div className="bm-create__pane">{renderMappingBlock()}</div> : null}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void createJob()}>
|
||||
{t("bizCompare.createCompare")}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onPress={() => setJobCreateOpen(false)}>
|
||||
<Button size="sm" variant="secondary" onPress={closeCreateJob}>
|
||||
{t("bizState.cancel")}
|
||||
</Button>
|
||||
<div className="bm-create__footer-spacer" />
|
||||
{createStep > 0 ? (
|
||||
<Button size="sm" variant="secondary" onPress={onCreateBack}>
|
||||
{t("bizCompare.back")}
|
||||
</Button>
|
||||
) : null}
|
||||
{createStep < CREATE_JOB_STEPS - 1 ? (
|
||||
<Button size="sm" variant="primary" onPress={onCreateNext}>
|
||||
{t("bizCompare.next")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void createJob()}>
|
||||
{t("bizCompare.createCompare")}
|
||||
</Button>
|
||||
)}
|
||||
</Modal.Footer>
|
||||
</AppModalShell>
|
||||
|
||||
|
|
@ -2343,7 +2645,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
</div>
|
||||
|
||||
{jobDetailTab === "config" ? (
|
||||
renderJobForm(false)
|
||||
renderJobForm()
|
||||
) : (
|
||||
<div
|
||||
ref={boardRef}
|
||||
|
|
@ -2375,13 +2677,20 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
);
|
||||
const bl = sideDeviceName(before);
|
||||
const al = sideDeviceName(after);
|
||||
const when = formatSystemTime((r as any).created_at) || "";
|
||||
return (
|
||||
<option key={r.id} value={r.id}>
|
||||
{when ? `${when} · ` : ""}
|
||||
{bl} {sideCollectTime(before)} → {al} {sideCollectTime(after)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
{runs.length ? (
|
||||
<span className="muted bs-cmp-board__run-count">
|
||||
{t("bizCompare.runCount", { n: String(runs.length) })}
|
||||
</span>
|
||||
) : null}
|
||||
{runDetail ? (
|
||||
<div className="bs-cmp-sides" aria-label={t("bizCompare.sidesTitle")}>
|
||||
{(() => {
|
||||
|
|
@ -2438,6 +2747,14 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
>
|
||||
{t("bizCompare.exportTables")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
isDisabled={busy || !runDetail?.id}
|
||||
onPress={() => void removeRun(String(runDetail?.id || ""))}
|
||||
>
|
||||
{t("bizCompare.deleteRun")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
|
|
@ -2782,6 +3099,11 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void runNow()}>
|
||||
{t("bizCompare.runNow")}
|
||||
</Button>
|
||||
{jobId ? (
|
||||
<Button size="sm" variant="danger" isDisabled={busy} onPress={() => void removeJob(jobId)}>
|
||||
{t("bizCompare.deleteJob")}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -2793,6 +3115,14 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
>
|
||||
{t("bizCompare.exportTables")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
isDisabled={busy || !runDetail?.id}
|
||||
onPress={() => void removeRun(String(runDetail?.id || ""))}
|
||||
>
|
||||
{t("bizCompare.deleteRun")}
|
||||
</Button>
|
||||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void runNow()}>
|
||||
{t("bizCompare.runNow")}
|
||||
</Button>
|
||||
|
|
@ -2801,11 +3131,6 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
<Button size="sm" variant="ghost" onPress={closeJob}>
|
||||
{t("bizState.cancel")}
|
||||
</Button>
|
||||
{jobId ? (
|
||||
<Button size="sm" variant="danger" isDisabled={busy} onPress={() => void removeJob(jobId)}>
|
||||
{t("bizCompare.delete")}
|
||||
</Button>
|
||||
) : null}
|
||||
</Modal.Footer>
|
||||
</AppModalShell>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ import {
|
|||
bizStateListProfiles,
|
||||
bizStateListTasks,
|
||||
bizStatePatchTask,
|
||||
bizStatePauseTask,
|
||||
bizStatePurgeTask,
|
||||
bizStateSetBatchBaseline,
|
||||
bizStateSetBindings,
|
||||
bizStateStartTask,
|
||||
fetchCliTargets,
|
||||
formatErr,
|
||||
} from "../../services/api";
|
||||
|
|
@ -538,7 +540,11 @@ export function BizStatePage() {
|
|||
const setScheduleEnabled = async (id: string, enabled: boolean) => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await bizStatePatchTask(id, { status: enabled ? "running" : "paused" });
|
||||
if (enabled) {
|
||||
await bizStateStartTask(id);
|
||||
} else {
|
||||
await bizStatePauseTask(id);
|
||||
}
|
||||
showOk(enabled ? t("bizState.started") : t("bizState.paused"));
|
||||
if (taskId === id) await loadTask(id);
|
||||
await refreshTasks();
|
||||
|
|
@ -981,10 +987,12 @@ export function BizStatePage() {
|
|||
<div className="pt-list-actions" style={{ flexWrap: "wrap", gap: 4 }}>
|
||||
{row.collect_running ? (
|
||||
<NmStatusChip color="accent">{t("bizState.collecting")}</NmStatusChip>
|
||||
) : row.status === "running" ? (
|
||||
<NmStatusChip color="success">{t("bizState.scheduleOn")}</NmStatusChip>
|
||||
) : row.status === "paused" ? (
|
||||
<NmStatusChip color="warning">{t("bizState.statusPaused")}</NmStatusChip>
|
||||
) : (
|
||||
<NmStatusChip color={row.status === "running" ? "success" : "default"}>
|
||||
{row.status === "running" ? t("bizState.scheduleOn") : t("bizState.scheduleOff")}
|
||||
</NmStatusChip>
|
||||
<NmStatusChip color="default">{t("bizState.scheduleOff")}</NmStatusChip>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -997,6 +1005,25 @@ export function BizStatePage() {
|
|||
<Button size="sm" variant="ghost" onPress={() => void openTask(row.id, "batches")}>
|
||||
{t("bizState.batches")}
|
||||
</Button>
|
||||
{row.status === "running" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
isDisabled={busy}
|
||||
onPress={() => void setScheduleEnabled(row.id, false)}
|
||||
>
|
||||
{t("bizState.pause")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
isDisabled={busy}
|
||||
onPress={() => void setScheduleEnabled(row.id, true)}
|
||||
>
|
||||
{t("bizState.start")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
|
|
@ -1161,6 +1188,25 @@ export function BizStatePage() {
|
|||
/>
|
||||
<span>{t("bizState.scheduleEnabled")}</span>
|
||||
</label>
|
||||
{detail.status === "running" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
isDisabled={busy}
|
||||
onPress={() => void setScheduleEnabled(taskId, false)}
|
||||
>
|
||||
{t("bizState.pause")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
isDisabled={busy}
|
||||
onPress={() => void setScheduleEnabled(taskId, true)}
|
||||
>
|
||||
{t("bizState.start")}
|
||||
</Button>
|
||||
)}
|
||||
<label className="config-sync-policy-field">
|
||||
<span>{t("bizState.interval")}</span>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -1804,6 +1804,12 @@ export const bizStateGetTask = (taskId: string) =>
|
|||
export const bizStatePatchTask = (taskId: string, body: Record<string, unknown>) =>
|
||||
apiPatch<Record<string, unknown>>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}`, body);
|
||||
|
||||
export const bizStatePauseTask = (taskId: string) =>
|
||||
apiPost<Record<string, unknown>>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/pause`, {});
|
||||
|
||||
export const bizStateStartTask = (taskId: string) =>
|
||||
apiPost<Record<string, unknown>>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/start`, {});
|
||||
|
||||
export const bizStateDeleteTask = (taskId: string) =>
|
||||
apiDelete<{ ok: boolean }>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}`);
|
||||
|
||||
|
|
@ -1985,6 +1991,11 @@ export const bizCompareListRuns = (jobId: string, limit = 20) =>
|
|||
export const bizCompareGetRun = (runId: string) =>
|
||||
apiGet<Record<string, unknown>>(`/v1/biz-state/compare/runs/${encodeURIComponent(runId)}`);
|
||||
|
||||
export const bizCompareDeleteRun = (runId: string) =>
|
||||
apiDelete<{ ok: boolean; job_id?: string; run_id?: string }>(
|
||||
`/v1/biz-state/compare/runs/${encodeURIComponent(runId)}`,
|
||||
);
|
||||
|
||||
export const bizCompareListRunDiffs = (params: {
|
||||
runId: string;
|
||||
metricId?: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue