One elapsed-time notation: ago() reads dur's units app-wide, health runs cell widened
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useSyncExternalStore } from "react"
|
||||
|
||||
import { ago } from "@/components/Health/queries"
|
||||
import { connectionStore } from "@/lib/connectionStore"
|
||||
import { dismiss, notify } from "@/lib/notificationStore"
|
||||
import { isPortal } from "@/lib/portal"
|
||||
import { ago } from "@/lib/utils"
|
||||
|
||||
/** One card, replaced in place, rather than one per poll that confirms it. */
|
||||
const ID = "connection-offline"
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Marquee } from "@/components/Common/Marquee"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Popover, PopoverAnchor, PopoverContent } from "@/components/ui/popover"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { ago } from "@/lib/utils"
|
||||
import { displayName } from "./deriveEdges"
|
||||
import { useLiveValue } from "./liveStore"
|
||||
import { MessageSparkline } from "./MessageSparkline"
|
||||
@@ -13,11 +14,7 @@ import { ValuePreview } from "./ValuePreview"
|
||||
|
||||
export function relativeTime(ts: number | null | undefined): string {
|
||||
if (!ts) return "not seen yet"
|
||||
const seconds = Math.max(0, Math.round(Date.now() / 1000 - ts))
|
||||
if (seconds < 5) return "just now"
|
||||
if (seconds < 60) return `${seconds}s ago`
|
||||
if (seconds < 3600) return `${Math.round(seconds / 60)}m ago`
|
||||
return `${Math.round(seconds / 3600)}h ago`
|
||||
return Date.now() / 1000 - ts < 5 ? "just now" : ago(ts)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,9 +8,8 @@ import { UplotChart } from "@/components/Common/UplotChart"
|
||||
import { useEngineEventMinute } from "@/components/Flow/liveStore"
|
||||
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn, dur, si } from "@/lib/utils"
|
||||
import { ago, cn, dur, si } from "@/lib/utils"
|
||||
import {
|
||||
ago,
|
||||
auditQueryOptions,
|
||||
CARD,
|
||||
clock,
|
||||
@@ -358,7 +357,7 @@ export function HealthActivity({ range }: { range: Range }) {
|
||||
<span className="w-20 shrink-0 whitespace-nowrap text-right text-muted-foreground">
|
||||
{dur(run.duration_ms)}
|
||||
</span>
|
||||
<span className="w-16 shrink-0 text-right text-xs text-muted-foreground">
|
||||
<span className="w-24 shrink-0 whitespace-nowrap text-right text-xs text-muted-foreground">
|
||||
{ago(run.started_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,8 @@ import { type Range, RangePicker } from "@/components/Common/RangePicker"
|
||||
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
|
||||
import { runOverviewQueryOptions } from "@/components/Runs/queries"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { cn, dur, si } from "@/lib/utils"
|
||||
import { ago, cn, dur, si } from "@/lib/utils"
|
||||
import {
|
||||
ago,
|
||||
CARD,
|
||||
failuresQueryOptions,
|
||||
flowRollupsQueryOptions,
|
||||
|
||||
@@ -122,17 +122,6 @@ export const deadLetterQueryOptions = () => ({
|
||||
refetchInterval: REFRESH,
|
||||
})
|
||||
|
||||
/** How long ago something happened, in the coarsest unit that still says it. */
|
||||
export function ago(ts: string | number | null | undefined): string {
|
||||
if (!ts) return "—"
|
||||
const stamp = typeof ts === "number" ? ts * 1000 : Date.parse(ts)
|
||||
const seconds = Math.max(0, (Date.now() - stamp) / 1000)
|
||||
if (seconds < 90) return `${Math.round(seconds)}s ago`
|
||||
if (seconds < 5400) return `${Math.round(seconds / 60)}m ago`
|
||||
if (seconds < 172800) return `${Math.round(seconds / 3600)}h ago`
|
||||
return `${Math.round(seconds / 86400)}d ago`
|
||||
}
|
||||
|
||||
/**
|
||||
* The minute a timestamp belongs to, as unix seconds.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useState } from "react"
|
||||
|
||||
import type { ArtifactRow, RunNodeRow } from "@/client"
|
||||
import { ValuePreview } from "@/components/Flow/ValuePreview"
|
||||
import { ago } from "@/components/Health/queries"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import {
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { cn, dur, si } from "@/lib/utils"
|
||||
import { ago, cn, dur, si } from "@/lib/utils"
|
||||
import { MetricPicker, RunMetricChart, useMetricNames } from "./MetricChart"
|
||||
import { OpenInDashboard } from "./OpenInDashboard"
|
||||
import {
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
import { cn, dur } from "@/lib/utils"
|
||||
import { ago, cn, dur } from "@/lib/utils"
|
||||
import {
|
||||
AxisPicker,
|
||||
MetricPicker,
|
||||
@@ -425,12 +425,6 @@ function ParamValue({ value }: { value: unknown }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** How long ago a moment was, in `dur`'s units so a row's two times agree. */
|
||||
function since(ts: unknown): string {
|
||||
if (!ts) return "—"
|
||||
return `${dur(Date.now() - Date.parse(String(ts)))} ago`
|
||||
}
|
||||
|
||||
function RunsTable({
|
||||
runs,
|
||||
search,
|
||||
@@ -611,7 +605,7 @@ function RunsTable({
|
||||
`${dur(run.duration_ms)} ago`
|
||||
) : (
|
||||
<>
|
||||
{since(run.created_at)}
|
||||
{ago(String(run.created_at ?? ""))}
|
||||
<span className="px-1.5 text-border">|</span>
|
||||
<span>{run.duration_ms ? dur(run.duration_ms) : "—"}</span>
|
||||
</>
|
||||
|
||||
@@ -81,3 +81,13 @@ export function dur(ms: number, digits = 3): string {
|
||||
]
|
||||
return `${Number((rounded / factor).toPrecision(digits))} ${unit}`
|
||||
}
|
||||
|
||||
/**
|
||||
* How long ago a moment was, in `dur`'s notation: "4.52 min ago". A number is
|
||||
* unix seconds, a string an ISO timestamp.
|
||||
*/
|
||||
export function ago(ts: string | number | null | undefined): string {
|
||||
if (!ts) return "—"
|
||||
const stamp = typeof ts === "number" ? ts * 1000 : Date.parse(ts)
|
||||
return `${dur(Math.max(0, Date.now() - stamp))} ago`
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import useAuth from "@/hooks/useAuth"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { ago } from "@/lib/utils"
|
||||
import { handleError } from "@/utils"
|
||||
|
||||
export const Route = createFileRoute("/_layout/workers")({
|
||||
@@ -40,13 +41,6 @@ const emptyFlavor = { name: "", cpus: 1, gpus: 0, ram: 2048, description: "" }
|
||||
/** Fresh enough to watch a node take a machine, slow enough to be free. */
|
||||
const REFETCH_MS = 5000
|
||||
|
||||
function ago(seconds: number): string {
|
||||
const since = Date.now() / 1000 - seconds
|
||||
if (since < 60) return `${Math.max(0, Math.round(since))}s ago`
|
||||
if (since < 3600) return `${Math.round(since / 60)}m ago`
|
||||
return `${Math.round(since / 3600)}h ago`
|
||||
}
|
||||
|
||||
type Level = { total: number; free: number }
|
||||
|
||||
/** "cpu 2/18" — what is in use, out of what there is. */
|
||||
@@ -203,7 +197,7 @@ function Attached() {
|
||||
{worker.protocol}
|
||||
</span>
|
||||
<span>
|
||||
{worker.behind ? "refused" : "seen"} {ago(worker.last_seen ?? 0)}
|
||||
{worker.behind ? "refused" : "seen"} {ago(worker.last_seen)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AxiosError } from "axios"
|
||||
import type { ApiError } from "./client"
|
||||
import { ago } from "./components/Health/queries"
|
||||
import { offlineDetail } from "./lib/connectionStore"
|
||||
import { ago } from "./lib/utils"
|
||||
|
||||
function extractErrorMessage(err: ApiError): string {
|
||||
// An unreachable instance is not a rejected action: say plainly that
|
||||
|
||||
Reference in New Issue
Block a user