Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion private-web/src/components/StatusSnapshot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PreviewIcon from "@mui/icons-material/Preview";
import { Fragment } from "react";
import { useApi, type ApiState } from "../api";
import TimeAgo from "./TimeAgo";
import TimezoneTooltip from "./TimezoneTooltip";
import VersionIndicator from "./VersionIndicator";
import type { StatusSnapshotData } from "../types";

Expand Down Expand Up @@ -123,7 +124,13 @@ function CuratedFields({ snap }: { snap: StatusSnapshotData }) {
/>
</Field>
{snap.platform && <Field label="Platform" value={snap.platform} />}
{snap.timezone && <Field label="Timezone" value={snap.timezone} />}
{snap.timezone && (
<Field label="Timezone">
<Typography variant="body2">
<TimezoneTooltip tz={snap.timezone} />
</Typography>
</Field>
)}
{snap.postgres && <Field label="PostgreSQL" value={snap.postgres} mono />}
{snap.nodejs && <Field label="Node.js" value={snap.nodejs} mono />}
{snap.min_chrome_version != null && (
Expand Down
44 changes: 44 additions & 0 deletions private-web/src/components/TimezoneTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Tooltip } from "@mui/material";
import { useEffect, useMemo, useState } from "react";

function formatLocalTime(tz: string, now: Date): string | null {
try {
return new Intl.DateTimeFormat(undefined, {
timeZone: tz,
weekday: "short",
day: "numeric",
month: "short",
hour: "2-digit",
minute: "2-digit",
}).format(now);
} catch {
return null;
}
}

/** Renders an IANA timezone name with the current local time at that
* timezone in a tooltip. Falls back to plain text if the timezone is
* unrecognised by the browser. Refreshes every minute while visible. */
export default function TimezoneTooltip({ tz }: { tz: string }) {
const valid = useMemo(
() => formatLocalTime(tz, new Date()) !== null,
[tz],
);
if (!valid) return <>{tz}</>;
return (
<Tooltip title={<LocalTime tz={tz} />}>
<span style={{ cursor: "help" }}>{tz}</span>
</Tooltip>
);
}

function LocalTime({ tz }: { tz: string }) {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const id = window.setInterval(() => {
if (!document.hidden) setNow(new Date());
}, 60_000);
return () => window.clearInterval(id);
}, []);
return <>Local time: {formatLocalTime(tz, now) ?? "—"}</>;
}
25 changes: 17 additions & 8 deletions private-web/src/routes/ServerDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import ManualEventButton from "../components/ManualEventButton";
import StatusDot from "../components/StatusDot";
import TailnetIdentitySection from "../components/TailnetIdentitySection";
import TimeAgo from "../components/TimeAgo";
import TimezoneTooltip from "../components/TimezoneTooltip";
import VersionIndicator from "../components/VersionIndicator";
import { HealthLegend, StatusLegend, VersionLegend } from "../components/Legends";
import ServerKindChip from "../components/ServerKindChip";
Expand Down Expand Up @@ -683,7 +684,11 @@ function StatusInfoFields({ status }: { status: ServerLastStatusData }) {
<InfoItem label="Platform" value={status.platform} />
)}
{status.timezone && (
<InfoItem label="Timezone" value={status.timezone} />
<InfoItem label="Timezone">
<Typography variant="body2">
<TimezoneTooltip tz={status.timezone} />
</Typography>
</InfoItem>
)}
<Stack spacing={0.25}>
<Typography variant="caption" color="text.secondary">
Expand Down Expand Up @@ -719,22 +724,26 @@ function InfoItem({
label,
value,
mono = false,
children,
}: {
label: string;
value: string;
value?: string;
mono?: boolean;
children?: React.ReactNode;
}) {
return (
<Stack spacing={0.25}>
<Typography variant="caption" color="text.secondary">
{label}
</Typography>
<Typography
variant="body2"
sx={mono ? { fontFamily: "monospace" } : undefined}
>
{value}
</Typography>
{children ?? (
<Typography
variant="body2"
sx={mono ? { fontFamily: "monospace" } : undefined}
>
{value}
</Typography>
)}
</Stack>
);
}
Expand Down