Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,11 @@ a.hero-status:hover .arrow {
animation: fleet-pulse 2.4s ease-in-out infinite;
}

.fleet-pulse-stale {
background: color-mix(in srgb, var(--paper) 45%, transparent);
border: none;
}

@keyframes fleet-pulse {
0%,
100% {
Expand Down
37 changes: 34 additions & 3 deletions components/site/FleetStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ const stats: Stats = {
amount: "500000",
epoch: 7,
},
{
txHash: "0xdef",
logIndex: 0,
blockNumber: 90,
timestamp: 1_790_000_000 - 24 * 60 * 60,
operator: "0x00000000000000000000000000000000000000a4",
bytesDelivered: "1000000000",
amount: "500000",
epoch: 6,
},
],
nodes: {
"0x01": {
Expand All @@ -65,6 +75,10 @@ const stats: Stats = {
operator: "0x00000000000000000000000000000000000000a3",
region: "unknown",
},
"0x04": {
operator: "0x00000000000000000000000000000000000000a4",
region: "US",
},
},
};

Expand Down Expand Up @@ -108,14 +122,31 @@ describe("FleetPanel", () => {

it("renders one row per registered node and the 24h aggregates", () => {
const { text } = panel({ status: "ok", stats, fetchedAt });
expect(text).toContain("3nodes registered");
expect(text).toContain("4nodes registered");
expect(text).toContain("de0x0000…00a1 · settled 4 min ago");
expect(text).toContain("de0x0000…00a2 · idle");
expect(text).toContain("n/a0x0000…00a3 · idle");
expect(text).toContain("us0x0000…00a4 · settled 24 h ago");
expect(text).toContain("de0x0000…00a2n/a0x0000…00a3Σ served");
expect(text).not.toContain("idle");
expect(text).toContain("$1.50");
expect(text).not.toContain("catching up");
});

// A node pulses while its newest settlement is inside the 24h window, greys
// out once it falls outside, and stays an empty ring with none on file.
it("pulses recent nodes and greys out stale ones", () => {
const { tree } = panel({ status: "ok", stats, fetchedAt });
const pulses = findAll(tree, "span")
.map((el) => attrs(el).className)
.filter((c): c is string => typeof c === "string")
.filter((c) => c.startsWith("fleet-pulse"));
expect(pulses).toEqual([
"fleet-pulse fleet-pulse-active pulse-0",
"fleet-pulse fleet-pulse-stale",
"fleet-pulse",
"fleet-pulse",
]);
});

it("flags a backfilling index", () => {
const { text } = panel({
status: "ok",
Expand Down
21 changes: 12 additions & 9 deletions components/site/FleetStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PANEL_CAPTIONS } from "@/lib/copy";
import {
FLEET_WINDOW_HOURS,
fleetView,
settledInWindow,
type StatsResult,
timeAgo,
} from "@/lib/stats";
Expand Down Expand Up @@ -76,19 +77,21 @@ export function FleetPanel({
<span className="fleet-code">{n.region}</span>
<span
className={
n.lastSettled !== null
? `fleet-pulse fleet-pulse-active pulse-${i % 6}`
: "fleet-pulse"
result.status !== "ok" || n.lastSettled === null
? "fleet-pulse"
: settledInWindow(n.lastSettled, result.fetchedAt)
? `fleet-pulse fleet-pulse-active pulse-${i % 6}`
: "fleet-pulse fleet-pulse-stale"
}
/>
<span className="fleet-rate">
{n.operator}
<span className="fleet-dim">
{" · "}
{n.lastSettled !== null && result.status === "ok"
? `settled ${timeAgo(n.lastSettled, result.fetchedAt)}`
: "idle"}
</span>
{n.lastSettled !== null && result.status === "ok" && (
<span className="fleet-dim">
{" · settled "}
{timeAgo(n.lastSettled, result.fetchedAt)}
</span>
)}
</span>
</div>
))
Expand Down
14 changes: 14 additions & 0 deletions lib/stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
formatBytes,
formatUsdc,
loadStats,
settledInWindow,
type Stats,
STATS_URL,
TERMINAL_SESSIONS,
Expand Down Expand Up @@ -122,6 +123,19 @@ describe("timeAgo", () => {
});
});

describe("settledInWindow", () => {
const now = 1_790_000_000_000;
it.each([
[1_790_000_000 - 4 * 60, true],
[1_790_000_000 - 24 * 3600 + 1, true],
[1_790_000_000 - 24 * 3600, false],
[1_790_000_000 - 3 * 86_400, false],
[null, false],
])("%s → %s", (lastSettled, inWindow) => {
expect(settledInWindow(lastSettled, now)).toBe(inWindow);
});
});

describe("terminalSessions", () => {
const stats: Stats = {
...base,
Expand Down
12 changes: 12 additions & 0 deletions lib/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ export type FleetView = {
caughtUp: boolean;
};

/** Whether a node's newest settlement falls inside the fleet window as of
* `now` (ms): its row pulses while this holds and greys out after. */
export function settledInWindow(
lastSettled: number | null,
now: number,
): boolean {
return (
lastSettled !== null &&
now / 1000 - lastSettled < FLEET_WINDOW_HOURS * 60 * 60
);
}

/** Each value as a fraction of the series max; all zeros stay zeros. */
function levels(values: string[]): number[] {
const big = values.map((v) => BigInt(v));
Expand Down
Loading