mirror of
https://github.com/hansjone/netx.git
synced 2026-10-10 02:00:45 +08:00
Checkpoint before workbench facade redesign: list defaults, API key quota, toast portal, topology canvas editor toolbar with More menu, and trusted-proxy client IP for sessions. Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import { Button } from "@heroui/react";
|
|
import { useQuery, keepPreviousData } from "@tanstack/react-query";
|
|
import { fetchPortTrafficCompare } from "../../services/api";
|
|
import { queryKeys } from "../../constants/queryKeys";
|
|
import { useI18n } from "../../i18n";
|
|
import type { PortTrafficBoardPanel } from "../../types";
|
|
import { PortTrafficWall, type WallYMode } from "./PortTrafficWall";
|
|
|
|
const POLL_MS = 5000;
|
|
const EMPTY: never[] = [];
|
|
|
|
type Props = {
|
|
boardId: string;
|
|
panel: PortTrafficBoardPanel;
|
|
editing?: boolean;
|
|
dense?: boolean;
|
|
onEdit?: () => void;
|
|
onRemove?: () => void;
|
|
onMove?: (dir: -1 | 1) => void;
|
|
};
|
|
|
|
export function PortTrafficBoardPanelCell({
|
|
boardId,
|
|
panel,
|
|
editing,
|
|
dense,
|
|
onEdit,
|
|
onRemove,
|
|
onMove,
|
|
}: 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(
|
|
panel.target_id,
|
|
panel.range_hours,
|
|
baseline,
|
|
baseline === "custom" ? panel.offset_hours : 0,
|
|
panel.baseline_target_id || "",
|
|
aheadHours,
|
|
),
|
|
boardId,
|
|
panel.id,
|
|
],
|
|
queryFn: () =>
|
|
fetchPortTrafficCompare({
|
|
targetId: panel.target_id,
|
|
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,
|
|
staleTime: 1000,
|
|
placeholderData: keepPreviousData,
|
|
refetchInterval: POLL_MS,
|
|
});
|
|
|
|
const points = compareQuery.data?.current ?? EMPTY;
|
|
const baselinePoints =
|
|
baseline === "off" && !panel.baseline_target_id
|
|
? EMPTY
|
|
: (compareQuery.data?.baseline ?? EMPTY);
|
|
const target = panel.target || compareQuery.data?.meta?.current_target || null;
|
|
const baselineTarget =
|
|
compareQuery.data?.meta?.baseline_target || panel.baseline_target || null;
|
|
const title =
|
|
panel.title ||
|
|
(target ? `${target.ne_name || target.ne_ip || "—"} · ${target.ifname}` : t("portTraffic.pickPort"));
|
|
|
|
const rangeLabel = `${panel.range_hours}h${
|
|
baseline === "off"
|
|
? ""
|
|
: baseline === "day"
|
|
? ` · ${t("portTraffic.compareDay")}`
|
|
: baseline === "week"
|
|
? ` · ${t("portTraffic.compareWeek")}`
|
|
: baseline === "shift"
|
|
? ` · ${t("portTraffic.compareShift")}`
|
|
: ` · ${t("portTraffic.compareCustom")}`
|
|
}${panel.baseline_target_id ? ` · ${t("portTraffic.mapBaselinePort")}` : ""}`;
|
|
|
|
return (
|
|
<div
|
|
className={`pt-board-panel${panel.stale ? " is-stale" : ""}`}
|
|
style={{ gridColumn: `span ${Math.max(1, panel.col_span || 1)}` }}
|
|
>
|
|
{editing ? (
|
|
<div className="pt-board-panel__editbar">
|
|
<span className="pt-board-panel__edit-title" title={title}>
|
|
{title}
|
|
</span>
|
|
<div className="btn-row">
|
|
<Button size="sm" variant="secondary" isIconOnly onPress={() => onMove?.(-1)}>
|
|
↑
|
|
</Button>
|
|
<Button size="sm" variant="secondary" isIconOnly onPress={() => onMove?.(1)}>
|
|
↓
|
|
</Button>
|
|
<Button size="sm" variant="secondary" onPress={() => onEdit?.()}>
|
|
{t("portTraffic.edit")}
|
|
</Button>
|
|
<Button size="sm" variant="danger" onPress={() => onRemove?.()}>
|
|
{t("portTraffic.delete")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
{panel.stale ? (
|
|
<div className="pt-board-panel__stale">
|
|
<p>{t("portTraffic.boardPanelStale")}</p>
|
|
{editing ? (
|
|
<Button variant="primary" onPress={() => onEdit?.()}>
|
|
{t("portTraffic.boardRetarget")}
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
) : (
|
|
<PortTrafficWall
|
|
target={target}
|
|
baselineTarget={baselineTarget}
|
|
points={points}
|
|
baselinePoints={baselinePoints}
|
|
yMode={(panel.y_mode as WallYMode) || "auto"}
|
|
rangeLabel={rangeLabel}
|
|
loading={compareQuery.isLoading || compareQuery.isFetching}
|
|
hint={t("portTraffic.waitSamples")}
|
|
dense={dense}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|