From 108c9da00fd7adfa3967d92c6c3aee03982d7f02 Mon Sep 17 00:00:00 2001 From: oliver Date: Thu, 17 Sep 2026 19:03:37 +0800 Subject: [PATCH] Add seconds unit to biz-state collection interval picker. Co-authored-by: Cursor --- netx_api/biz_state/service.py | 4 +- web/src/i18n/en.ts | 1 + web/src/i18n/zh.ts | 1 + web/src/pages/network/BizStatePage.tsx | 74 ++++++++++++++++++-------- 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/netx_api/biz_state/service.py b/netx_api/biz_state/service.py index 5b3e98b..1d1f1aa 100644 --- a/netx_api/biz_state/service.py +++ b/netx_api/biz_state/service.py @@ -154,7 +154,7 @@ def create_task(db: Session, body: dict[str, Any]) -> dict[str, Any]: device_type=device_type, note=str(body.get("note") or "")[:256], status="draft", - interval_sec=max(3600, int(body.get("interval_sec") or 3600)), + interval_sec=max(60, int(body.get("interval_sec") or 3600)), retention_batches=max(1, int(body.get("retention_batches") or 30)), created_at=_utcnow(), updated_at=_utcnow(), @@ -228,7 +228,7 @@ def update_task(db: Session, task_id: str, body: dict[str, Any]) -> dict[str, An if "note" in body: task.note = str(body.get("note") or "")[:256] if "interval_sec" in body: - task.interval_sec = max(3600, int(body.get("interval_sec") or 3600)) + task.interval_sec = max(60, int(body.get("interval_sec") or 3600)) if "retention_batches" in body: task.retention_batches = max(1, int(body.get("retention_batches") or 30)) if "items" in body: diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 838d007..da8b399 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -194,6 +194,7 @@ const en = { interval: "Interval", intervalUnitDays: "days", intervalUnitHours: "hours", + intervalUnitSeconds: "seconds", retention: "Keep batches", saveSchedule: "Save schedule", scheduleSaved: "Schedule saved", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 444329e..b3afd19 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -194,6 +194,7 @@ const zh = { interval: "采集周期", intervalUnitDays: "天", intervalUnitHours: "小时", + intervalUnitSeconds: "秒", retention: "保留批次数", saveSchedule: "保存周期", scheduleSaved: "周期配置已保存", diff --git a/web/src/pages/network/BizStatePage.tsx b/web/src/pages/network/BizStatePage.tsx index cfcce4d..64f053d 100644 --- a/web/src/pages/network/BizStatePage.tsx +++ b/web/src/pages/network/BizStatePage.tsx @@ -74,21 +74,47 @@ type NeSourceFilter = "all" | "managed" | "ume"; const NE_PAGE_SIZE = 10; -function fmtIntervalLabel(sec: number, daysLabel: string, hoursLabel: string) { - const hours = Math.max(1, Math.round(Number(sec || 0) / 3600) || 1); - if (hours % 24 === 0) return `${hours / 24} ${daysLabel}`; - return `${hours} ${hoursLabel}`; +function fmtIntervalLabel( + sec: number, + daysLabel: string, + hoursLabel: string, + secondsLabel: string, +) { + const s = Math.max(1, Math.round(Number(sec || 0)) || 1); + if (s % 86400 === 0) return `${s / 86400} ${daysLabel}`; + if (s % 3600 === 0) return `${s / 3600} ${hoursLabel}`; + return `${s} ${secondsLabel}`; } -function secToIntervalUi(sec: number): { value: number; unit: "days" | "hours" } { - const hours = Math.max(1, Math.round(Number(sec || 3600) / 3600) || 1); - if (hours % 24 === 0) return { value: hours / 24, unit: "days" }; - return { value: hours, unit: "hours" }; +function secToIntervalUi(sec: number): { value: number; unit: "days" | "hours" | "seconds" } { + const s = Math.max(1, Math.round(Number(sec || 3600)) || 3600); + if (s % 86400 === 0) return { value: s / 86400, unit: "days" }; + if (s % 3600 === 0) return { value: s / 3600, unit: "hours" }; + return { value: s, unit: "seconds" }; } -function intervalUiToSec(value: number, unit: "days" | "hours") { +function intervalUiToSec(value: number, unit: "days" | "hours" | "seconds") { const n = Math.max(1, Number(value) || 1); - return unit === "days" ? n * 86400 : n * 3600; + if (unit === "days") return n * 86400; + if (unit === "hours") return n * 3600; + return Math.max(60, n); +} + +function intervalUnitMax(unit: "days" | "hours" | "seconds") { + if (unit === "days") return 365; + if (unit === "hours") return 8760; + return 604800; // up to 7 days in seconds +} + +function convertIntervalValue( + value: number, + from: "days" | "hours" | "seconds", + to: "days" | "hours" | "seconds", +) { + const sec = intervalUiToSec(value, from); + if (to === "days") return Math.max(1, Math.min(365, Math.round(sec / 86400) || 1)); + if (to === "hours") return Math.max(1, Math.min(8760, Math.round(sec / 3600) || 1)); + return Math.max(60, Math.min(604800, sec)); } function fmtTime(v?: string | null) { @@ -202,7 +228,7 @@ export function BizStatePage() { const [batches, setBatches] = useState([]); const [taskTab, setTaskTab] = useState("profiles"); const [intervalValue, setIntervalValue] = useState(1); - const [intervalUnit, setIntervalUnit] = useState<"days" | "hours">("hours"); + const [intervalUnit, setIntervalUnit] = useState<"days" | "hours" | "seconds">("hours"); const [retentionBatches, setRetentionBatches] = useState(30); // VRF bind (inside task modal) @@ -612,6 +638,7 @@ export function BizStatePage() { Number(row.interval_sec || 3600), t("bizState.intervalUnitDays"), t("bizState.intervalUnitHours"), + t("bizState.intervalUnitSeconds"), )} @@ -823,29 +850,32 @@ export function BizStatePage() { {t("bizState.interval")} { - const max = intervalUnit === "days" ? 365 : 8760; - setIntervalValue(Math.max(1, Math.min(max, Number(e.target.value) || 1))); + const min = intervalUnit === "seconds" ? 60 : 1; + const max = intervalUnitMax(intervalUnit); + setIntervalValue(Math.max(min, Math.min(max, Number(e.target.value) || min))); }} />