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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 13 additions & 17 deletions apps/local-ui/src/components/log-detail-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@maple/ui/components/u
import { ScrollArea } from "@maple/ui/components/ui/scroll-area"
import { Badge } from "@maple/ui/components/ui/badge"
import { Button } from "@maple/ui/components/ui/button"
import { KeyValue, KeyValueList } from "@maple/ui/components/ui/key-value"
import { shortId } from "@maple/ui/lib/ids"
import { SeverityBadge } from "@maple/ui/components/logs/severity-badge"
import {
CircleInfoIcon,
Expand Down Expand Up @@ -174,7 +176,7 @@ function LogHeroHeader({ log, onClose }: { log: LocalLog; onClose: () => void })
<div className={cn("shrink-0 border-b px-4 py-3", tone)}>
<div className="flex items-center gap-2">
<SeverityBadge severity={log.severityText} />
<Badge variant="outline" className="font-mono text-[10px]">
<Badge variant="outline" size="xs" mono>
<CopyableValue value={log.serviceName}>{log.serviceName}</CopyableValue>
</Badge>
<Button
Expand Down Expand Up @@ -231,13 +233,13 @@ function LogMetaStrip({
title={`View trace ${log.traceId}`}
>
<PulseIcon size={10} />
trace:{log.traceId.slice(0, 8)}
trace:{shortId(log.traceId, "trace")}
</a>
)}

{log.spanId && (
<span className="shrink-0 font-mono text-[11px] text-muted-foreground">
<CopyableValue value={log.spanId}>span:{log.spanId.slice(0, 8)}</CopyableValue>
<CopyableValue value={log.spanId}>span:{shortId(log.spanId, "span")}</CopyableValue>
</span>
)}

Expand Down Expand Up @@ -308,22 +310,16 @@ function LogTracePanel({
}) {
return (
<div className="space-y-3">
<div className="rounded-md border p-2 text-xs space-y-1">
<div className="flex justify-between gap-3">
<span className="text-muted-foreground">Trace ID</span>
<span className="truncate font-mono">
<CopyableValue value={log.traceId}>{log.traceId}</CopyableValue>
</span>
</div>
<KeyValueList className="gap-1 rounded-md border p-2">
<KeyValue label="Trace ID" mono>
<CopyableValue value={log.traceId}>{log.traceId}</CopyableValue>
</KeyValue>
{log.spanId && (
<div className="flex justify-between gap-3">
<span className="text-muted-foreground">Span ID</span>
<span className="truncate font-mono">
<CopyableValue value={log.spanId}>{log.spanId}</CopyableValue>
</span>
</div>
<KeyValue label="Span ID" mono>
<CopyableValue value={log.spanId}>{log.spanId}</CopyableValue>
</KeyValue>
)}
</div>
</KeyValueList>
<Button
variant="outline"
size="sm"
Expand Down
33 changes: 10 additions & 23 deletions apps/local-ui/src/components/service-map-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import type { ServiceOverview } from "@maple/query-engine"
import { MagnifierIcon, XmarkIcon } from "@maple/ui/components/icons"
import { Button } from "@maple/ui/components/ui/button"
import { ScrollArea } from "@maple/ui/components/ui/scroll-area"
import { formatLatency } from "@maple/ui/lib/format"
import { formatErrorRate, formatLatency } from "@maple/ui/lib/format"
import { ERROR_RATE_TEXT, errorRateLevel } from "@maple/ui/lib/error-rate"
import { formatRate, getHealthDotClass } from "@maple/ui/components/service-map/service-map-node"
import { getServiceColor } from "@maple/ui/lib/colors"
import { latencyToneClass } from "@maple/ui/lib/latency-tone"
import { cn } from "@maple/ui/lib/utils"
Expand All @@ -24,24 +26,9 @@ import type {
} from "@maple/ui/components/service-map/service-map-types"
import { hrefFor } from "../lib/router"

function formatRate(value: number): string {
if (value >= 1000) return `${(value / 1000).toFixed(1)}k`
if (value >= 1) return value.toFixed(1)
return value.toFixed(2)
}

const formatErrorPct = (rate: number) => `${(rate * 100).toFixed(1)}%`

function healthDotClass(errorRate: number): string {
if (errorRate > 0.05) return "bg-severity-error"
if (errorRate > 0.01) return "bg-severity-warn"
return "bg-severity-info"
}

function errorToneClass(errorRate: number, calm = "text-foreground"): string {
if (errorRate > 0.05) return "text-severity-error"
if (errorRate > 0.01) return "text-severity-warn"
return calm
const level = errorRateLevel(errorRate)
return level === "neutral" ? calm : ERROR_RATE_TEXT[level]
}

export function serviceHref(serviceName: string, range: string): string {
Expand Down Expand Up @@ -73,7 +60,7 @@ function PanelHeader({
className="h-[18px] w-[3px] shrink-0 rounded-sm"
style={{ backgroundColor: accentColor }}
/>
<div className={cn("size-1.5 shrink-0 rounded-full", healthDotClass(errorRate))} />
<div className={cn("size-1.5 shrink-0 rounded-full", getHealthDotClass(errorRate))} />
<div className="flex min-w-0 flex-col">
<span className="truncate text-sm font-semibold text-foreground">{title}</span>
{subtitle ? (
Expand Down Expand Up @@ -152,7 +139,7 @@ function PeerList({ title, rows }: { title: string; rows: ReadonlyArray<PeerRow>
key={row.key}
className={cn(
"flex items-center justify-between gap-2 rounded-md border px-2.5 py-2 text-xs",
row.errorRate > 0.05
errorRateLevel(row.errorRate) === "crit"
? "border-severity-error/[0.12] bg-severity-error/[0.04]"
: "border-border bg-card",
)}
Expand Down Expand Up @@ -180,7 +167,7 @@ function PeerList({ title, rows }: { title: string; rows: ReadonlyArray<PeerRow>
errorToneClass(row.errorRate, "text-severity-info"),
)}
>
{formatErrorPct(row.errorRate)}
{formatErrorRate(row.errorRate)}
</span>
</span>
</div>
Expand Down Expand Up @@ -325,7 +312,7 @@ export function ServiceMapServicePanel({
/>
<MetricTile
label="Error Rate"
value={formatErrorPct(errorRate)}
value={formatErrorRate(errorRate)}
className={errorToneClass(errorRate)}
/>
<MetricTile
Expand Down Expand Up @@ -425,7 +412,7 @@ export function ServiceMapDatabasePanel({
/>
<MetricTile
label="Error Rate"
value={formatErrorPct(errorRate)}
value={formatErrorRate(errorRate)}
className={errorToneClass(errorRate)}
/>
<MetricTile
Expand Down
84 changes: 36 additions & 48 deletions apps/local-ui/src/components/span-detail-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { HttpSpanLabel } from "@maple/ui/components/traces/http-span-label"
import { SeverityBadge } from "@maple/ui/components/logs/severity-badge"
import { Button } from "@maple/ui/components/ui/button"
import { Badge } from "@maple/ui/components/ui/badge"
import { Skeleton } from "@maple/ui/components/ui/skeleton"
import { KeyValue, KeyValueList } from "@maple/ui/components/ui/key-value"
import { Skeleton, SkeletonList } from "@maple/ui/components/ui/skeleton"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@maple/ui/components/ui/tabs"
import { ScrollArea } from "@maple/ui/components/ui/scroll-area"
import { XmarkIcon, ClockIcon, CircleInfoIcon, CodeIcon } from "@maple/ui/components/icons"
import { CopyableValue, AttributesSection, ResourceAttributesSection } from "@maple/ui/components/attributes"
import { getCacheInfo, cacheResultStyles } from "@maple/ui/lib/cache"
import { getServiceColor } from "@maple/ui/lib/colors"
import { formatDuration } from "@maple/ui/lib/format"
import { cn } from "@maple/ui/lib/utils"
import { getSpanKindLabel, getSpanStatusBadgeClass } from "@maple/ui/lib/span-kind"
import type { SpanNode } from "@maple/ui/lib/types"
import { useLocalSpanDetail } from "../hooks/use-local-span-detail"
Expand Down Expand Up @@ -68,7 +68,8 @@ export function SpanDetailPanel({ span, onClose }: SpanDetailPanelProps) {
<div className="mt-0.5 flex items-center gap-2">
<Badge
variant="outline"
className="font-mono text-[10px]"
size="xs"
mono
style={{ color: getServiceColor(span.serviceName) }}
>
<CopyableValue value={span.serviceName}>{span.serviceName}</CopyableValue>
Expand Down Expand Up @@ -100,14 +101,11 @@ export function SpanDetailPanel({ span, onClose }: SpanDetailPanelProps) {
</CopyableValue>
</span>
</div>
<Badge variant="outline" className={cn("text-[10px] font-medium", statusStyle)}>
<Badge variant="outline" size="xs" className={statusStyle}>
{span.statusCode || "Unset"}
</Badge>
{cacheInfo?.result && (
<Badge
variant="outline"
className={cn("text-[10px] font-medium", cacheResultStyles[cacheInfo.result])}
>
<Badge variant="outline" size="xs" className={cacheResultStyles[cacheInfo.result]}>
{cacheInfo.result === "hit" ? "HIT" : "MISS"}
</Badge>
)}
Expand Down Expand Up @@ -137,7 +135,7 @@ export function SpanDetailPanel({ span, onClose }: SpanDetailPanelProps) {
<TabsTrigger value="logs">
<CodeIcon size={14} /> Logs
{logCount !== null && logCount > 0 && (
<Badge variant="secondary" className="ml-1 px-1.5 py-0 text-[10px]">
<Badge variant="secondary" size="xs" className="ml-1">
{logCount >= LOG_LIMIT ? `${LOG_LIMIT}+` : logCount}
</Badge>
)}
Expand All @@ -148,44 +146,32 @@ export function SpanDetailPanel({ span, onClose }: SpanDetailPanelProps) {
<div className="space-y-3 p-3">
<div className="space-y-1">
<h4 className="text-xs font-medium text-muted-foreground">Timing</h4>
<div className="space-y-1 rounded-md border p-2 text-xs">
<div className="flex justify-between">
<span className="text-muted-foreground">Start Time</span>
<span className="font-mono" title={formatUtcTitle(span.startTime)}>
<KeyValueList className="gap-1 rounded-md border p-2">
<KeyValue label="Start Time" mono>
<span title={formatUtcTitle(span.startTime)}>
<CopyableValue value={span.startTime}>
{formatLocalDateTime(span.startTime)}
</CopyableValue>
</span>
</div>
</div>
</KeyValue>
</KeyValueList>
</div>

<div className="space-y-1">
<h4 className="text-xs font-medium text-muted-foreground">Identifiers</h4>
<div className="space-y-1 rounded-md border p-2 text-xs">
<div className="flex justify-between gap-3">
<span className="text-muted-foreground">Span ID</span>
<span className="truncate font-mono">
<CopyableValue value={span.spanId}>{span.spanId}</CopyableValue>
</span>
</div>
<div className="flex justify-between gap-3">
<span className="text-muted-foreground">Trace ID</span>
<span className="truncate font-mono">
<CopyableValue value={span.traceId}>{span.traceId}</CopyableValue>
</span>
</div>
<KeyValueList className="gap-1 rounded-md border p-2">
<KeyValue label="Span ID" mono>
<CopyableValue value={span.spanId}>{span.spanId}</CopyableValue>
</KeyValue>
<KeyValue label="Trace ID" mono>
<CopyableValue value={span.traceId}>{span.traceId}</CopyableValue>
</KeyValue>
{span.parentSpanId && (
<div className="flex justify-between gap-3">
<span className="text-muted-foreground">Parent Span ID</span>
<span className="truncate font-mono">
<CopyableValue value={span.parentSpanId}>
{span.parentSpanId}
</CopyableValue>
</span>
</div>
<KeyValue label="Parent Span ID" mono>
<CopyableValue value={span.parentSpanId}>{span.parentSpanId}</CopyableValue>
</KeyValue>
)}
</div>
</KeyValueList>
</div>

{span.isMissing ? (
Expand All @@ -195,12 +181,11 @@ export function SpanDetailPanel({ span, onClose }: SpanDetailPanelProps) {
groupByNamespace
/>
) : detail.isPending ? (
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-24 w-full" />
<Skeleton className="h-4 w-32" />
<Skeleton className="h-24 w-full" />
</div>
<SkeletonList
rows={4}
gap="2"
renderRow={(i) => <Skeleton className={i % 2 === 0 ? "h-4 w-32" : "h-24 w-full"} />}
/>
) : (
<>
{detail.isError && (
Expand Down Expand Up @@ -248,14 +233,17 @@ function SpanLogs({

if (isPending) {
return (
<div className="space-y-2 p-2">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="space-y-1">
<SkeletonList
rows={3}
gap="2"
className="p-2"
renderRow={() => (
<div className="space-y-1">
<Skeleton className="h-3 w-24" />
<Skeleton className="h-4 w-full" />
</div>
))}
</div>
)}
/>
)
}

Expand Down
16 changes: 7 additions & 9 deletions apps/local-ui/src/components/view-states.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from "@maple/ui/components/ui/empty"
import { Button } from "@maple/ui/components/ui/button"
import { Separator } from "@maple/ui/components/ui/separator"
import { Skeleton } from "@maple/ui/components/ui/skeleton"
import { SkeletonList } from "@maple/ui/components/ui/skeleton"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@maple/ui/components/ui/tabs"
import { CircleWarningIcon, ConnectionIcon } from "@maple/ui/components/icons"
import { CopyableField } from "@maple/ui/components/ui/copyable-field"
Expand Down Expand Up @@ -198,13 +198,11 @@ function DocsLink({ href, children }: { href: string; children: ReactNode }) {
*/
export function ListSkeleton({ rows = 8, variant = "table" }: { rows?: number; variant?: "table" | "card" }) {
return (
<div className="space-y-2 p-4">
{Array.from({ length: rows }).map((_, i) => (
<Skeleton
key={i}
className={variant === "card" ? "h-[68px] w-full rounded-xl" : "h-10 w-full rounded-md"}
/>
))}
</div>
<SkeletonList
rows={rows}
gap="2"
className="p-4"
rowClassName={variant === "card" ? "h-[68px] rounded-xl" : "h-10 rounded-md"}
/>
)
}
6 changes: 1 addition & 5 deletions apps/local-ui/src/views/errors-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,7 @@ function ErrorTypeCard({
{row.serviceNames.length > 0 ? (
<>
{row.serviceNames.map((serviceName) => (
<Badge
key={serviceName}
variant="outline"
className="gap-1.5 font-mono text-[10px]"
>
<Badge key={serviceName} variant="outline" size="xs" mono className="gap-1.5">
<ServiceDot serviceName={serviceName} />
{serviceName}
</Badge>
Expand Down
11 changes: 4 additions & 7 deletions apps/local-ui/src/views/metric-detail-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useMemo } from "react"
import { ArrowLeftIcon } from "@maple/ui/components/icons"
import { Badge } from "@maple/ui/components/ui/badge"
import { Button } from "@maple/ui/components/ui/button"
import { TruncatedText } from "@maple/ui/components/ui/truncated-text"
import { Spinner } from "@maple/ui/components/ui/spinner"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@maple/ui/components/ui/table"
import { MetricTypeBadge } from "@maple/ui/components/metrics/metric-type-badge"
Expand Down Expand Up @@ -50,16 +51,12 @@ export function MetricDetailView({ metricName, backLabel, onBack }: MetricDetail
<ArrowLeftIcon size={14} />
{backLabel}
</Button>
<span className="min-w-0 truncate font-mono text-xs" title={metricName}>
<TruncatedText mono className="text-xs">
{metricName}
</span>
</TruncatedText>
{entry ? <MetricTypeBadge type={entry.metricType} /> : null}
{unitLabel ? (
<Badge
variant="outline"
className="px-1 py-0 font-mono text-[10px]"
title={entry?.metricUnit}
>
<Badge variant="outline" size="xs" mono title={entry?.metricUnit}>
{unitLabel}
</Badge>
) : null}
Expand Down
5 changes: 3 additions & 2 deletions apps/local-ui/src/views/metrics-list-view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useMemo, useRef, useState } from "react"
import { useVirtualizer } from "@tanstack/react-virtual"
import { Badge } from "@maple/ui/components/ui/badge"
import { TruncatedText } from "@maple/ui/components/ui/truncated-text"
import { StatSparkline } from "@maple/ui/components/charts/sparkline/stat-sparkline"
import { METRIC_TYPE_COLORS, MetricTypeBadge } from "@maple/ui/components/metrics/metric-type-badge"
import { cn } from "@maple/ui/lib/utils"
Expand Down Expand Up @@ -230,9 +231,9 @@ function MetricPreviewCard({
className="group flex h-[124px] flex-col gap-2 rounded-md border bg-card p-3 text-left transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-2 focus-visible:outline-ring"
>
<div className="flex w-full items-start justify-between gap-2">
<span className="min-w-0 truncate font-mono text-xs font-medium" title={entry.metricName}>
<TruncatedText mono className="text-xs font-medium">
{entry.metricName}
</span>
</TruncatedText>
<MetricTypeBadge type={entry.metricType} />
</div>

Expand Down
Loading
Loading