web: shared timestamp formatters + PageState adoption; Agents test gets ConfirmProvider
formatUnixTime/formatTimeOnly into utils (ProcessDetailModal's formatTime was never a duration). RateLimiting loading state onto PageState; six candidate sites correctly left alone as section/inline loaders. Agents.test.tsx wraps in ConfirmProvider — the 31c4ae74 conversion broke it and nobody ran the full suite.
This commit is contained in:
parent
8c396c1f7a
commit
6fb7e7c81f
6 changed files with 32 additions and 31 deletions
|
|
@ -20,7 +20,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { logApi } from '@/lib/api';
|
||||
import { useRetryCommand } from '@/hooks/useCommands';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, formatTimeOnly } from '@/lib/utils';
|
||||
import toast from 'react-hot-toast';
|
||||
import { Highlight, themes } from 'prism-react-renderer';
|
||||
import { getCommandDisplay } from '@/lib/command-naming';
|
||||
|
|
@ -280,16 +280,6 @@ const ChatTimeline: React.FC<ChatTimelineProps> = ({ agentId, className, isScope
|
|||
return timeline;
|
||||
};
|
||||
|
||||
// Format timestamp
|
||||
const formatTimestamp = (timestamp: string) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
// Interface for narrative event summary
|
||||
interface NarrativeSummary {
|
||||
sentence: string;
|
||||
|
|
@ -622,7 +612,7 @@ const ChatTimeline: React.FC<ChatTimelineProps> = ({ agentId, className, isScope
|
|||
}
|
||||
|
||||
// Add inline timestamp and duration
|
||||
const timeStr = formatTimestamp(entry.created_at);
|
||||
const timeStr = formatTimeOnly(entry.created_at);
|
||||
const duration = entry.duration_seconds || 0;
|
||||
let durationStr = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Activity, Network, FileText, Key, Layers, Box } from 'lucide-react';
|
||||
import { useProcessDetail } from '@/hooks/useProcesses';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, formatUnixTime } from '@/lib/utils';
|
||||
import Modal from '@/components/primitives/Modal';
|
||||
|
||||
// Safe JSON parse — returns null on malformed data instead of crashing.
|
||||
|
|
@ -38,12 +38,6 @@ export const ProcessDetailModal: React.FC<ProcessDetailModalProps> = ({
|
|||
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
if (!seconds) return '—';
|
||||
const d = new Date(seconds * 1000);
|
||||
return d.toLocaleString();
|
||||
};
|
||||
|
||||
const tabs: { key: DetailTab; label: string; icon: React.ReactNode; count?: number }[] = data ? [
|
||||
{ key: 'overview', label: 'Overview', icon: <Activity className="h-4 w-4" /> },
|
||||
{ key: 'network', label: 'Network', icon: <Network className="h-4 w-4" />, count: (data.open_sockets?.length ?? 0) + (data.listening_ports?.length ?? 0) },
|
||||
|
|
@ -121,7 +115,7 @@ export const ProcessDetailModal: React.FC<ProcessDetailModalProps> = ({
|
|||
<Field label="Parent PID" value={String(proc.parent_pid)} />
|
||||
<Field label="Process Group" value={String(proc.process_group_id)} />
|
||||
<Field label="TTY" value={proc.tty_name || String(proc.tty)} />
|
||||
<Field label="Started" value={formatTime(proc.start_time_seconds)} />
|
||||
<Field label="Started" value={formatUnixTime(proc.start_time_seconds)} />
|
||||
<Field label="Disk Read" value={formatBytes(proc.disk_bytes_read)} />
|
||||
<Field label="Disk Written" value={formatBytes(proc.disk_bytes_written)} />
|
||||
<Field label="On Disk" value={proc.on_disk === 1 ? 'yes' : proc.on_disk === 0 ? 'no (deleted)' : 'unknown'} />
|
||||
|
|
|
|||
|
|
@ -69,11 +69,10 @@ const SecurityEvents: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Format timestamp
|
||||
const formatTimestamp = (timestamp: string) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
// toLocaleString() (no options) — produces date+time with seconds in the
|
||||
// browser's default locale. formatDateTime in utils pins en-US and drops
|
||||
// seconds, so they diverge. Keep local until the display format is aligned.
|
||||
const formatTimestamp = (timestamp: string) => new Date(timestamp).toLocaleString();
|
||||
|
||||
// Copy event details to clipboard
|
||||
const copyEventDetails = (event: SecurityEvent) => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,24 @@ export function cn(...inputs: ClassValue[]) {
|
|||
}
|
||||
|
||||
// Date formatting utilities
|
||||
|
||||
// formatTimeOnly — extracts the time portion of an ISO/DB timestamp as HH:MM:SS AM/PM.
|
||||
// Used where only the time-of-day is shown (e.g. history timeline rows).
|
||||
export const formatTimeOnly = (timestamp: string): string => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
// formatUnixTime — converts a Unix epoch in seconds to a locale date+time string.
|
||||
export const formatUnixTime = (seconds: number): string => {
|
||||
if (!seconds) return '—';
|
||||
return new Date(seconds * 1000).toLocaleString();
|
||||
};
|
||||
|
||||
export const formatDate = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ vi.mock('../hooks/useDebounce', () => ({
|
|||
}));
|
||||
|
||||
import Agents from './Agents';
|
||||
import { ConfirmProvider } from '@/components/primitives';
|
||||
|
||||
function renderPage(initialEntries = ['/agents']) {
|
||||
const queryClient = new QueryClient({
|
||||
|
|
@ -43,7 +44,9 @@ function renderPage(initialEntries = ['/agents']) {
|
|||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<Agents />
|
||||
<ConfirmProvider>
|
||||
<Agents />
|
||||
</ConfirmProvider>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useConfirm } from '@/components/primitives';
|
||||
import { useConfirm, PageState } from '@/components/primitives';
|
||||
import {
|
||||
Shield,
|
||||
RefreshCw,
|
||||
|
|
@ -129,10 +129,7 @@ const RateLimiting: React.FC = () => {
|
|||
if (isLoading || !editing) {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-6 py-8">
|
||||
<div className="text-center py-12">
|
||||
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<p className="mt-2 text-gray-600">Loading rate limit settings...</p>
|
||||
</div>
|
||||
<PageState loading={true} empty={false} loadingTitle="Loading rate limit settings..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue