feat(ui): redesign live-ops-space to use professional dark mode SaaS aesthetics
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m3s
CD Pipeline / build-and-deploy (push) Successful in 5m34s
CD Pipeline / post-deploy-checks (push) Successful in 3m53s
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m3s
CD Pipeline / build-and-deploy (push) Successful in 5m34s
CD Pipeline / post-deploy-checks (push) Successful in 3m53s
This commit is contained in:
@@ -110,11 +110,11 @@ function stateLabel(t: ReturnType<typeof useTranslations>, state: string): strin
|
||||
}
|
||||
|
||||
function stateClass(state: string): string {
|
||||
if (state === "working") return "border-[#4a90d9] bg-white/70 backdrop-blur-[20px] text-[#4a90d9] shadow-[0_0_15px_rgba(74,144,217,0.4)]";
|
||||
if (state === "verified") return "border-[#8fc29a] bg-white/70 backdrop-blur-[20px] text-[#17602a]";
|
||||
if (state === "blocked") return "border-[#e2a29b] bg-white/70 backdrop-blur-[20px] text-[#9f2f25]";
|
||||
if (state === "waiting") return "border-[#d9b36f] bg-white/70 backdrop-blur-[20px] text-[#8a5a08]";
|
||||
return "border-[#d8d3c7] bg-white/70 backdrop-blur-[20px] text-[#5f5b52]";
|
||||
if (state === "working") return "border-[#4a90d9]/50 bg-[#4a90d9]/10 backdrop-blur-[20px] text-[#60a5fa] shadow-[0_0_15px_rgba(74,144,217,0.3)]";
|
||||
if (state === "verified") return "border-[#4f9b62]/50 bg-[#4f9b62]/10 backdrop-blur-[20px] text-[#8fc29a]";
|
||||
if (state === "blocked") return "border-[#d95f4f]/50 bg-[#d95f4f]/10 backdrop-blur-[20px] text-[#e2a29b]";
|
||||
if (state === "waiting") return "border-[#f0bd69]/50 bg-[#f0bd69]/10 backdrop-blur-[20px] text-[#fcd34d]";
|
||||
return "border-white/10 bg-white/5 backdrop-blur-[20px] text-[#a3a097]";
|
||||
}
|
||||
|
||||
function stateFromWorkItemStatus(status: string): string {
|
||||
@@ -198,26 +198,33 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
|
||||
return (
|
||||
<AppLayout locale={params.locale} fullBleed>
|
||||
<main className="min-h-screen overflow-x-hidden bg-[#f7f8f7] text-[#141413]">
|
||||
<main className="min-h-screen overflow-x-hidden bg-[#141413] text-[#f5f4ed]">
|
||||
<div className="mx-auto flex w-full max-w-[1520px] flex-col gap-4 px-3 py-4 lg:px-5">
|
||||
<section className="grid min-w-0 items-start gap-4 lg:grid-cols-[minmax(0,1fr)_360px]">
|
||||
<div className="min-w-0 border border-[#d8d3c7] bg-white">
|
||||
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3 border-b border-[#e5e1d8] px-4 py-3">
|
||||
{/* Main Stage */}
|
||||
<div className="min-w-0 rounded-2xl border border-white/10 bg-[#1c1c1a]/50 shadow-2xl backdrop-blur-3xl">
|
||||
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3 border-b border-white/10 px-6 py-5">
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-semibold uppercase text-[#77736a]">
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-[#a3a097]">
|
||||
OpenClaw
|
||||
</p>
|
||||
<h1 className="mt-1 break-words text-2xl font-semibold leading-tight text-[#141413] sm:text-3xl">
|
||||
<h1 className="mt-1 flex items-center gap-3 break-words text-2xl font-bold leading-tight text-[#f5f4ed] sm:text-3xl">
|
||||
{t("title")}
|
||||
{status === "ready" && (
|
||||
<span className="relative flex h-3 w-3">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#4a90d9] opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-3 w-3 bg-[#4a90d9]"></span>
|
||||
</span>
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 border px-3 py-1.5 text-xs font-semibold",
|
||||
"inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-semibold uppercase tracking-wider backdrop-blur-md",
|
||||
status === "ready"
|
||||
? "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]"
|
||||
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
? "border-[#4f9b62]/30 bg-[#4f9b62]/10 text-[#8fc29a]"
|
||||
: "border-[#d9b36f]/30 bg-[#d9b36f]/10 text-[#fcd34d]",
|
||||
)}
|
||||
>
|
||||
{status === "ready" ? (
|
||||
@@ -234,7 +241,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void fetchSceneState()}
|
||||
className="inline-flex h-9 w-9 items-center justify-center border border-[#d8d3c7] bg-white text-[#4f4b44] transition hover:border-[#4a90d9] hover:text-[#1f5b9b]"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-white/10 bg-white/5 text-[#a3a097] transition-all hover:bg-white/10 hover:text-[#f5f4ed] hover:shadow-[0_0_15px_rgba(255,255,255,0.1)]"
|
||||
title={t("actions.refresh")}
|
||||
aria-label={t("actions.refresh")}
|
||||
>
|
||||
@@ -243,37 +250,45 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Isometric Room */}
|
||||
<div
|
||||
data-testid="openclaw-live-ops-room"
|
||||
className="relative aspect-[16/10] min-h-[420px] overflow-hidden bg-[#e8eff3] sm:min-h-[560px]"
|
||||
className="relative aspect-[16/10] min-h-[420px] overflow-hidden bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-[#1c1c1a] via-[#141413] to-[#0a0a0a] sm:min-h-[560px] rounded-b-2xl"
|
||||
>
|
||||
<div className="absolute inset-x-[3%] bottom-[4%] top-[7%] rotate-[-2deg] skew-y-[-7deg] border border-[#b7c8d0] bg-[#fdfefe] shadow-[0_30px_80px_rgba(37,55,68,0.18)]" />
|
||||
<div className="absolute inset-x-[5%] bottom-[9%] top-[11%] rotate-[-2deg] skew-y-[-7deg] bg-[linear-gradient(90deg,rgba(74,144,217,0.14)_1px,transparent_1px),linear-gradient(0deg,rgba(74,144,217,0.11)_1px,transparent_1px)] bg-[size:44px_44px]" />
|
||||
<div className="absolute left-[8%] top-[10%] h-[13%] w-[84%] rotate-[-2deg] skew-y-[-7deg] border border-[#c6d1d5] bg-[#e9eee8]" />
|
||||
<div className="absolute left-[14%] top-[14%] h-10 w-[17%] rotate-[-2deg] skew-y-[-7deg] border border-[#aebbc0] bg-[#26333a] shadow-[0_10px_22px_rgba(37,55,68,0.22)]">
|
||||
{/* Dark mode ISO Planes */}
|
||||
<div className="absolute inset-x-[3%] bottom-[4%] top-[7%] rotate-[-2deg] skew-y-[-7deg] border border-white/10 bg-[#141413] shadow-[0_30px_80px_rgba(0,0,0,0.8)]" />
|
||||
<div className="absolute inset-x-[5%] bottom-[9%] top-[11%] rotate-[-2deg] skew-y-[-7deg] bg-[linear-gradient(90deg,rgba(74,144,217,0.05)_1px,transparent_1px),linear-gradient(0deg,rgba(74,144,217,0.05)_1px,transparent_1px)] bg-[size:44px_44px] shadow-inner" />
|
||||
<div className="absolute left-[8%] top-[10%] h-[13%] w-[84%] rotate-[-2deg] skew-y-[-7deg] border border-white/5 bg-[#1c1c1a]" />
|
||||
|
||||
{/* Server racks / Databanks */}
|
||||
<div className="absolute left-[14%] top-[14%] h-10 w-[17%] rotate-[-2deg] skew-y-[-7deg] border border-[#4a90d9]/20 bg-[#1a1f24] shadow-[0_10px_22px_rgba(0,0,0,0.5)]">
|
||||
<span className="openclaw-screen-sweep absolute inset-0" />
|
||||
<span className="absolute left-2 top-2 h-1.5 w-14 bg-[#70d6a4]" />
|
||||
<span className="absolute bottom-2 left-2 h-1.5 w-9 bg-[#f0bd69]" />
|
||||
<span className="absolute left-2 top-2 h-1.5 w-14 bg-[#4f9b62] shadow-[0_0_8px_rgba(79,155,98,0.6)]" />
|
||||
<span className="absolute bottom-2 left-2 h-1.5 w-9 bg-[#f0bd69] shadow-[0_0_8px_rgba(240,189,105,0.6)]" />
|
||||
</div>
|
||||
<div className="absolute right-[14%] top-[14%] h-10 w-[17%] rotate-[-2deg] skew-y-[-7deg] border border-[#aebbc0] bg-[#26333a] shadow-[0_10px_22px_rgba(37,55,68,0.22)]">
|
||||
<div className="absolute right-[14%] top-[14%] h-10 w-[17%] rotate-[-2deg] skew-y-[-7deg] border border-[#4a90d9]/20 bg-[#1a1f24] shadow-[0_10px_22px_rgba(0,0,0,0.5)]">
|
||||
<span className="openclaw-screen-sweep absolute inset-0" />
|
||||
<span className="absolute left-2 top-2 h-1.5 w-11 bg-[#8fc29a]" />
|
||||
<span className="absolute bottom-2 left-2 h-1.5 w-16 bg-[#4a90d9]" />
|
||||
<span className="absolute left-2 top-2 h-1.5 w-11 bg-[#8fc29a] shadow-[0_0_8px_rgba(143,194,154,0.6)]" />
|
||||
<span className="absolute bottom-2 left-2 h-1.5 w-16 bg-[#4a90d9] shadow-[0_0_10px_rgba(74,144,217,0.8)] animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="absolute left-1/2 top-[48%] h-[19%] w-[30%] -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] skew-y-[-7deg] border border-[#b7b0a5] bg-[#ead9bd] shadow-[0_22px_42px_rgba(63,49,34,0.16)]" />
|
||||
<div className="absolute left-1/2 top-[46%] h-[10%] w-[20%] -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] skew-y-[-7deg] border border-[#87939a] bg-[#27333a] shadow-[0_14px_28px_rgba(37,55,68,0.2)]">
|
||||
<span className="absolute left-3 top-3 h-1.5 w-16 bg-[#4a90d9]" />
|
||||
<span className="absolute bottom-3 right-3 h-1.5 w-12 bg-[#8fc29a]" />
|
||||
{/* Central AI Core Platform */}
|
||||
<div className="absolute left-1/2 top-[48%] h-[19%] w-[30%] -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] skew-y-[-7deg] border border-[#d97757]/20 bg-[#1f1a18] shadow-[0_22px_42px_rgba(217,119,87,0.1)]" />
|
||||
<div className="absolute left-1/2 top-[46%] h-[10%] w-[20%] -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] skew-y-[-7deg] border border-[#4a90d9]/30 bg-[#161b22] shadow-[0_14px_28px_rgba(0,0,0,0.6)]">
|
||||
<span className="absolute left-3 top-3 h-1.5 w-16 bg-[#4a90d9] shadow-[0_0_10px_rgba(74,144,217,0.8)]" />
|
||||
<span className="absolute bottom-3 right-3 h-1.5 w-12 bg-[#d97757] shadow-[0_0_10px_rgba(217,119,87,0.8)]" />
|
||||
</div>
|
||||
<div className="absolute left-[25%] top-[77%] h-[9%] w-[15%] rotate-[-2deg] skew-y-[-7deg] border border-[#b7b0a5] bg-[#d6c8aa]" />
|
||||
<div className="absolute right-[20%] top-[76%] h-[10%] w-[12%] rotate-[-2deg] skew-y-[-7deg] border border-[#b7b0a5] bg-[#d8e5df]" />
|
||||
|
||||
{/* Secondary Platforms */}
|
||||
<div className="absolute left-[25%] top-[77%] h-[9%] w-[15%] rotate-[-2deg] skew-y-[-7deg] border border-white/5 bg-[#1c1c1a]" />
|
||||
<div className="absolute right-[20%] top-[76%] h-[10%] w-[12%] rotate-[-2deg] skew-y-[-7deg] border border-white/5 bg-[#1c1c1a]" />
|
||||
|
||||
{/* Data Packets (Flow) */}
|
||||
{packetSlots.map((slot) => (
|
||||
<span
|
||||
key={slot}
|
||||
data-testid="openclaw-flow-packet"
|
||||
className="openclaw-flow-packet absolute h-2.5 w-2.5 border border-[#f0bd69] bg-[#fff6c7] shadow-[0_0_18px_rgba(240,189,105,0.5)]"
|
||||
className="openclaw-flow-packet absolute h-2 w-2 rounded-full border border-[#60a5fa] bg-[#bfdbfe] shadow-[0_0_12px_rgba(96,165,250,0.9)]"
|
||||
style={{
|
||||
left: `${14 + (slot % 7) * 11}%`,
|
||||
top: `${28 + (slot % 4) * 12}%`,
|
||||
@@ -283,31 +298,33 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Zones (Glass Capsules) */}
|
||||
{zones.map((zone) => {
|
||||
const Icon = zoneIconByKind[zone.kind] ?? Activity;
|
||||
return (
|
||||
<div
|
||||
key={zone.zone_id}
|
||||
data-testid="openclaw-zone"
|
||||
className="absolute flex h-14 w-32 -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] items-center gap-2 rounded-xl border border-white/40 bg-white/70 px-2.5 shadow-[0_8px_32px_rgba(31,38,135,0.07)] backdrop-blur-[20px]"
|
||||
className="absolute flex h-14 w-36 -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] items-center gap-3 rounded-2xl border border-white/10 bg-[#1c1c1a]/60 px-3 shadow-[0_8px_32px_rgba(0,0,0,0.4)] backdrop-blur-xl transition-all hover:scale-105 hover:bg-[#242422]/80"
|
||||
style={{ left: `${zone.position.x}%`, top: `${zone.position.y}%` }}
|
||||
>
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-white/50 bg-white/40 text-[#4a90d9] shadow-sm backdrop-blur-md">
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-white/10 bg-[#2a2a28]/50 text-[#a3a097] shadow-inner backdrop-blur-md">
|
||||
<Icon className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
<span className="min-w-0 truncate font-mono text-[11px] font-semibold tracking-tight text-[#34302a]">
|
||||
<span className="min-w-0 truncate font-mono text-[11px] font-bold tracking-tight text-[#f5f4ed]">
|
||||
{zone.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Work Items */}
|
||||
{workItems.map((item, index) => (
|
||||
<div
|
||||
key={item.work_item_id}
|
||||
data-testid="openclaw-work-token"
|
||||
className={cn(
|
||||
"openclaw-work-token absolute h-8 w-24 -translate-x-1/2 -translate-y-1/2 rounded-full border px-3 py-1 text-[10px] font-semibold shadow-[0_8px_20px_rgba(54,72,88,0.12)] flex items-center justify-center",
|
||||
"openclaw-work-token absolute h-8 w-28 -translate-x-1/2 -translate-y-1/2 rounded-full border px-3 py-1 text-[10px] font-semibold shadow-[0_8px_20px_rgba(0,0,0,0.5)] flex items-center justify-center transition-all",
|
||||
stateClass(stateFromWorkItemStatus(item.status)),
|
||||
)}
|
||||
style={{
|
||||
@@ -320,11 +337,12 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Agents */}
|
||||
{agents.map((agent, index) => (
|
||||
<div
|
||||
key={agent.agent_id}
|
||||
data-testid="openclaw-agent-avatar"
|
||||
className="openclaw-agent absolute -translate-x-1/2 -translate-y-1/2 flex flex-col items-center"
|
||||
className="openclaw-agent absolute -translate-x-1/2 -translate-y-1/2 flex flex-col items-center group cursor-default"
|
||||
style={{
|
||||
left: `${agent.position.x}%`,
|
||||
top: `${agent.position.y}%`,
|
||||
@@ -333,87 +351,96 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
>
|
||||
<div className="relative">
|
||||
{agent.state === "working" && (
|
||||
<div className="openclaw-working-ripple absolute inset-0 rounded-full border-2 border-[#4a90d9]" />
|
||||
<div className="openclaw-working-ripple absolute inset-0 rounded-full border-2 border-[#4a90d9]/80 shadow-[0_0_20px_rgba(74,144,217,0.6)]" />
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-10 flex h-14 w-14 items-center justify-center rounded-full border-2 shadow-[0_16px_30px_rgba(54,72,88,0.15)] transition-all duration-300",
|
||||
"relative z-10 flex h-14 w-14 items-center justify-center rounded-full border-2 shadow-[0_16px_30px_rgba(0,0,0,0.4)] transition-all duration-300 group-hover:scale-110",
|
||||
stateClass(agent.state),
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -right-0 -top-0 h-3.5 w-3.5 rounded-full border-2 border-white",
|
||||
"absolute -right-0 -top-0 h-3.5 w-3.5 rounded-full border-2 border-[#1c1c1a]",
|
||||
agent.state === "blocked"
|
||||
? "bg-[#d95f4f] animate-pulse"
|
||||
? "bg-[#d95f4f] animate-pulse shadow-[0_0_8px_rgba(217,95,79,0.8)]"
|
||||
: agent.state === "verified"
|
||||
? "bg-[#4f9b62]"
|
||||
? "bg-[#4f9b62] shadow-[0_0_8px_rgba(79,155,98,0.8)]"
|
||||
: agent.state === "working"
|
||||
? "bg-[#4a90d9] animate-pulse"
|
||||
: "bg-[#f0bd69]",
|
||||
? "bg-[#4a90d9] animate-pulse shadow-[0_0_8px_rgba(74,144,217,0.8)]"
|
||||
: "bg-[#f0bd69] shadow-[0_0_8px_rgba(240,189,105,0.8)]",
|
||||
)}
|
||||
/>
|
||||
<Bot className={cn("h-6 w-6", agent.state === "working" ? "animate-bounce-slow text-[#4a90d9]" : "")} aria-hidden="true" />
|
||||
<Bot className={cn("h-6 w-6", agent.state === "working" ? "animate-bounce-slow text-[#60a5fa]" : "")} aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 max-w-32 truncate rounded-md border border-white/50 bg-white/70 px-2 py-1 text-center font-mono text-[10px] font-semibold text-[#34302a] shadow-sm backdrop-blur-[10px]">
|
||||
<div className="mt-3 max-w-[140px] truncate rounded-lg border border-white/10 bg-[#1c1c1a]/80 px-2.5 py-1 text-center font-mono text-[10px] font-semibold text-[#f5f4ed] shadow-lg backdrop-blur-xl transition-all group-hover:-translate-y-1 group-hover:border-white/30">
|
||||
{agent.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(145deg,rgba(255,255,255,0.18),transparent_45%,rgba(20,20,19,0.04))]" />
|
||||
<div className="absolute bottom-3 left-3 flex flex-wrap gap-2">
|
||||
<span className="border border-[#d8d3c7] bg-white/90 px-2 py-1 font-mono text-[11px] text-[#5f5b52]">
|
||||
{t("source.marker")} {shortValue(scene?.source.deploy_readback_marker)}
|
||||
{/* Lighting overlay */}
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(145deg,rgba(255,255,255,0.03),transparent_45%,rgba(0,0,0,0.4))]" />
|
||||
|
||||
{/* Footer Badges */}
|
||||
<div className="absolute bottom-4 left-4 flex flex-wrap gap-2">
|
||||
<span className="rounded-md border border-white/10 bg-[#1c1c1a]/80 px-2.5 py-1 font-mono text-[11px] text-[#a3a097] backdrop-blur-md">
|
||||
{t("source.marker")} <span className="text-[#f5f4ed]">{shortValue(scene?.source.deploy_readback_marker)}</span>
|
||||
</span>
|
||||
<span
|
||||
data-testid="openclaw-animation-loop"
|
||||
className={cn(
|
||||
"border px-2 py-1 font-mono text-[11px]",
|
||||
"rounded-md border px-2.5 py-1 font-mono text-[11px] backdrop-blur-md",
|
||||
animationLoopEnabled
|
||||
? "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]"
|
||||
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
? "border-[#4f9b62]/30 bg-[#4f9b62]/10 text-[#8fc29a]"
|
||||
: "border-[#d9b36f]/30 bg-[#d9b36f]/10 text-[#fcd34d]",
|
||||
)}
|
||||
>
|
||||
{t("animation.loop")}{" "}
|
||||
{animationLoopEnabled ? t("animation.on") : t("animation.off")} ·{" "}
|
||||
{scene?.room.animation_loop.tick_ms ?? 0}ms
|
||||
<span className={cn("font-bold", animationLoopEnabled ? "text-[#4f9b62]" : "text-[#d9b36f]")}>
|
||||
{animationLoopEnabled ? t("animation.on") : t("animation.off")}
|
||||
</span>{" "}
|
||||
· {scene?.room.animation_loop.tick_ms ?? 0}ms
|
||||
</span>
|
||||
<span className="border border-[#d8d3c7] bg-white/90 px-2 py-1 font-mono text-[11px] text-[#5f5b52]">
|
||||
<span className="rounded-md border border-white/10 bg-[#1c1c1a]/80 px-2.5 py-1 font-mono text-[11px] text-[#a3a097] backdrop-blur-md">
|
||||
{t("source.updated")}{" "}
|
||||
{updatedAt
|
||||
? updatedAt.toLocaleTimeString(locale, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
: "--"}
|
||||
<span className="text-[#f5f4ed]">
|
||||
{updatedAt
|
||||
? updatedAt.toLocaleTimeString(locale, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
: "--"}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="grid min-w-0 gap-3">
|
||||
<section className="border border-[#d8d3c7] bg-white p-4">
|
||||
{/* Sidebar Panels */}
|
||||
<aside className="grid min-w-0 gap-4">
|
||||
{/* Rollups (Stat Cards) */}
|
||||
<section className="rounded-2xl border border-white/10 bg-[#1c1c1a]/50 p-5 shadow-xl backdrop-blur-2xl">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="text-base font-semibold text-[#141413]">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-[#f5f4ed]">
|
||||
{t("panels.rollups")}
|
||||
</h2>
|
||||
<Activity className="h-4 w-4 text-[#4a90d9]" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<div className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
{[
|
||||
["agents", scene?.rollups.agent_count],
|
||||
["workItems", scene?.rollups.work_item_count],
|
||||
["animated", scene?.rollups.animated_entity_count],
|
||||
["blocked", scene?.rollups.blocked_work_item_count],
|
||||
].map(([key, value]) => (
|
||||
<div key={String(key)} className="border border-[#e5e1d8] bg-[#fafafa] p-3">
|
||||
<p className="text-[11px] font-semibold text-[#77736a]">
|
||||
<div key={String(key)} className="group rounded-xl border border-white/5 bg-white/5 p-4 transition-all hover:border-white/20 hover:bg-white/10">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-widest text-[#a3a097] group-hover:text-[#d8d3c7]">
|
||||
{t(`metrics.${key}`)}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-2xl font-semibold text-[#141413]">
|
||||
<p className="mt-2 font-mono text-3xl font-bold tracking-tight text-[#f5f4ed]">
|
||||
{formatNumber(Number(value ?? 0))}
|
||||
</p>
|
||||
</div>
|
||||
@@ -421,17 +448,18 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border border-[#d8d3c7] bg-white p-4">
|
||||
{/* Boundaries */}
|
||||
<section className="rounded-2xl border border-white/10 bg-[#1c1c1a]/50 p-5 shadow-xl backdrop-blur-2xl">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="text-base font-semibold text-[#141413]">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-[#f5f4ed]">
|
||||
{t("panels.boundaries")}
|
||||
</h2>
|
||||
<ShieldCheck
|
||||
className={cn("h-4 w-4", boundaryOk ? "text-[#17602a]" : "text-[#8a5a08]")}
|
||||
className={cn("h-4 w-4", boundaryOk ? "text-[#4f9b62]" : "text-[#d9b36f]")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2">
|
||||
<div className="mt-4 grid gap-2">
|
||||
{[
|
||||
"raw_session_read_allowed",
|
||||
"sqlite_read_allowed",
|
||||
@@ -441,17 +469,17 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
].map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="flex min-w-0 items-center justify-between gap-3 border border-[#e5e1d8] px-3 py-2"
|
||||
className="group flex min-w-0 items-center justify-between gap-3 rounded-lg border border-white/5 bg-white/5 px-3 py-2.5 transition-all hover:bg-white/10"
|
||||
>
|
||||
<span className="truncate font-mono text-[11px] text-[#5f5b52]">
|
||||
<span className="truncate font-mono text-[10px] text-[#a3a097] group-hover:text-[#d8d3c7]">
|
||||
{key}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"border px-2 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-full border px-2 py-0.5 text-[9px] font-bold uppercase tracking-widest",
|
||||
scene?.boundaries[key] === false
|
||||
? "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]"
|
||||
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
? "border-[#4f9b62]/30 bg-[#4f9b62]/10 text-[#8fc29a]"
|
||||
: "border-[#d9b36f]/30 bg-[#d9b36f]/10 text-[#fcd34d]",
|
||||
)}
|
||||
>
|
||||
{scene?.boundaries[key] === false ? t("boundary.closed") : t("boundary.open")}
|
||||
@@ -461,35 +489,38 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="min-h-[260px] border border-[#d8d3c7] bg-white p-4">
|
||||
{/* Work Items Detail */}
|
||||
<section className="min-h-[260px] rounded-2xl border border-white/10 bg-[#1c1c1a]/50 p-5 shadow-xl backdrop-blur-2xl">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="text-base font-semibold text-[#141413]">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-[#f5f4ed]">
|
||||
{t("panels.workItems")}
|
||||
</h2>
|
||||
<SearchCheck className="h-4 w-4 text-[#4a90d9]" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2">
|
||||
<div className="mt-4 grid gap-3">
|
||||
{workItems.length === 0 ? (
|
||||
<div className="border border-[#e5e1d8] bg-[#fafafa] p-4 text-sm text-[#77736a]">
|
||||
{t("empty")}
|
||||
<div className="flex flex-col items-center justify-center rounded-xl border border-white/5 bg-white/5 py-8 text-[#a3a097]">
|
||||
<SearchCheck className="mb-2 h-6 w-6 opacity-50" />
|
||||
<span className="text-xs font-medium">{t("empty")}</span>
|
||||
</div>
|
||||
) : (
|
||||
workItems.slice(0, 6).map((item) => (
|
||||
<div key={item.work_item_id} className="border border-[#e5e1d8] p-3">
|
||||
<div key={item.work_item_id} className="group rounded-xl border border-white/5 bg-white/5 p-3.5 transition-all hover:-translate-y-0.5 hover:border-white/20 hover:bg-white/10 hover:shadow-lg">
|
||||
<div className="flex min-w-0 items-center justify-between gap-2">
|
||||
<span className="min-w-0 truncate text-sm font-semibold text-[#141413]">
|
||||
<span className="min-w-0 truncate text-sm font-bold text-[#f5f4ed]">
|
||||
{item.title || item.work_item_id}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 border px-2 py-0.5 text-[10px] font-semibold",
|
||||
"shrink-0 rounded-full border px-2 py-0.5 text-[9px] font-bold uppercase tracking-widest",
|
||||
stateClass(stateFromWorkItemStatus(item.status)),
|
||||
)}
|
||||
>
|
||||
{stateLabel(t, stateFromWorkItemStatus(item.status))}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 truncate font-mono text-[11px] text-[#77736a]">
|
||||
<p className="mt-2 truncate font-mono text-[10px] text-[#a3a097] group-hover:text-[#d8d3c7]">
|
||||
<span className="mr-1 text-[#d97757]">❯</span>
|
||||
{item.next_controlled_action || item.exit_criteria || "--"}
|
||||
</p>
|
||||
</div>
|
||||
@@ -551,7 +582,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
opacity: 0.42;
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(160%);
|
||||
@@ -564,7 +595,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2.4);
|
||||
transform: scale(2.2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -592,7 +623,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(112, 214, 164, 0.3),
|
||||
rgba(74, 144, 217, 0.15),
|
||||
transparent
|
||||
);
|
||||
animation: openclaw-screen-sweep 3.8s linear infinite;
|
||||
|
||||
Reference in New Issue
Block a user