import { useCallback, useState } from "react" import * as Tabs from "@radix-ui/react-tabs" import { Activity, AlertTriangle, Info, CheckCircle2, ChevronDown, ChevronUp, } from "lucide-react" import type { LucideIcon } from "lucide-react" import { MetricsVisualizer } from "./MetricsVisualizer" import TraceVisualization from "./TraceVisualization" import { IssuesVisualizer } from "./IssuesVisualizer" import { SetupPanel } from "./SetupPanel" import { SearchAndWatcher } from "./SearchAndWatcher" import { RumSessionsVisualizer } from "./RumSessionsVisualizer" import { ViewErrorBoundary } from "./ViewErrorBoundary" import { useObservabilityData } from "../hooks/useObservabilityData" import { useIssues } from "../hooks/useIssues" import { useStatsAvailability } from "../hooks/useStats" import { TrendsVisualizer } from "./TrendsVisualizer" import { authFetch } from "../lib/auth" import type { Issue, LogEntry, OtelAttribute } from "../lib/types" import { formatAttributeValue, getServicePalette, getServiceName } from "../lib/otel-utils" import { TelemetryFilters } from "./TelemetryFilters" import { PatternsVisualizer } from "./PatternsVisualizer" import { RolloutsVisualizer } from "./RolloutsVisualizer" type SeverityMeta = { baseLabel: string bar: string badge: string ring: string icon: LucideIcon } const severityStyles: Record = { ERROR: { baseLabel: "Error", bar: "bg-ds-critical", badge: "bg-ds-critical-bg text-ds-critical", ring: "ring-ds-critical/40", icon: AlertTriangle, }, WARN: { baseLabel: "Warning", bar: "bg-ds-warn", badge: "bg-ds-warn-bg text-ds-warn", ring: "ring-ds-warn/40", icon: AlertTriangle, }, INFO: { baseLabel: "Info", bar: "bg-ds-accent", badge: "bg-ds-accent-bg text-ds-accent", ring: "ring-ds-accent/40", icon: Info, }, DEBUG: { baseLabel: "Debug", bar: "bg-ds-ok", badge: "bg-ds-ok/15 text-ds-ok", ring: "ring-ds-ok/40", icon: CheckCircle2, }, } const fallbackSeverity: SeverityMeta = { baseLabel: "Log", bar: "bg-ds-line-strong", badge: "bg-ds-surface-2 text-ds-text-2", ring: "ring-ds-line-strong", icon: Info, } const getSeverityPresentation = ( severityText?: string, severityNumber?: number, ) => { if (severityText) { const normalized = severityText.toUpperCase() if (severityStyles[normalized]) { const meta = severityStyles[normalized] return { ...meta, display: meta.baseLabel } } return { ...fallbackSeverity, display: severityText } } if (severityNumber !== undefined) { if (severityNumber >= 17) { const meta = severityStyles.ERROR return { ...meta, display: meta.baseLabel } } if (severityNumber >= 13) { const meta = severityStyles.WARN return { ...meta, display: meta.baseLabel } } if (severityNumber >= 9) { const meta = severityStyles.INFO return { ...meta, display: meta.baseLabel } } const meta = severityStyles.DEBUG return { ...meta, display: meta.baseLabel } } return { ...fallbackSeverity, display: fallbackSeverity.baseLabel } } // tabItems moved inside component to access live data export const LogEntryView = ({ log }: { log: LogEntry }) => { const { setActiveTraceId, setActiveSpanId, setActiveTab } = useObservabilityData() const [expandedId, setExpandedId] = useState(null) const logRecords = (log.scopeLogs ?? []).flatMap((scopeLog) => (scopeLog.logRecords ?? []).map((record) => ({ ...record, scope: scopeLog.scope?.name, resourceAttributes: log.resource?.attributes ?? [], })), ) const getRecordServiceName = (recordAttributes: OtelAttribute[]) => { return getServiceName(log.resource?.attributes ?? [], recordAttributes) } const navigateToSpan = (traceId: string, spanId?: string) => { setActiveTraceId(traceId) if (spanId) setActiveSpanId(spanId) setActiveTab("tab1") } const formatTimestamp = (timeUnixNano: string) => { const timestamp = (parseInt(timeUnixNano ?? "0") || 0) / 1e9 const date = new Date(timestamp * 1000) return date.toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) } if (logRecords.length === 0) { return null } return (
{logRecords.map((record) => { const severity = getSeverityPresentation( record.severityText, record.severityNumber, ) const bodyMessage = record.body?.stringValue ?? "(no body)" const recordKey = record.uid const isExpanded = expandedId === recordKey return (
{/* The Main Log Row */}
setExpandedId(isExpanded ? null : recordKey)} className="flex cursor-pointer flex-wrap items-center gap-x-4 gap-y-2 p-2.5 font-mono text-[12px]" >
{/* Timestamp */} {formatTimestamp(record.timeUnixNano)} {/* Service Badge */} {(() => { const sName = getRecordServiceName(record.attributes ?? []) const palette = getServicePalette(sName) return ( {sName} ) })()} {/* Severity Badge */} {severity.display} {/* Log Message */} {bodyMessage} {/* Action Icons */}
{record.traceId && ( )} {isExpanded ? : }
{/* Expansion Content */} {isExpanded && (
{(record.attributes ?? []).length > 0 && (

Attributes

{(record.attributes ?? []).map(attr => (
{attr.key}: {formatAttributeValue(attr.value)}
))}
)} {(record.resourceAttributes ?? []).length > 0 && (

Resource

{(record.resourceAttributes ?? []).map(attr => (
{attr.key}: {formatAttributeValue(attr.value)}
))}
)}
Scope: {record.scope || "unknown"} {record.spanId && Span ID: {record.spanId}} {record.traceId && Trace ID: {record.traceId}}
{record.traceId && ( )}
)}
) })}
) } import { LogsVisualizer } from "./LogsVisualizer" export const TabsComponent = () => { const { traces, logs, activeTab, setActiveTab } = useObservabilityData() const issuesState = useIssues() const [statusBusy, setStatusBusy] = useState(false) // Trends tab only exists when the backend has ClickHouse-backed stats; // null while the capability check is in flight. const statsAvailable = useStatsAvailability() // Triage action: PATCH the issue status, then refresh so counts/badges // reflect the server state. Failures surface via the issues error banner. const handleIssueStatusChange = useCallback( async (issue: Issue, status: string) => { setStatusBusy(true) try { const res = await authFetch(`/api/issues/${issue.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status }), }) if (!res.ok) throw new Error(`HTTP ${res.status}`) } catch (err) { console.error("Failed to update issue status:", err) } finally { await issuesState.refresh() setStatusBusy(false) } }, [issuesState], ) return (
{activeTab === "tab0" && ( )} {activeTab === "tab1" && } {activeTab === "tab8" && } {activeTab === "tab9" && } {activeTab === "tab2" && } {activeTab === "tab3" && } {activeTab === "tab7" && } {activeTab === "tab4" && } {statsAvailable && ( {activeTab === "tab5" && } )} {activeTab === "tab6" && }
) }