Add ops task overview, chart ahead window, and WebCRT lifecycle status.

Unify live runners in /audit/tasks (including UME sync and connect tests), extend port-traffic compare past now via ahead_hours, and distinguish WebCRT connecting/ready/detached.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-02 14:05:59 +08:00
parent 3f0787da11
commit 3908b78dbb
28 changed files with 1083 additions and 54 deletions

View file

@ -29,6 +29,10 @@ export function PortTrafficBoardPanelCell({
}: Props) {
const { t } = useI18n();
const baseline = panel.baseline || "off";
const aheadHours =
baseline === "off" && !panel.baseline_target_id
? 0
: Math.max(0, Number(panel.ahead_hours ?? 1) || 0);
const compareQuery = useQuery({
queryKey: [
...queryKeys.portTrafficCompare(
@ -37,6 +41,7 @@ export function PortTrafficBoardPanelCell({
baseline,
baseline === "custom" ? panel.offset_hours : 0,
panel.baseline_target_id || "",
aheadHours,
),
boardId,
panel.id,
@ -47,6 +52,7 @@ export function PortTrafficBoardPanelCell({
rangeHours: panel.range_hours,
baseline,
offsetHours: baseline === "custom" ? panel.offset_hours : undefined,
aheadHours: aheadHours || undefined,
baselineTargetId: panel.baseline_target_id || undefined,
}),
enabled: Boolean(panel.target_id) && !panel.stale,

View file

@ -41,6 +41,7 @@ function panelToIn(p: PortTrafficBoardPanel): PortTrafficBoardPanelIn {
range_hours: p.range_hours,
baseline: p.baseline || "off",
offset_hours: p.offset_hours || 0,
ahead_hours: p.ahead_hours ?? 1,
baseline_target_id: p.baseline_target_id || "",
y_mode: p.y_mode || "auto",
ord: p.ord,
@ -49,6 +50,10 @@ function panelToIn(p: PortTrafficBoardPanel): PortTrafficBoardPanelIn {
};
}
function normalizePanel(p: PortTrafficBoardPanel): PortTrafficBoardPanel {
return { ...p, ahead_hours: p.ahead_hours ?? 1 };
}
/** Dedicated board wall tab at `/port-traffic/wall/:boardId` (outside Network shell). */
export function PortTrafficWallPage() {
const { t } = useI18n();
@ -136,7 +141,7 @@ export function PortTrafficWallPage() {
useEffect(() => {
if (!board || editing) return;
setDraftPanels(board.panels || []);
setDraftPanels((board.panels || []).map(normalizePanel));
setDraftName(board.name || "");
setDraftCols(board.cols || 2);
}, [board, editing]);
@ -188,7 +193,7 @@ export function PortTrafficWallPage() {
const startEdit = () => {
if (!board) return;
setDraftPanels([...(board.panels || [])]);
setDraftPanels((board.panels || []).map(normalizePanel));
setDraftName(board.name || "");
setDraftCols(board.cols || 2);
setEditing(true);
@ -201,7 +206,7 @@ export function PortTrafficWallPage() {
setDirty(false);
closePanelSettings();
if (board) {
setDraftPanels(board.panels || []);
setDraftPanels((board.panels || []).map(normalizePanel));
setDraftName(board.name || "");
setDraftCols(board.cols || 2);
}
@ -249,6 +254,7 @@ export function PortTrafficWallPage() {
range_hours: 24,
baseline: "off",
offset_hours: 0,
ahead_hours: 1,
baseline_target_id: "",
y_mode: "auto",
ord: draftPanels.length,
@ -571,9 +577,15 @@ export function PortTrafficWallPage() {
{t("portTraffic.compare")}
<select
value={editPanel.baseline}
onChange={(e) =>
updatePanel(editPanel.id, { baseline: e.target.value as BaselineMode })
}
onChange={(e) => {
const next = e.target.value as BaselineMode;
updatePanel(editPanel.id, {
baseline: next,
...(next !== "off" && !(editPanel.ahead_hours > 0)
? { ahead_hours: 1 }
: {}),
});
}}
>
<option value="off">{t("portTraffic.compareOff")}</option>
<option value="day">{t("portTraffic.compareDay")}</option>
@ -660,6 +672,7 @@ export function PortTrafficWallPage() {
updatePanel(editPanel.id, {
baseline_target_id: tid,
baseline_target: tgt,
...(tid && !(editPanel.ahead_hours > 0) ? { ahead_hours: 1 } : {}),
});
}}
>
@ -671,12 +684,35 @@ export function PortTrafficWallPage() {
))}
</select>
</label>
{editPanel.baseline !== "off" || editPanel.baseline_target_id ? (
<label>
{t("portTraffic.aheadHours")}
<input
type="number"
min={0}
max={24}
step={1}
title={t("portTraffic.aheadHoursHint")}
value={editPanel.ahead_hours ?? 1}
onChange={(e) =>
updatePanel(editPanel.id, {
ahead_hours: Math.max(0, Math.min(24, Number(e.target.value) || 0)),
})
}
/>
</label>
) : null}
</div>
{editPanel.baseline === "week" ? (
<p className="muted" style={{ marginTop: 10 }}>
{t("portTraffic.retentionHint")}
</p>
) : null}
{editPanel.baseline_target_id ? (
<p className="muted" style={{ marginTop: 10 }}>
{t("portTraffic.mapBaselineHint")}
</p>
) : null}
<div className="modal__actions">
<button type="button" className="btn-primary" onClick={closePanelSettings}>
{t("portTraffic.logClose")}