Skip to content
Open
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
7 changes: 3 additions & 4 deletions frontend/src/components/admin/admin-org-registry-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { toast } from "@/components/ui/use-toast"
import { useAdminOrgRegistry } from "@/hooks/use-admin"
import { getApiErrorDetail } from "@/lib/errors"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

interface AdminOrgRegistryTableProps {
orgId: string
Expand Down Expand Up @@ -121,11 +121,10 @@ export function AdminOrgRegistryTable({
return <div className="text-xs text-muted-foreground">Never</div>
}
const date = new Date(lastSynced)
const ago = getRelativeTime(date)
const exactTimestamp = formatExactTimestamp(date)
return (
<div className="text-xs text-muted-foreground">
<span>{date.toLocaleDateString()}</span>
<span className="ml-1">({ago})</span>
{exactTimestamp}
</div>
)
},
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/components/admin/admin-users-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { Input } from "@/components/ui/input"
import { toast } from "@/components/ui/use-toast"
import { useAdminUsers } from "@/hooks/use-admin"
import { useAuth } from "@/hooks/use-auth"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

const createUserFormSchema = z.object({
email: z.string().email("Invalid email address"),
Expand Down Expand Up @@ -425,11 +425,10 @@ export function AdminUsersTable() {
return <div className="text-xs text-muted-foreground">-</div>
}
const date = new Date(lastLoginAt)
const ago = getRelativeTime(date)
const exactTimestamp = formatExactTimestamp(date)
return (
<div className="text-xs text-muted-foreground">
<span>{date.toLocaleDateString()}</span>
<span className="ml-1">({ago})</span>
{exactTimestamp}
</div>
)
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/admin/org-registry-versions-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
useAdminOrgRegistry,
useAdminOrgRepositoryVersions,
} from "@/hooks/use-admin"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

interface OrgRegistryVersionsPanelProps {
orgId: string
Expand Down Expand Up @@ -123,7 +123,7 @@ export function OrgRegistryVersionsPanel({
</TableCell>
<TableCell>
<span className="text-xs text-muted-foreground">
{getRelativeTime(new Date(version.created_at))}
{formatExactTimestamp(new Date(version.created_at))}
</span>
</TableCell>
<TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { toast } from "@/components/ui/use-toast"
import { useAdminRegistryStatus, useAdminRegistrySync } from "@/hooks/use-admin"
import { getApiErrorDetail } from "@/lib/errors"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

export function PlatformRegistryReposTable() {
const [syncingId, setSyncingId] = useState<string | null>(null)
Expand Down Expand Up @@ -139,11 +139,10 @@ export function PlatformRegistryReposTable() {
return <div className="text-xs text-muted-foreground">Never</div>
}
const date = new Date(lastSynced)
const ago = getRelativeTime(date)
const exactTimestamp = formatExactTimestamp(date)
return (
<div className="text-xs text-muted-foreground">
<span>{date.toLocaleDateString()}</span>
<span className="ml-1">({ago})</span>
{exactTimestamp}
</div>
)
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/admin/platform-registry-status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { AdminRegistryGetRegistryStatusResponse } from "@/client"
import { Badge } from "@/components/ui/badge"
import { Item } from "@/components/ui/item"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

type PlatformRegistryStatusProps = {
status?: AdminRegistryGetRegistryStatusResponse
Expand Down Expand Up @@ -33,7 +33,7 @@ export function PlatformRegistryStatus({
<div className="text-sm text-muted-foreground">Last sync</div>
<div className="text-lg">
{status?.last_sync_at
? getRelativeTime(new Date(status.last_sync_at))
? formatExactTimestamp(new Date(status.last_sync_at))
: "Never"}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
useAdminRegistrySync,
useAdminRegistryVersions,
} from "@/hooks/use-admin"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

type RegistryVersionRead =
tracecat__admin__registry__schemas__RegistryVersionRead
Expand Down Expand Up @@ -298,7 +298,7 @@ export function PlatformRegistryVersionsTable() {
const date = new Date(createdAt)
return (
<div className="text-xs text-muted-foreground">
{getRelativeTime(date)}
{formatExactTimestamp(date)}
</div>
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
useCompareAgentPresetVersions,
useRestoreAgentPresetVersion,
} from "@/hooks/use-agent-presets"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

type VersionsPanelView = "history" | "compare"

Expand Down Expand Up @@ -256,7 +256,7 @@ function VersionsHistoryView({
) : null}
</div>
<div className="mt-1 text-xs text-muted-foreground">
{getRelativeTime(new Date(version.created_at))}
{formatExactTimestamp(new Date(version.created_at))}
</div>
</div>
<div className="flex items-center gap-2">
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/builder/panel/workflow-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import {
isRequestValidationErrorArray,
type TracecatApiError,
} from "@/lib/errors"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"
import { useWorkflow } from "@/providers/workflow"

const createWorkflowUpdateFormSchema = (workspaceId: string) =>
Expand Down Expand Up @@ -378,7 +378,7 @@ function WorkflowVersionsHistory({
{isLatest ? <Badge variant="outline">Latest</Badge> : null}
</div>
<div className="mt-1 text-xs text-muted-foreground">
{getRelativeTime(new Date(definition.created_at))}
{formatExactTimestamp(new Date(definition.created_at))}
</div>
</div>
{!isCurrent ? (
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/organization/org-members-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"
import {
useOrgMembers,
useRbacRoles,
Expand Down Expand Up @@ -355,11 +355,10 @@ export function OrgMembersTable() {
return <div className="text-xs">-</div>
}
const date = new Date(lastLoginAt)
const ago = getRelativeTime(date)
const exactTimestamp = formatExactTimestamp(date)
return (
<div className="space-x-2 text-xs">
<span>{date.toLocaleString()}</span>
<span className="text-muted-foreground">({ago})</span>
<div className="text-xs text-muted-foreground">
{exactTimestamp}
</div>
)
},
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/organization/org-sessions-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"
import { useSessions } from "@/lib/hooks"

export function OrgSessionsTable() {
Expand Down Expand Up @@ -91,11 +91,10 @@ export function OrgSessionsTable() {
const createdAt =
row.getValue<SessionRead["created_at"]>("created_at")
const date = new Date(createdAt)
const ago = getRelativeTime(date)
const exactTimestamp = formatExactTimestamp(date)
return (
<div className="space-x-2 text-xs">
<span>{date.toLocaleString()}</span>
<span className="text-muted-foreground">({ago})</span>
<div className="text-xs text-muted-foreground">
{exactTimestamp}
</div>
)
},
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/components/organization/workflow-pull-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
useRepositoryCommits,
useWorkflowSync,
} from "@/hooks/use-workspace-sync"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

interface WorkflowPullDialogProps {
open: boolean
Expand Down Expand Up @@ -181,7 +181,7 @@ export function WorkflowPullDialog({
if (!selectedCommit) return null

const commitDate = new Date(selectedCommit.date)
const relativeTime = getRelativeTime(commitDate)
const exactTimestamp = formatExactTimestamp(commitDate)
const isHead = commits[0]?.sha === effectiveCommitSha

return (
Expand Down Expand Up @@ -213,11 +213,7 @@ export function WorkflowPullDialog({
by {selectedCommit.author} (
{selectedCommit.author_email})
</span>
<div className="flex items-center space-x-2">
<span>{commitDate.toLocaleDateString()}</span>
<span>•</span>
<span>{relativeTime}</span>
</div>
<div>{exactTimestamp}</div>
</div>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/registry/commit-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Skeleton } from "@/components/ui/skeleton"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"
import { cn } from "@/lib/utils"

interface CommitSelectorProps {
Expand Down Expand Up @@ -105,7 +105,7 @@ export function CommitSelector({
const isSelected = commit.sha === effectiveCurrentSha
const isHead = index === 0
const commitDate = new Date(commit.date)
const relativeTime = getRelativeTime(commitDate)
const exactTimestamp = formatExactTimestamp(commitDate)

return (
<DropdownMenuItem
Expand Down Expand Up @@ -142,7 +142,7 @@ export function CommitSelector({
)}
</div>
<span className="text-xs text-muted-foreground">
{relativeTime}
{exactTimestamp}
</span>
</div>
{commit.tags && commit.tags.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from "@/components/ui/dialog"
import { Skeleton } from "@/components/ui/skeleton"
import { toast } from "@/components/ui/use-toast"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"
import { useRegistryRepositories, useRepositoryCommits } from "@/lib/hooks"

interface CommitSelectorDialogProps {
Expand Down Expand Up @@ -148,7 +148,7 @@ export function CommitSelectorDialog({
if (!selectedCommit) return null

const commitDate = new Date(selectedCommit.date)
const relativeTime = getRelativeTime(commitDate)
const exactTimestamp = formatExactTimestamp(commitDate)
const isHead = commits[0]?.sha === effectiveCommitSha

return (
Expand Down Expand Up @@ -182,11 +182,7 @@ export function CommitSelectorDialog({
by {selectedCommit.author} (
{selectedCommit.author_email})
</span>
<div className="flex items-center space-x-2">
<span>{commitDate.toLocaleDateString()}</span>
<span>•</span>
<span>{relativeTime}</span>
</div>
<div>{exactTimestamp}</div>
</div>
<div className="pt-1">
<span className="text-xs text-muted-foreground font-mono">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip"
import { toast } from "@/components/ui/use-toast"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"

/** Shorten a version string if it looks like a full commit SHA. */
function shortVersion(version: string): string {
Expand Down Expand Up @@ -383,7 +383,7 @@ function VersionsView({
)}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{getRelativeTime(new Date(version.created_at))}
{formatExactTimestamp(new Date(version.created_at))}
</TableCell>
<TableCell>
{isCurrent && (
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/watchtower/watchtower-monitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import {
useWatchtowerAgents,
useWatchtowerSessionToolCalls,
} from "@/hooks/use-watchtower"
import { getRelativeTime } from "@/lib/event-history"
import { formatExactTimestamp } from "@/lib/event-history"
import { useWorkspaceManager } from "@/lib/hooks"
import { cn } from "@/lib/utils"

Expand Down Expand Up @@ -1271,7 +1271,7 @@ function shortId(value: string) {
}

function formatRelative(value: string) {
return getRelativeTime(new Date(value))
return formatExactTimestamp(new Date(value))
}

function normalizeReason(value: string | undefined): string | undefined {
Expand Down
28 changes: 9 additions & 19 deletions frontend/src/lib/event-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,15 @@ export function parseEventType(eventType: WorkflowEventType) {
.join(" ")
}

export function getRelativeTime(date: Date) {
const now = new Date().getTime()
const timestamp = date.getTime()
const difference = now - timestamp

const seconds = Math.floor(difference / 1000)
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
const months = Math.floor(days / 30)
const years = Math.floor(months / 12)

if (years > 0) return `about ${years} year${years > 1 ? "s" : ""} ago`
if (months > 0) return `about ${months} month${months > 1 ? "s" : ""} ago`
if (days > 0) return `about ${days} day${days > 1 ? "s" : ""} ago`
if (hours > 0) return `about ${hours} hour${hours > 1 ? "s" : ""} ago`
if (minutes > 0) return `about ${minutes} minute${minutes > 1 ? "s" : ""} ago`
if (seconds > 0) return `${seconds} second${seconds > 1 ? "s" : ""} ago`
return "just now"
/**
* Format a timestamp exactly for debugging.
*
* Keep this in UTC so the rendered value matches API payloads and logs without
* requiring the viewer to infer their local timezone.
*/
export function formatExactTimestamp(date: Date) {
if (Number.isNaN(date.getTime())) return "Invalid date"
return date.toISOString().replace("T", " ").replace("Z", " UTC")
}

/**
Expand Down
2 changes: 1 addition & 1 deletion frontend/tests/agent-preset-versions-panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jest.mock("@/hooks/use-agent-presets", () => ({
}))

jest.mock("@/lib/event-history", () => ({
getRelativeTime: () => "just now",
formatExactTimestamp: () => "2026-01-01 00:00:00.000 UTC",
}))

jest.mock("react-diff-viewer-continued", () => {
Expand Down
Loading
Loading