feat(07.1-05): user-tz on admin operational pages

- zabbix-wan, rmm-overshell, itglue-writes, ticket-digest,
  device-link-conflicts, workflow/history, workflow/pipelines/[id]:
  each gets useUserTimezone() at the component entry; threads tz into
  every inline toLocaleString call.

Migrates 11 of 81 audit leak callsites.
This commit is contained in:
lorentz 2026-05-07 08:31:22 -04:00
parent 8c56cafe0b
commit 23b179f2a7
7 changed files with 24 additions and 10 deletions

View file

@ -16,6 +16,7 @@ import {
} from '@/components/ui/select';
import { CheckCircle2, AlertTriangle, Loader2 } from 'lucide-react';
import { PageHeader } from '@/components/navigation/page-header';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
interface Candidate {
ciId: string;
@ -77,6 +78,7 @@ function dedupeCandidates(candidates: Candidate[]): Candidate[] {
}
export default function DeviceLinkConflictsPage() {
const tz = useUserTimezone();
const [items, setItems] = useState<Review[] | null>(null);
const [total, setTotal] = useState(0);
const [error, setError] = useState<string | null>(null);
@ -220,7 +222,7 @@ export default function DeviceLinkConflictsPage() {
{r.xref.serial && <span>serial: {r.xref.serial}</span>}
{r.xref.mac && <span>mac: {r.xref.mac}</span>}
{r.xref.lastSeenAt && (
<span>last seen: {new Date(r.xref.lastSeenAt).toLocaleString()}</span>
<span>last seen: {new Date(r.xref.lastSeenAt).toLocaleString(undefined, { timeZone: tz })}</span>
)}
</div>
</CardHeader>

View file

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { PageHeader } from '@/components/navigation/page-header';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
interface WriteRow {
id: string;
@ -47,6 +48,7 @@ function statusVariant(
}
export default function ItglueWritesPage() {
const tz = useUserTimezone();
const [rows, setRows] = useState<WriteRow[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [statusFilter, setStatusFilter] =
@ -141,7 +143,7 @@ export default function ItglueWritesPage() {
<span className="font-medium">{w.field_name}</span>
</p>
<p className="text-xs text-muted-foreground mt-1">
{new Date(w.performed_at).toLocaleString()}
{new Date(w.performed_at).toLocaleString(undefined, { timeZone: tz })}
</p>
<p className="text-xs mt-1 break-words">
<span className="text-muted-foreground">Before: </span>

View file

@ -9,6 +9,7 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Loader2, RefreshCw, Terminal } from 'lucide-react';
import { toast } from 'sonner';
import { PageHeader } from '@/components/navigation/page-header';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
interface Settings {
overshellComponentUid: string | null;
@ -35,6 +36,7 @@ interface ExecRow {
}
export default function RmmOvershellAdminPage() {
const tz = useUserTimezone();
const [settings, setSettings] = useState<Settings | null>(null);
const [counts, setCounts] = useState<{ total: string; running: string; failed_24h: string } | null>(null);
const [executions, setExecutions] = useState<ExecRow[] | null>(null);
@ -139,7 +141,7 @@ export default function RmmOvershellAdminPage() {
</p>
{settings.discoveredAt && (
<p className="text-[10px] text-muted-foreground mt-1">
discovered {new Date(settings.discoveredAt).toLocaleString()}
discovered {new Date(settings.discoveredAt).toLocaleString(undefined, { timeZone: tz })}
</p>
)}
</>
@ -196,7 +198,7 @@ export default function RmmOvershellAdminPage() {
{settings.logliftDiscoveredAt && (
<p className="text-[10px] text-muted-foreground mt-1">
discovered{' '}
{new Date(settings.logliftDiscoveredAt).toLocaleString()}
{new Date(settings.logliftDiscoveredAt).toLocaleString(undefined, { timeZone: tz })}
</p>
)}
</>
@ -262,7 +264,7 @@ export default function RmmOvershellAdminPage() {
</Badge>
</span>
<span className="col-span-3 text-xs text-muted-foreground truncate">
{new Date(e.queuedAt).toLocaleString()}
{new Date(e.queuedAt).toLocaleString(undefined, { timeZone: tz })}
</span>
<span className="col-span-1 text-right">
{e.errorMessage && (

View file

@ -8,6 +8,7 @@ import {
Calendar, CalendarDays, CalendarRange, MessageSquare, Bell, Globe, ExternalLink,
} from 'lucide-react';
import { PageHeader } from '@/components/navigation/page-header';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
interface DigestConfig {
daily_enabled: boolean;
@ -73,6 +74,7 @@ function PeriodIcon({ period }: { period: string }) {
}
export default function TicketDigestPage() {
const tz = useUserTimezone();
const [config, setConfig] = useState<DigestConfig | null>(null);
const [channels, setChannels] = useState<NotificationChannel[]>([]);
const [history, setHistory] = useState<DigestReport[]>([]);
@ -407,7 +409,7 @@ export default function TicketDigestPage() {
<div>
<span className="font-medium text-sm capitalize">{report.period_type}</span>
<span className="text-xs text-muted-foreground ml-2">
{new Date(report.generated_at).toLocaleString()}
{new Date(report.generated_at).toLocaleString(undefined, { timeZone: tz })}
</span>
</div>
</div>

View file

@ -32,6 +32,7 @@ import {
ChevronRight,
} from 'lucide-react';
import { WorkflowExecutionWithSteps } from '@/lib/types/workflow';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
export default function ExecutionHistoryPage() {
return (
@ -42,6 +43,7 @@ export default function ExecutionHistoryPage() {
}
function ExecutionHistoryContent() {
const tz = useUserTimezone();
const searchParams = useSearchParams();
const highlightId = searchParams.get('id');
@ -189,7 +191,7 @@ function ExecutionHistoryContent() {
{exec.duration_ms ? `${exec.duration_ms}ms` : '-'}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{new Date(exec.created_at).toLocaleString()}
{new Date(exec.created_at).toLocaleString(undefined, { timeZone: tz })}
</TableCell>
</TableRow>
))}

View file

@ -26,6 +26,7 @@ import {
Eye,
} from 'lucide-react';
import StepConfigEditor from '@/components/admin/pipeline/StepConfigEditor';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
interface Pipeline {
id: number;
@ -91,6 +92,7 @@ const STATUS_ICONS: Record<string, any> = {
};
export default function PipelineEditorPage() {
const tz = useUserTimezone();
const params = useParams();
const router = useRouter();
const pipelineId = params.id as string;
@ -579,7 +581,7 @@ export default function PipelineEditorPage() {
<span className="text-sm text-muted-foreground">{exec.trigger_source}</span>
{exec.duration_ms && <span className="text-sm text-muted-foreground">{exec.duration_ms}ms</span>}
<span className="text-xs text-muted-foreground ml-auto">
{new Date(exec.started_at).toLocaleString()}
{new Date(exec.started_at).toLocaleString(undefined, { timeZone: tz })}
</span>
</div>
{exec.error_message && (

View file

@ -51,6 +51,7 @@ import {
import { toast } from 'sonner';
import { HostManager } from '@/components/zabbix/host-manager';
import { PageHeader } from '@/components/navigation/page-header';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
type SyncMode = 'all' | 'client' | 'site';
@ -139,6 +140,7 @@ interface RmmOnlyRow { alert_uid: string; site_name: string; alert_class: string
type PageTab = 'sync' | 'gaps' | 'correlation';
export default function ZabbixWanPage() {
const tz = useUserTimezone();
const [activeTab, setActiveTab] = useState<PageTab>('sync');
const [mode, setMode] = useState<SyncMode>('all');
const [companyId, setCompanyId] = useState<string>('');
@ -209,8 +211,8 @@ export default function ZabbixWanPage() {
finally { setSyncing(null); }
};
const fmtSynced = (ts: string | null) => ts ? new Date(ts).toLocaleString() : 'Never';
const fmtTs = (ts: string | null) => ts ? new Date(ts).toLocaleString() : '—';
const fmtSynced = (ts: string | null) => ts ? new Date(ts).toLocaleString(undefined, { timeZone: tz }) : 'Never';
const fmtTs = (ts: string | null) => ts ? new Date(ts).toLocaleString(undefined, { timeZone: tz }) : '—';
const fmtDuration = (s: number | null, hasRecovery?: boolean) => {
if (s === null || s === undefined) return hasRecovery ? '< 1m' : 'Open';
if (s === 0) return hasRecovery ? '< 1m' : 'Open';