Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# VITE_MAP_ZOOM=3.2

# Hide tabs completely (comma list, case-insensitive). Unset = show all.
# Options: Packets,Channels,Map,Nodes,Observers,Routes,Traces,Analytics
# Options: Packets,Channels,Map,Nodes,Observers,Routes,Traces,Analytics,MyAtlas
# VITE_DISABLED_TABS=Map,Traces

# Only offer these themes in the picker (comma list of theme ids). Unset = the default set.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: CI

on:
push:
branches: [main, dev]
branches: [main, dev, dev1]
pull_request:

jobs:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build and Publish Docker Image

on:
push:
branches: [main, dev]
branches: [main, dev, dev1]
tags: ["v*"]

env:
Expand Down Expand Up @@ -42,6 +42,7 @@ jobs:
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }}
type=raw,value=dev1,enable=${{ github.ref == 'refs/heads/dev1' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "beacon-web",
"private": true,
"version": "2.0.5",
"version": "2.0.5-dev1.1",
"license": "AGPL-3.0-or-later",
"type": "module",
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ import { shouldRetryQuery } from "./api/rate-limit";
import { WS_URL, ENABLED_TABS } from "./lib/constants";
import type { PacketDetail } from "./types/api";

// Map is the only heavy tab (maplibre-gl is ~1MB), so lazy-load it — its chunk is fetched the
// first time someone opens the Map tab instead of bloating the initial bundle.
// Map (maplibre-gl, ~1MB) and My Atlas are lazy-loaded so their chunks are fetched on first visit
// instead of bloating the initial bundle.
const MyAtlasPage = lazy(() => import("./features/atlas/MyAtlasPage").then(m => ({ default: m.MyAtlasPage })));
const MapView = lazy(() => import("./features/map/MapView").then((m) => ({ default: m.MapView })));

// Stats pulls in ECharts (~150-200KB gz), so lazy-load it too — the chunk loads on first visit to Stats.
Expand Down Expand Up @@ -302,6 +303,7 @@ function AppInner() {
}, []);

const tabContent: Record<string, React.ReactNode> = {
MyAtlas: <MyAtlasPage onViewNode={viewNode} onViewObserver={viewObserver} onAnalyzePacket={viewPacket} />,
Packets: (
<PacketList
wsManager={wsManager}
Expand Down
14 changes: 8 additions & 6 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ export function getPackets(
});
}

export function getPacketDetail(packetHash: string): Promise<PacketDetail> {
return request(`/packets/${packetHash}`);
export function getPacketDetail(packetHash: string, signal?: AbortSignal): Promise<PacketDetail> {
return request(`/packets/${packetHash}`, undefined, signal);
}

// /info: the oldest client versions this server accepts (null = no requirement) and its own version.
Expand Down Expand Up @@ -302,6 +302,7 @@ export function getNodesPage(
supportsMultibyteTraces?: "true" | "false";
neighbors?: boolean; // include each node's neighborIds (?neighbors=true)
},
signal?: AbortSignal,
): Promise<CursorPage<NodeSummary>> {
if (noIatas(iatas)) return Promise.resolve(emptyPage());
return request("/nodes", {
Expand All @@ -314,7 +315,7 @@ export function getNodesPage(
supportsMultibytePaths: params?.supportsMultibytePaths,
supportsMultibyteTraces: params?.supportsMultibyteTraces,
neighbors: params?.neighbors ? "true" : undefined,
});
}, signal);
}

export interface ObserverDirectoryRequest {
Expand Down Expand Up @@ -374,18 +375,19 @@ export function getObserversPage(
});
}

export function getNode(nodeId: string): Promise<Node> {
return request(`/nodes/${nodeId}`);
export function getNode(nodeId: string, signal?: AbortSignal): Promise<Node> {
return request(`/nodes/${nodeId}`, undefined, signal);
}

export function getNodeObservations(
nodeId: string,
params?: { cursor?: number; limit?: number },
signal?: AbortSignal,
): Promise<CursorPage<NodeObservation>> {
return request(`/nodes/${nodeId}/observations`, {
cursor: params?.cursor,
limit: params?.limit ?? DEFAULT_PAGE_SIZE,
});
}, signal);
}

export function getNodeNeighbors(nodeId: string): Promise<NodeNeighbor[]> {
Expand Down
4 changes: 4 additions & 0 deletions src/api/rate-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export function shouldRetryQuery(failureCount: number, error: unknown): boolean
return true;
}

// TanStack Query retry delay: wait out a Retry-After when the server sent one, otherwise back off.
export const queryRetryDelay = (attempt: number, error: Error) =>
(error as { retryAfterMs?: number }).retryAfterMs ?? Math.min(1000 * 2 ** attempt, 30_000);

// App-wide "the API is throttling us" flag: fed by the fetch wrapper, read by the header badge.

let limitedUntil: number | null = null;
Expand Down
5 changes: 3 additions & 2 deletions src/components/BottomNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { touchClick } from "../lib/touch-click";

// Mobile-only tab bar (hidden at md+); overflow tabs live behind "More" in a bottom sheet.
const PRIMARY_TABS = ["Packets", "Channels", "Map", "Nodes"] as const;
const OVERFLOW_TABS = ["Observers", "Routes", "Traces", "Analytics"] as const;

const OVERFLOW_TABS = ["Observers", "Routes", "Traces", "Analytics", "MyAtlas"] as const;
// inline SVGs, 20px / 1.6 stroke to match the rest of the icons
function Icon({ name }: { name: string }) {
const common = { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round" as const, strokeLinejoin: "round" as const };
switch (name) {
case "MyAtlas":
return <svg {...common}><circle cx="12" cy="12" r="9" /><path d="m16 8-2.5 5.5L8 16l2.5-5.5L16 8z" /></svg>;
case "Packets":
return (
<svg {...common}>
Expand Down
Loading
Loading