more otel
This commit is contained in:
1 parent
9620e3bfa6
commit
21f04d696b
88 files changed
+23419
-192
No files matched your search
@@ -0,0 +1,48 @@
|
||||
import { useState } from "react"
|
||||
import { Activity, CheckCircle2, CloudCog, RefreshCw, XCircle } from "lucide-react"
|
||||
import { useRollouts } from "../hooks/useRollouts"
|
||||
import { formatRelativeTime } from "../lib/timeUtils"
|
||||
import type { RolloutImpact } from "../lib/types"
|
||||
|
||||
const phaseStyle: Record<string, { className: string; Icon: typeof Activity }> = {
|
||||
completed: { className: "text-emerald-300 bg-emerald-400/10 border-emerald-400/20", Icon: CheckCircle2 },
|
||||
failed: { className: "text-rose-300 bg-rose-400/10 border-rose-400/20", Icon: XCircle },
|
||||
progressing: { className: "text-amber-300 bg-amber-400/10 border-amber-400/20", Icon: Activity },
|
||||
}
|
||||
|
||||
export function RolloutsVisualizer() {
|
||||
const { rollouts, loading, error, refresh, impact } = useRollouts()
|
||||
const [selected, setSelected] = useState<string | null>(null)
|
||||
const [impactData, setImpactData] = useState<Record<string, RolloutImpact>>({})
|
||||
const [impactError, setImpactError] = useState<string | null>(null)
|
||||
|
||||
const select = async (id: string) => {
|
||||
setSelected(selected === id ? null : id)
|
||||
if (impactData[id]) return
|
||||
try {
|
||||
setImpactError(null)
|
||||
const result = await impact(id)
|
||||
setImpactData((previous) => ({ ...previous, [id]: result }))
|
||||
}
|
||||
catch (cause) { setImpactError(cause instanceof Error ? cause.message : String(cause)) }
|
||||
}
|
||||
|
||||
if (loading && rollouts.length === 0) return <div className="py-16 text-center text-sm text-slate-500">Loading cluster rollouts…</div>
|
||||
return <div className="space-y-4">
|
||||
<div className="flex flex-wrap items-center gap-3 border-b border-white/5 pb-3">
|
||||
<div><div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-[0.2em] text-slate-500"><CloudCog className="h-3 w-3 text-sky-400" /> Cluster Rollouts</div><div className="mt-1 text-xs text-slate-500">Read-only Kubernetes deployment state with optional Prometheus impact data.</div></div>
|
||||
<button onClick={() => void refresh()} className="ml-auto inline-flex items-center gap-2 rounded-lg border border-white/10 px-3 py-2 text-[10px] font-bold uppercase tracking-wider text-slate-400 hover:text-white"><RefreshCw className="h-3 w-3" /> Refresh</button>
|
||||
</div>
|
||||
{error && <div className="rounded-lg border border-amber-400/20 bg-amber-400/5 p-3 text-xs text-amber-200">Rollout watcher unavailable: {String(error)}</div>}
|
||||
{impactError && <div className="rounded-lg border border-rose-400/20 bg-rose-400/5 p-3 text-xs text-rose-300">Impact query failed: {impactError}</div>}
|
||||
{rollouts.length === 0 ? <div className="py-16 text-center text-sm text-slate-500">No rollout data. Enable the Kubernetes watcher and grant its read-only RBAC permissions.</div> : <div className="space-y-2">{rollouts.map((rollout) => {
|
||||
const status = phaseStyle[rollout.phase] ?? phaseStyle.progressing
|
||||
const Icon = status.Icon
|
||||
const data = impactData[rollout.id]
|
||||
return <div key={rollout.id} className="rounded-xl border border-white/10 bg-slate-950/40 p-4">
|
||||
<button onClick={() => void select(rollout.id)} className="w-full text-left"><div className="flex flex-wrap items-center gap-3"><span className={`inline-flex items-center gap-1 rounded border px-2 py-1 text-[10px] font-bold uppercase ${status.className}`}><Icon className="h-3 w-3" /> {rollout.phase}</span><span className="font-mono text-xs text-slate-200">{rollout.namespace}/{rollout.workload}</span><span className="text-xs text-slate-400">revision {rollout.revision || "unknown"}</span><span className="ml-auto text-[10px] text-slate-500">{formatRelativeTime(rollout.lastSeen / 1e6)}</span></div><div className="mt-3 grid gap-2 text-[11px] text-slate-400 sm:grid-cols-2 lg:grid-cols-4"><span>release: <b className="text-slate-200">{rollout.release || "unlabeled"}</b></span><span>image: <b className="font-mono text-slate-200">{rollout.image || "unknown"}</b></span><span>replicas: <b className="text-slate-200">{rollout.availableReplicas}/{rollout.desiredReplicas} ready</b></span><span>reason: <b className="text-slate-200">{rollout.reason || "unknown"}</b></span></div></button>
|
||||
{selected === rollout.id && <div className="mt-4 border-t border-white/5 pt-3 text-xs text-slate-400"><div>Started {formatRelativeTime(rollout.startedAt / 1e6)}{rollout.completedAt ? ` · completed ${formatRelativeTime(rollout.completedAt / 1e6)}` : ""}</div><div className="mt-2 text-slate-500">{rollout.message || "No rollout message reported."}</div>{data && <div className="mt-3 rounded-lg bg-black/20 p-3"><div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-slate-500"><Activity className="h-3 w-3" /> Prometheus error-rate points</div>{!data.configured ? <div className="mt-2 text-slate-600">Prometheus is not configured.</div> : data.points.length === 0 ? <div className="mt-2 text-slate-600">No matching Prometheus series.</div> : <div className="mt-2 flex flex-wrap gap-2">{data.points.slice(-12).map((point) => <span key={`${point.timestamp}-${point.value}`} className="rounded bg-sky-400/10 px-2 py-1 font-mono text-[10px] text-sky-300">{new Date(point.timestamp * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}: {point.value}</span>)}</div>}</div>}</div>}
|
||||
</div>
|
||||
})}</div>}
|
||||
</div>
|
||||
}
|
||||
Reference in new issue
Block a user