-
- {edges.map(edge => {
- const from = nodes.find(n => n.id === edge.from)
- const to = nodes.find(n => n.id === edge.to)
- if (!from || !to) return null
- return (
-
-
-
- {edge.latencyMs.toFixed(0)}ms
-
-
- )
- })}
-
-
-
-
-
- {nodes.map(node => (
- handleNodeClick(node)}
- onContextMenu={event => handleContextMenu(event, node)}
- >
-
-
-
- {node.label}
-
-
- {node.type}
-
-
- ))}
-
- {contextMenu.visible && contextMenu.node && (
-
-
Inspect {contextMenu.node.label}
-
- handleInspect('metrics')} className="rounded-lg px-2 py-1 text-left hover:bg-slate-800">
- View Metrics
-
- handleInspect('logs')} className="rounded-lg px-2 py-1 text-left hover:bg-slate-800">
- View Logs
-
- handleInspect('traces')} className="rounded-lg px-2 py-1 text-left hover:bg-slate-800">
- View Traces
-
-
-
- )}
- {contextMenu.visible && (
-
setContextMenu({ visible: false, x: 0, y: 0 })} />
- )}
-
- )
-}
-
-function createMockNodes(mode: InsightState['topologyMode']): TopologyNode[] {
- switch (mode) {
- case 'network':
- return [
- { id: '1', label: 'Edge Router', type: 'network', status: 'healthy', x: 100, y: 140 },
- { id: '2', label: 'Service Mesh', type: 'network', status: 'warning', x: 260, y: 140 },
- { id: '3', label: 'Kubernetes', type: 'network', status: 'healthy', x: 440, y: 140 }
- ]
- case 'resource':
- return [
- { id: 'node', label: 'Node pool', type: 'database', status: 'healthy', x: 120, y: 160 },
- { id: 'pod', label: 'Checkout pod', type: 'service', status: 'warning', x: 300, y: 120, service: 'checkout' },
- { id: 'db', label: 'Postgres', type: 'database', status: 'critical', x: 480, y: 180, service: 'postgres' }
- ]
- default:
- return [
- { id: 'gw', label: 'API Gateway', type: 'gateway', status: 'healthy', x: 120, y: 120, service: 'gateway' },
- { id: 'checkout', label: 'Checkout', type: 'service', status: 'warning', x: 300, y: 160, service: 'checkout' },
- { id: 'payments', label: 'Payments', type: 'service', status: 'healthy', x: 480, y: 120, service: 'payments' }
- ]
- }
-}
-
-function createMockEdges(nodes: TopologyNode[]): TopologyEdge[] {
- if (nodes.length < 2) return []
- const edges: TopologyEdge[] = []
- for (let i = 0; i < nodes.length - 1; i++) {
- edges.push({ id: `${nodes[i].id}-${nodes[i + 1].id}`, from: nodes[i].id, to: nodes[i + 1].id, latencyMs: 20 + i * 15 })
- }
- return edges
-}
-
-const statusStyles: Record
= {
- healthy: 'stroke-emerald-500/60',
- warning: 'stroke-amber-400/60',
- critical: 'stroke-red-500/60'
-}
diff --git a/src/app/components/insight/topology/types.ts b/src/app/components/insight/topology/types.ts
deleted file mode 100644
index 7078b8f..0000000
--- a/src/app/components/insight/topology/types.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export interface TopologyNode {
- id: string
- label: string
- type: 'service' | 'gateway' | 'database' | 'network'
- status: 'healthy' | 'warning' | 'critical'
- x: number
- y: number
- service?: string
-}
-
-export interface TopologyEdge {
- id: string
- from: string
- to: string
- latencyMs: number
-}
diff --git a/src/app/components/insight/viz/LogsTable.tsx b/src/app/components/insight/viz/LogsTable.tsx
deleted file mode 100644
index e3bdc5d..0000000
--- a/src/app/components/insight/viz/LogsTable.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-'use client'
-
-import { LogEntry } from '../../insight/services/adapters/logs'
-import { getLogLevelColor } from '@lib/format'
-
-interface LogsTableProps {
- logs: LogEntry[]
-}
-
-export function LogsTable({ logs }: LogsTableProps) {
- if (!logs.length) {
- return Run a LogQL query to inspect log lines in a table.
- }
-
- return (
-
-
-
-
-
- Time
-
-
- Level
-
-
- Service
-
-
- Message
-
-
-
-
- {logs.map(log => (
-
-
- {new Date(log.timestamp).toLocaleTimeString()}
-
- {log.level.toUpperCase()}
- {log.service}
- {log.message}
-
- ))}
-
-
-
- )
-}
diff --git a/src/app/components/insight/viz/LogsTopStats.tsx b/src/app/components/insight/viz/LogsTopStats.tsx
deleted file mode 100644
index 5e32d65..0000000
--- a/src/app/components/insight/viz/LogsTopStats.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-'use client'
-
-import { LogEntry } from '../../insight/services/adapters/logs'
-
-interface LogsTopStatsProps {
- logs: LogEntry[]
-}
-
-export function LogsTopStats({ logs }: LogsTopStatsProps) {
- if (!logs.length) {
- return Run a LogQL query to review top-level log insights.
- }
-
- const total = logs.length
- const errorCount = logs.filter(log => log.level.toLowerCase() === 'error').length
- const errorRate = total ? (errorCount / total) * 100 : 0
-
- const serviceCounts = logs.reduce>((acc, log) => {
- acc[log.service] = (acc[log.service] ?? 0) + 1
- return acc
- }, {})
- const [topService, topServiceCount] = Object.entries(serviceCounts).sort((a, b) => b[1] - a[1])[0]
-
- const levelCounts = logs.reduce>((acc, log) => {
- const level = log.level.toUpperCase()
- acc[level] = (acc[level] ?? 0) + 1
- return acc
- }, {})
-
- return (
-
-
-
Log volume
-
{total}
-
Entries returned
-
-
-
Error rate
-
{errorRate.toFixed(1)}%
-
{errorCount} errors
-
-
-
Top service
-
{topService ?? 'unknown'}
-
{topServiceCount ?? 0} entries
-
-
-
Level distribution
-
- {Object.entries(levelCounts).map(([level, count]) => (
-
- {level}: {count}
-
- ))}
-
-
-
- )
-}
diff --git a/src/app/components/insight/viz/LogsViewer.tsx b/src/app/components/insight/viz/LogsViewer.tsx
deleted file mode 100644
index e2fbcf7..0000000
--- a/src/app/components/insight/viz/LogsViewer.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-'use client'
-
-import { LogEntry } from '../../insight/services/adapters/logs'
-import { getLogLevelColor } from '@lib/format'
-
-interface LogsViewerProps {
- logs: LogEntry[]
-}
-
-export function LogsViewer({ logs }: LogsViewerProps) {
- if (!logs.length) {
- return Run a LogQL query to inspect log events.
- }
-
- return (
-
- {logs.map(log => (
-
-
- {new Date(log.timestamp).toLocaleTimeString()}
- {log.level.toUpperCase()}
-
-
{log.message}
- {log.fields && (
-
- {JSON.stringify(log.fields, null, 2)}
-
- )}
-
- ))}
-
- )
-}
diff --git a/src/app/components/insight/viz/MetricsChart.tsx b/src/app/components/insight/viz/MetricsChart.tsx
deleted file mode 100644
index a9dd7e8..0000000
--- a/src/app/components/insight/viz/MetricsChart.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-'use client'
-
-import { PrometheusResponse } from '../../insight/services/adapters/prometheus'
-import { formatNumber } from '@lib/format'
-
-interface MetricsChartProps {
- series: PrometheusResponse[]
-}
-
-export function MetricsChart({ series }: MetricsChartProps) {
- if (!series.length) {
- return Run a query to render time series data.
- }
-
- const width = 520
- const height = 200
- const flat = series.flatMap(s => s.points)
- const min = Math.min(...flat.map(p => p.value))
- const max = Math.max(...flat.map(p => p.value))
- const range = max - min || 1
-
- return (
-
- {series.map((s, idx) => {
- const path = s.points
- .map((point, i) => {
- const x = (i / Math.max(1, s.points.length - 1)) * (width - 40) + 20
- const y = height - 20 - ((point.value - min) / range) * (height - 40)
- return `${i === 0 ? 'M' : 'L'}${x},${y}`
- })
- .join(' ')
- const color = palette[idx % palette.length]
- return (
-
-
-
- {s.metric}: {formatNumber(s.points[s.points.length - 1]?.value ?? 0)}
-
-
- )
- })}
-
-
-
- )
-}
-
-const palette = ['#34d399', '#60a5fa', '#fbbf24']
diff --git a/src/app/components/insight/viz/MetricsTable.tsx b/src/app/components/insight/viz/MetricsTable.tsx
deleted file mode 100644
index 925ab8c..0000000
--- a/src/app/components/insight/viz/MetricsTable.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-'use client'
-
-import { PrometheusResponse } from '../../insight/services/adapters/prometheus'
-import { formatNumber } from '@lib/format'
-
-interface MetricsTableProps {
- series: PrometheusResponse[]
-}
-
-export function MetricsTable({ series }: MetricsTableProps) {
- if (!series.length) {
- return Run a query to inspect series in a tabular view.
- }
-
- const rows = series.map(item => {
- const values = item.points.map(point => point.value)
- const latest = values.at(-1) ?? 0
- const min = Math.min(...values)
- const max = Math.max(...values)
- const avg = values.reduce((acc, value) => acc + value, 0) / (values.length || 1)
- return {
- metric: item.metric,
- latest,
- min,
- max,
- avg
- }
- })
-
- return (
-
-
-
-
-
- Metric
-
-
- Latest
-
-
- Average
-
-
- Min
-
-
- Max
-
-
-
-
- {rows.map(row => (
-
- {row.metric}
- {formatNumber(row.latest)}
- {formatNumber(row.avg)}
- {formatNumber(row.min)}
- {formatNumber(row.max)}
-
- ))}
-
-
-
- )
-}
diff --git a/src/app/components/insight/viz/MetricsTopStats.tsx b/src/app/components/insight/viz/MetricsTopStats.tsx
deleted file mode 100644
index d2264ac..0000000
--- a/src/app/components/insight/viz/MetricsTopStats.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-'use client'
-
-import { PrometheusResponse } from '../../insight/services/adapters/prometheus'
-import { formatNumber } from '@lib/format'
-
-interface MetricsTopStatsProps {
- series: PrometheusResponse[]
-}
-
-export function MetricsTopStats({ series }: MetricsTopStatsProps) {
- if (!series.length) {
- return Run a query to surface top metrics and aggregates.
- }
-
- const ranked = series
- .map(item => {
- const values = item.points.map(point => point.value)
- const latest = values.at(-1) ?? 0
- const peak = Math.max(...values)
- return { metric: item.metric, latest, peak }
- })
- .sort((a, b) => b.latest - a.latest)
- .slice(0, 3)
-
- const overallLatest = ranked.reduce((sum, item) => sum + item.latest, 0)
-
- return (
-
-
-
Combined latest value
-
{formatNumber(overallLatest)}
-
- {ranked.map(item => (
-
-
{item.metric}
-
{formatNumber(item.latest)}
-
Peak {formatNumber(item.peak)}
-
- ))}
-
- )
-}
diff --git a/src/app/components/insight/viz/TracesTable.tsx b/src/app/components/insight/viz/TracesTable.tsx
deleted file mode 100644
index b37677f..0000000
--- a/src/app/components/insight/viz/TracesTable.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-'use client'
-
-import { TraceSpan } from '../../insight/services/adapters/traces'
-
-interface TracesTableProps {
- spans: TraceSpan[]
-}
-
-export function TracesTable({ spans }: TracesTableProps) {
- if (!spans.length) {
- return Run a TraceQL query to review spans in a table.
- }
-
- return (
-
-
-
-
-
- Span
-
-
- Service
-
-
- Duration (ms)
-
-
- Start time
-
-
-
-
- {spans.map(span => (
-
- {span.name}
- {span.service}
- {span.durationMs.toFixed(1)}
-
- {new Date(span.startTime).toLocaleTimeString()}
-
-
- ))}
-
-
-
- )
-}
diff --git a/src/app/components/insight/viz/TracesTopStats.tsx b/src/app/components/insight/viz/TracesTopStats.tsx
deleted file mode 100644
index e1a084b..0000000
--- a/src/app/components/insight/viz/TracesTopStats.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-'use client'
-
-import { TraceSpan } from '../../insight/services/adapters/traces'
-
-interface TracesTopStatsProps {
- spans: TraceSpan[]
-}
-
-export function TracesTopStats({ spans }: TracesTopStatsProps) {
- if (!spans.length) {
- return Run a TraceQL query to surface top spans and bottlenecks.
- }
-
- const longestSpan = spans.reduce((prev, current) => (current.durationMs > prev.durationMs ? current : prev), spans[0])
- const averageDuration = spans.reduce((sum, span) => sum + span.durationMs, 0) / spans.length
-
- const serviceDurations = spans.reduce>((acc, span) => {
- acc[span.service] = (acc[span.service] ?? 0) + span.durationMs
- return acc
- }, {})
- const [heaviestService, heaviestDuration] = Object.entries(serviceDurations).sort((a, b) => b[1] - a[1])[0]
-
- return (
-
-
-
Longest span
-
{longestSpan.name}
-
{longestSpan.durationMs.toFixed(1)} ms
-
-
-
Average duration
-
{averageDuration.toFixed(1)} ms
-
Across {spans.length} spans
-
-
-
Heaviest service
-
{heaviestService ?? 'unknown'}
-
{(heaviestDuration ?? 0).toFixed(1)} ms total
-
-
- )
-}
diff --git a/src/app/components/insight/viz/TracesWaterfall.tsx b/src/app/components/insight/viz/TracesWaterfall.tsx
deleted file mode 100644
index 028f827..0000000
--- a/src/app/components/insight/viz/TracesWaterfall.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-'use client'
-
-import { TraceSpan } from '../../insight/services/adapters/traces'
-
-interface TracesWaterfallProps {
- spans: TraceSpan[]
-}
-
-export function TracesWaterfall({ spans }: TracesWaterfallProps) {
- if (!spans.length) {
- return Run a TraceQL query to render waterfall timelines.
- }
-
- const rootStart = Math.min(...spans.map(span => span.startTime))
- const totalDuration = Math.max(...spans.map(span => span.startTime + span.durationMs)) - rootStart || 1
-
- return (
-
- {spans.map(span => {
- const offset = ((span.startTime - rootStart) / totalDuration) * 100
- const width = (span.durationMs / totalDuration) * 100
- return (
-
-
- {span.name}
- {span.durationMs.toFixed(1)} ms
-
-
-
- )
- })}
-
- )
-}
diff --git a/src/app/components/insight/viz/VizArea.tsx b/src/app/components/insight/viz/VizArea.tsx
deleted file mode 100644
index b28c7fc..0000000
--- a/src/app/components/insight/viz/VizArea.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-'use client'
-
-import { ReactNode, useEffect, useMemo, useState } from 'react'
-import { buildCorrelatedQuery } from '../../insight/services/correlator'
-import { DataSource, InsightState } from '../../insight/store/urlState'
-import { MetricsChart } from './MetricsChart'
-import { MetricsTable } from './MetricsTable'
-import { MetricsTopStats } from './MetricsTopStats'
-import { LogsViewer } from './LogsViewer'
-import { LogsTable } from './LogsTable'
-import { LogsTopStats } from './LogsTopStats'
-import { TracesWaterfall } from './TracesWaterfall'
-import { TracesTable } from './TracesTable'
-import { TracesTopStats } from './TracesTopStats'
-
-type ViewMode = 'trend' | 'table' | 'top'
-
-interface VizAreaProps {
- state: InsightState
- data: any
- onUpdate: (partial: Partial) => void
-}
-
-export function VizArea({ state, data, onUpdate }: VizAreaProps) {
- const mode = state.dataSource
- const [viewMode, setViewMode] = useState('trend')
-
- useEffect(() => {
- setViewMode('trend')
- }, [mode])
-
- const viewOptions = useMemo(
- () => [
- { id: 'trend' as ViewMode, label: 'Trend chart' },
- { id: 'table' as ViewMode, label: 'Table' },
- { id: 'top' as ViewMode, label: 'Top stats' }
- ],
- []
- )
-
- const title = modeLabel[mode]
-
- function correlate(target: DataSource) {
- const language: InsightState['queryLanguage'] = target === 'metrics' ? 'promql' : target === 'logs' ? 'logql' : 'traceql'
- const query = buildCorrelatedQuery(target, {
- service: state.service || 'checkout',
- namespace: state.namespace,
- timeRange: state.timeRange
- })
- onUpdate({
- dataSource: target,
- queryLanguage: language,
- queries: { ...state.queries, [language]: query },
- activeLanguages: Array.from(new Set([...state.activeLanguages, language]))
- })
- }
-
- function renderContent(): ReactNode {
- const metricsSeries = Array.isArray(data) ? data : []
- const logs = Array.isArray(data) ? data : []
- const traces = Array.isArray(data) ? data : []
-
- switch (mode) {
- case 'metrics':
- if (viewMode === 'table') {
- return
- }
- if (viewMode === 'top') {
- return
- }
- return
- case 'logs':
- if (viewMode === 'table') {
- return
- }
- if (viewMode === 'top') {
- return
- }
- return
- case 'traces':
- if (viewMode === 'table') {
- return
- }
- if (viewMode === 'top') {
- return
- }
- return
- default:
- return null
- }
- }
-
- return (
-
-
- {title &&
{title} }
-
-
- {viewOptions.map(option => (
- setViewMode(option.id)}
- className={`px-3 py-1 text-xs transition ${
- viewMode === option.id ? 'bg-slate-800 text-slate-100' : 'bg-slate-900/50 text-slate-400 hover:bg-slate-800'
- }`}
- >
- {option.label}
-
- ))}
-
-
correlate('metrics')} className="rounded-xl border border-slate-800 px-3 py-1 hover:bg-slate-800">
- Link to Metrics
-
-
correlate('logs')} className="rounded-xl border border-slate-800 px-3 py-1 hover:bg-slate-800">
- Link to Logs
-
-
correlate('traces')} className="rounded-xl border border-slate-800 px-3 py-1 hover:bg-slate-800">
- Link to Traces
-
-
Save to dashboard
-
-
-
- {renderContent()}
-
-
- )
-}
-
-const modeLabel: Record = {
- metrics: '',
- logs: 'Log stream',
- traces: 'Trace waterfall'
-}
diff --git a/src/app/insight/InsightWorkbench.tsx b/src/app/insight/InsightWorkbench.tsx
deleted file mode 100644
index 057f052..0000000
--- a/src/app/insight/InsightWorkbench.tsx
+++ /dev/null
@@ -1,377 +0,0 @@
-'use client'
-
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import type { Layout } from 'react-grid-layout'
-import { ChevronLeft, ChevronRight, PanelLeftOpen } from 'lucide-react'
-import { Sidebar } from '../components/insight/layout/Sidebar'
-import { WorkspaceHeader } from '../components/insight/layout/WorkspaceHeader'
-import { BreadcrumbBar } from '../components/insight/layout/BreadcrumbBar'
-import { WorkspaceGrid } from '../components/insight/layout/WorkspaceGrid'
-import { NetworkTopologyPanel } from '../components/insight/topology/NetworkTopologyPanel'
-import { ExploreBuilder, languageMeta } from '../components/insight/explore/ExploreBuilder'
-import { VizArea } from '../components/insight/viz/VizArea'
-import { SLOPanel } from '../components/insight/slo/SLOPanel'
-import { AIAssistant } from '../components/insight/ai/Assistant'
-import { useInsightStore } from '../components/insight/store/useInsightState'
-import { DataSource, QueryLanguage } from '../components/insight/store/urlState'
-
-const LAYOUT_STORAGE_KEY = 'insight-workspace-layout-v1'
-
-const DEFAULT_LAYOUT: Layout[] = [
- { i: 'network', x: 0, y: 0, w: 6, h: 8, minW: 4, minH: 6 },
- { i: 'promql', x: 6, y: 0, w: 6, h: 8, minW: 4, minH: 6 },
- { i: 'logql', x: 0, y: 8, w: 6, h: 8, minW: 4, minH: 6 },
- { i: 'traceql', x: 6, y: 8, w: 6, h: 8, minW: 4, minH: 6 }
-]
-
-export default function InsightWorkbench() {
- const state = useInsightStore((store) => store.state)
- const updateState = useInsightStore((store) => store.updateInsight)
- const shareableLink = useInsightStore((store) => store.shareableLink)
- const [activeSection, setActiveSection] = useState('topology')
- const [history, setHistory] = useState>({
- promql: [],
- logql: [],
- traceql: []
- })
- const [resultData, setResultData] = useState>({
- promql: [],
- logql: [],
- traceql: []
- })
- const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
- const [sidebarHidden, setSidebarHidden] = useState(false)
- const [panelLayout, setPanelLayout] = useState(() => DEFAULT_LAYOUT.map(item => ({ ...item })))
- const [layoutDirty, setLayoutDirty] = useState(false)
- const [layoutStatus, setLayoutStatus] = useState(null)
- const [detailsCollapsed, setDetailsCollapsed] = useState(false)
- const statusTimeout = useRef(null)
-
- const handleSelectSection = useCallback((section: string) => {
- setActiveSection(section)
- const el = document.getElementById(section)
- if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
- }, [])
-
- const updateHistory = useCallback(
- (language: QueryLanguage, items: string[]) => {
- setHistory(prev => ({ ...prev, [language]: items }))
- },
- []
- )
-
- const updateResults = useCallback((language: QueryLanguage, data: any) => {
- setResultData(prev => ({ ...prev, [language]: data }))
- }, [])
-
- const toggleLanguage = useCallback(
- (language: QueryLanguage) => {
- const exists = state.activeLanguages.includes(language)
- let nextActive = exists
- ? state.activeLanguages.filter(item => item !== language)
- : [...state.activeLanguages, language]
- if (nextActive.length === 0) {
- nextActive = [language]
- }
- const primary = nextActive[0]
- const nextSource: DataSource = primary === 'promql' ? 'metrics' : primary === 'logql' ? 'logs' : 'traces'
- updateState({
- activeLanguages: nextActive,
- queryLanguage: primary,
- dataSource: nextSource
- })
- },
- [state.activeLanguages, updateState]
- )
-
- const handleLayoutChange = useCallback((next: Layout[]) => {
- setPanelLayout(next)
- setLayoutDirty(true)
- }, [])
-
- const resetStatusMessage = useCallback(() => {
- if (statusTimeout.current) {
- window.clearTimeout(statusTimeout.current)
- statusTimeout.current = null
- }
- }, [])
-
- const handleSaveLayout = useCallback(() => {
- if (typeof window === 'undefined') return
- window.localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(panelLayout))
- setLayoutDirty(false)
- setLayoutStatus('Layout saved locally')
- resetStatusMessage()
- statusTimeout.current = window.setTimeout(() => setLayoutStatus(null), 2200)
- }, [panelLayout, resetStatusMessage])
-
- const handleResetLayout = useCallback(() => {
- setPanelLayout(DEFAULT_LAYOUT.map(item => ({ ...item })))
- setLayoutDirty(false)
- if (typeof window !== 'undefined') {
- window.localStorage.removeItem(LAYOUT_STORAGE_KEY)
- }
- setLayoutStatus('Layout reset to default')
- resetStatusMessage()
- statusTimeout.current = window.setTimeout(() => setLayoutStatus(null), 2200)
- }, [resetStatusMessage])
-
- useEffect(() => {
- if (typeof window === 'undefined') return
- const stored = window.localStorage.getItem(LAYOUT_STORAGE_KEY)
- if (!stored) return
- try {
- const parsed = JSON.parse(stored) as Layout[]
- if (!Array.isArray(parsed)) return
- const merged = DEFAULT_LAYOUT.map(item => {
- const match = parsed.find(entry => entry.i === item.i)
- return match ? { ...item, ...match } : { ...item }
- })
- setPanelLayout(merged)
- setLayoutDirty(false)
- } catch (error) {
- console.error('Failed to restore insight layout', error)
- }
- }, [])
-
- useEffect(() => {
- return () => {
- if (statusTimeout.current) {
- window.clearTimeout(statusTimeout.current)
- }
- }
- }, [])
-
- const keyMetrics = useMemo(
- () => [
- {
- label: 'Availability',
- value: state.topologyMode === 'network' ? '99.96%' : '99.90%',
- trend: '+0.3% vs last 7d',
- tone: 'positive' as const
- },
- {
- label: 'P95 latency',
- value: state.topologyMode === 'network' ? '82 ms' : '248 ms',
- trend: `${state.timeRange} window`,
- tone: 'neutral' as const
- },
- {
- label: 'Error rate',
- value: state.topologyMode === 'application' ? '0.7%' : '0.4%',
- trend: 'Target < 1%',
- tone: 'warning' as const
- }
- ],
- [state.timeRange, state.topologyMode]
- )
-
- const explorerPanels = (language: QueryLanguage, domId?: string) => {
- const enabled = state.activeLanguages.includes(language)
- return {
- id: language,
- domId,
- minW: 4,
- minH: 6,
- content: enabled ? (
-
- ) : (
- toggleLanguage(language)} />
- )
- }
- }
-
- const panels = [
- {
- id: 'network',
- domId: 'topology',
- minW: 4,
- minH: 6,
- content:
- },
- explorerPanels('promql', 'explore'),
- explorerPanels('logql'),
- explorerPanels('traceql')
- ]
-
- const insightAsideWidth = detailsCollapsed ? 'lg:w-60 xl:w-64' : 'lg:w-80 xl:w-96'
-
- return (
-
- {sidebarHidden && (
-
setSidebarHidden(false)}
- className="fixed left-4 top-4 z-20 flex items-center gap-2 rounded-full border border-slate-800 bg-slate-900/80 px-4 py-2 text-sm text-slate-200 shadow-lg backdrop-blur transition hover:border-slate-700 hover:text-slate-100"
- >
-
- Show menu
-
- )}
-
-
- {!sidebarHidden && (
-
- updateState({ topologyMode: mode })}
- onToggleLanguage={toggleLanguage}
- onToggleCollapse={() => setSidebarCollapsed(prev => !prev)}
- onHide={() => setSidebarHidden(true)}
- collapsed={sidebarCollapsed}
- />
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- setDetailsCollapsed(prev => !prev)}
- className="flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/70 px-3 py-1 text-xs text-slate-300 transition hover:border-slate-700 hover:text-slate-100"
- >
- {detailsCollapsed ? (
- <>
- Expand insights
- >
- ) : (
- <>
- Collapse insights
- >
- )}
-
-
- {detailsCollapsed ? (
-
-
Key health metrics
-
Pinned while the panel is collapsed for quick status checks.
-
- {keyMetrics.map(metric => (
-
-
{metric.label}
-
{metric.value}
-
- {metric.trend}
-
-
- ))}
-
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- )
-}
-
-interface DisabledExplorerCardProps {
- language: QueryLanguage
- onEnable: () => void
-}
-
-function DisabledExplorerCard({ language, onEnable }: DisabledExplorerCardProps) {
- const meta = languageMeta[language]
- return (
-
-
-
-
Capture metrics, logs or traces by toggling the language on the left-hand menu.
-
- Enable {meta.label.split(' ')[0]}
-
-
-
- )
-}
diff --git a/src/app/insight/page.tsx b/src/app/insight/page.tsx
deleted file mode 100644
index 88fc371..0000000
--- a/src/app/insight/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { redirect } from 'next/navigation'
-
-export default function InsightPage() {
- redirect('https://infra.svc.plus/')
-}
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
index f211f79..588a978 100644
--- a/src/app/sitemap.ts
+++ b/src/app/sitemap.ts
@@ -49,11 +49,6 @@ export default async function sitemap(): Promise {
changeFrequency: 'monthly',
priority: 0.6,
},
- {
- url: `${baseUrl}/insight`,
- changeFrequency: 'monthly',
- priority: 0.6,
- },
{
url: `${baseUrl}/cloud_iac`,
changeFrequency: 'monthly',