From f8daef6ee400b0be522e5cfcf39a2d2716f62cd6 Mon Sep 17 00:00:00 2001 From: null Date: Wed, 27 May 2026 19:22:55 -0500 Subject: [PATCH] animation --- frontend/src/app/dashboard/page.tsx | 8 ++ .../dashboard/DashboardInfoBlock.tsx | 73 ++++++++++++------- .../dashboard/DashboardMetricCard.tsx | 18 +++-- .../components/dashboard/GatewayCronPanel.tsx | 22 +++++- .../dashboard/GatewayHealthPanel.tsx | 18 ++++- .../dashboard/PendingApprovalsSection.tsx | 18 ++++- .../dashboard/RecentActivitySection.tsx | 30 +++++++- .../dashboard/RuntimeUsageSection.tsx | 45 +++++++++++- .../components/dashboard/SessionsSection.tsx | 23 +++++- 9 files changed, 218 insertions(+), 37 deletions(-) diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index 1cdcb4f..c84a0e9 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -1413,6 +1413,7 @@ export default function DashboardPage() { secondary={`${formatCount(agents.length)} total`} icon={} tone="accent" + isLoading={!metrics && metricsQuery.isLoading} /> } tone="success" + isLoading={!metrics && metricsQuery.isLoading} /> } tone="warning" + isLoading={!metrics && metricsQuery.isLoading} /> } tone="success" + isLoading={!metrics && metricsQuery.isLoading} /> @@ -1491,12 +1495,14 @@ export default function DashboardPage() { title="Workload" rows={workloadRows} tone="accent" + isLoading={!metrics && metricsQuery.isLoading} /> @@ -1552,6 +1559,7 @@ export default function DashboardPage() {
- {rows.map((row) => ( -
- - - {row.label} - - - {row.value} - -
- ))} + {isLoading + ? [28, 20, 36, 24, 32].map((w, i) => ( +
+ + +
+ +
+
+ )) + : rows.map((row) => ( +
+ + + {row.label} + + + {row.value} + +
+ ))}
); diff --git a/frontend/src/components/dashboard/DashboardMetricCard.tsx b/frontend/src/components/dashboard/DashboardMetricCard.tsx index e9b78a6..bb595e1 100644 --- a/frontend/src/components/dashboard/DashboardMetricCard.tsx +++ b/frontend/src/components/dashboard/DashboardMetricCard.tsx @@ -9,6 +9,7 @@ interface DashboardMetricCardProps { infoText?: string; icon: ReactNode; tone: MetricToneKey; + isLoading?: boolean; } /** @@ -22,6 +23,7 @@ export function DashboardMetricCard({ infoText, icon, tone, + isLoading = false, }: DashboardMetricCardProps) { return (
-

- {value} -

- {secondary && ( -

{secondary}

+ {isLoading ? ( +
+ ) : ( +

+ {value} +

)} + {isLoading ? ( +
+ ) : secondary ? ( +

{secondary}

+ ) : null}
{icon}
diff --git a/frontend/src/components/dashboard/GatewayCronPanel.tsx b/frontend/src/components/dashboard/GatewayCronPanel.tsx index 5e46d73..7688356 100644 --- a/frontend/src/components/dashboard/GatewayCronPanel.tsx +++ b/frontend/src/components/dashboard/GatewayCronPanel.tsx @@ -56,7 +56,27 @@ export function GatewayCronPanel({ action={jobs.length > 0 ? { label: `${jobs.length}`, href: "#" } : undefined} > {isLoading && !cron ? ( - +
+ {[0, 1].map((i) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
) : jobs.length === 0 ? ( ) : ( diff --git a/frontend/src/components/dashboard/GatewayHealthPanel.tsx b/frontend/src/components/dashboard/GatewayHealthPanel.tsx index 7aa9b51..0c8d5ca 100644 --- a/frontend/src/components/dashboard/GatewayHealthPanel.tsx +++ b/frontend/src/components/dashboard/GatewayHealthPanel.tsx @@ -63,7 +63,23 @@ export function GatewayHealthPanel({ return ( {isLoading && !health ? ( - +
+ {["CPU", "Memory", "Disk"].map((label) => ( +
+
+
+
+
+
+
+
+
+ ))} +
+
+
+
+
) : !health || (!c?.cpu_pct && !c?.memory_pct && !c?.uptime_seconds) ? ( ) : ( diff --git a/frontend/src/components/dashboard/PendingApprovalsSection.tsx b/frontend/src/components/dashboard/PendingApprovalsSection.tsx index cedb4b7..a55cf45 100644 --- a/frontend/src/components/dashboard/PendingApprovalsSection.tsx +++ b/frontend/src/components/dashboard/PendingApprovalsSection.tsx @@ -35,7 +35,23 @@ export function PendingApprovalsSection({ tone={items.length > 0 ? "warning" : "success"} > {isLoading ? ( - +
+ {[0, 1, 2].map((i) => ( +
+ + + + + + + + +
+ ))} +
) : hasError ? ( , href: string) => void; onRowKeyDown: (e: KeyboardEvent, href: string) => void; buildHref: (event: ActivityEvent) => string; @@ -51,6 +52,7 @@ const eventTone = (eventType: string) => { export function RecentActivitySection({ events, feedHref, + isLoading = false, onRowClick, onRowKeyDown, buildHref, @@ -64,7 +66,33 @@ export function RecentActivitySection({ tone="accent" >
- {events.length > 0 ? ( + {isLoading && events.length === 0 ? ( + <> + {[60, 80, 48, 72].map((titleW, i) => ( +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ ))} + + ) : events.length > 0 ? ( events.map((event) => { const href = buildHref(event); const tone = eventTone(event.event_type); diff --git a/frontend/src/components/dashboard/RuntimeUsageSection.tsx b/frontend/src/components/dashboard/RuntimeUsageSection.tsx index 283c46c..67cf1e2 100644 --- a/frontend/src/components/dashboard/RuntimeUsageSection.tsx +++ b/frontend/src/components/dashboard/RuntimeUsageSection.tsx @@ -413,7 +413,50 @@ export function RuntimeUsageSection({ return ( {isLoading && !usage ? ( - +
+ {/* Provider windows skeleton */} +
+
+
+ {[0, 1].map((i) => ( +
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ {/* Stat cards skeleton */} +
+ {[ + { w: "w-16" }, + { w: "w-14" }, + { w: "w-20" }, + { w: "w-12" }, + ].map(({ w }, i) => ( +
+
+
+
+
+ ))} +
+
) : noData ? ( ) : hasRuntimeData || providerRows.length > 0 || perGatewayUsage.length > 0 ? ( diff --git a/frontend/src/components/dashboard/SessionsSection.tsx b/frontend/src/components/dashboard/SessionsSection.tsx index e133666..4f54c0a 100644 --- a/frontend/src/components/dashboard/SessionsSection.tsx +++ b/frontend/src/components/dashboard/SessionsSection.tsx @@ -47,7 +47,28 @@ export function SessionsSection({ {!hasConfiguredGateways ? ( ) : isLoading ? ( - +
+ {[0, 1].map((i) => ( +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ ))} +
) : sessions.length > 0 ? ( <> {gatewayUnavailableCount > 0 && (