Open WebCRT in its own module tab and fix UTC naive time display.

Config/NE/topology links now focus the webcrt window instead of replacing network routes; formatSystemTime treats naive API timestamps as UTC.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-07-31 11:13:22 +08:00
parent 3fc80f55bc
commit 68883d1a84
4 changed files with 27 additions and 13 deletions

View file

@ -780,7 +780,7 @@ export function NePage() {
className="link-btn" className="link-btn"
onClick={() => onClick={() =>
openOrFocusModule({ openOrFocusModule({
moduleId: "network", moduleId: "webcrt",
path: `/webcrt?ne_id=${encodeURIComponent(row.id)}`, path: `/webcrt?ne_id=${encodeURIComponent(row.id)}`,
}) })
} }

View file

@ -676,14 +676,14 @@ export function TopologyPage() {
const umeId = node?.data.ume_ne_id; const umeId = node?.data.ume_ne_id;
if (managedId) { if (managedId) {
openOrFocusModule({ openOrFocusModule({
moduleId: "network", moduleId: "webcrt",
path: `/webcrt?ne_id=${encodeURIComponent(managedId)}`, path: `/webcrt?ne_id=${encodeURIComponent(managedId)}`,
}); });
return; return;
} }
if (umeId) { if (umeId) {
openOrFocusModule({ openOrFocusModule({
moduleId: "network", moduleId: "webcrt",
path: `/webcrt?ne_id=${encodeURIComponent(umeId)}&source=ume`, path: `/webcrt?ne_id=${encodeURIComponent(umeId)}&source=ume`,
}); });
return; return;

View file

@ -1,5 +1,4 @@
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { import {
downloadNeConfigSnapshot, downloadNeConfigSnapshot,
@ -9,6 +8,7 @@ import {
import { queryKeys } from "../../constants/queryKeys"; import { queryKeys } from "../../constants/queryKeys";
import { useI18n } from "../../i18n"; import { useI18n } from "../../i18n";
import { useToast } from "../../hooks/useToast"; import { useToast } from "../../hooks/useToast";
import { openOrFocusModule } from "../../utils/moduleWindows";
import { pageCount } from "../../utils/display"; import { pageCount } from "../../utils/display";
import { formatSystemTime } from "../../utils/time"; import { formatSystemTime } from "../../utils/time";
@ -161,11 +161,19 @@ export function NetworkConfigsPage() {
> >
{t("networkConfigs.export")} {t("networkConfigs.export")}
</button> </button>
<Link <button
to={`/webcrt?${row.source === "ume" ? "ume_ne_id" : "ne_id"}=${encodeURIComponent(row.target_id)}`} type="button"
className="link-btn"
onClick={() => {
const path =
row.source === "ume"
? `/webcrt?ne_id=${encodeURIComponent(row.target_id)}&source=ume`
: `/webcrt?ne_id=${encodeURIComponent(row.target_id)}`;
openOrFocusModule({ moduleId: "webcrt", path });
}}
> >
WebCRT WebCRT
</Link> </button>
</td> </td>
</tr> </tr>
); );

View file

@ -1,17 +1,24 @@
const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
type FormatTimeOptions = { type FormatTimeOptions = {
/** Treat timezone-less ISO timestamp strings as UTC. */ /**
* Treat timezone-less timestamps as UTC (DB/API default).
* Defaults to true so naive ISO strings show in the browser system timezone.
*/
assumeUtcNaive?: boolean; assumeUtcNaive?: boolean;
}; };
function normalizeTimeInput(value: string | number | Date, options?: FormatTimeOptions): string | number | Date { function normalizeTimeInput(value: string | number | Date, options?: FormatTimeOptions): string | number | Date {
if (!options?.assumeUtcNaive || typeof value !== "string") return value; const assumeUtc = options?.assumeUtcNaive !== false;
if (!assumeUtc || typeof value !== "string") return value;
const text = value.trim(); const text = value.trim();
if (!text) return value; if (!text) return value;
// Match "YYYY-MM-DDTHH:mm:ss(.sss)" without explicit timezone offset. // Already has explicit offset / Z.
const isIsoLike = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(text); if (/[zZ]$|[+-]\d{2}:\d{2}$/.test(text)) return value;
return isIsoLike ? `${text}Z` : value; // "YYYY-MM-DDTHH:mm:ss(.sss)" or "YYYY-MM-DD HH:mm:ss(.sss)"
const isoLike = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(text);
if (!isoLike) return value;
return `${text.replace(" ", "T")}Z`;
} }
export function formatSystemTime( export function formatSystemTime(
@ -27,4 +34,3 @@ export function formatSystemTime(
timeZone: systemTimeZone, timeZone: systemTimeZone,
}); });
} }