From 8786ef3d0a1b0639e4ffd13e590fc9335e9cb01d Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Mon, 13 Jul 2026 11:14:34 -0400 Subject: [PATCH] Add .env options for hiding tabs, MeshMapper themes, app name and a GitHub link --- .build/Dockerfile | 3 + .build/docker-entrypoint.sh | 8 +++ .env.example | 11 ++++ docker/.env.example | 11 ++++ docker/docker-compose.yml | 4 ++ public/themes.json | 44 +++++++++++++++ src/App.tsx | 4 +- src/components/AppShell.tsx | 31 ++++++---- src/components/BeaconWordmark.tsx | 7 ++- src/components/BottomNav.tsx | 36 +++++++----- src/components/CloseButton.tsx | 2 +- src/components/DetailPanel.tsx | 2 +- src/components/MultiSelectDropdown.tsx | 2 +- src/components/SearchBar.tsx | 2 +- src/components/SelectDropdown.tsx | 4 +- src/features/channels/MessagePanel.tsx | 2 +- src/features/map/SegmentedControl.tsx | 2 +- src/features/nodes/NodeDetailPanel.tsx | 4 +- .../observers/ObserverDetailPanel.tsx | 2 +- src/features/packets/ObservationCard.tsx | 2 +- src/features/stats/ObserverTab.tsx | 2 +- src/lib/constants.ts | 32 +++++++++++ src/lib/themes.ts | 2 + tests/lib/constants.test.ts | 56 +++++++++++++++++++ 24 files changed, 233 insertions(+), 42 deletions(-) create mode 100644 tests/lib/constants.test.ts diff --git a/.build/Dockerfile b/.build/Dockerfile index ea6449b..1806c4c 100644 --- a/.build/Dockerfile +++ b/.build/Dockerfile @@ -7,6 +7,9 @@ ENV VITE_API_BASE=__VITE_API_BASE__ ENV VITE_WS_URL=__VITE_WS_URL__ ENV VITE_MAP_CENTER=__VITE_MAP_CENTER__ ENV VITE_MAP_ZOOM=__VITE_MAP_ZOOM__ +ENV VITE_DISABLED_TABS=__VITE_DISABLED_TABS__ +ENV VITE_ENABLED_THEMES=__VITE_ENABLED_THEMES__ +ENV VITE_APP_NAME=__VITE_APP_NAME__ RUN npm run build FROM caddy:2-alpine diff --git a/.build/docker-entrypoint.sh b/.build/docker-entrypoint.sh index b844b89..4cf2325 100644 --- a/.build/docker-entrypoint.sh +++ b/.build/docker-entrypoint.sh @@ -6,12 +6,20 @@ WS_URL="${VITE_WS_URL:-ws://localhost:8080/ws}" # Optional map view — leave empty when unset; the app falls back to a world overview. MAP_CENTER="${VITE_MAP_CENTER:-}" MAP_ZOOM="${VITE_MAP_ZOOM:-}" +# Optional branding/customization — empty means default behaviour (all tabs, hidden themes hidden). +DISABLED_TABS="${VITE_DISABLED_TABS:-}" +ENABLED_THEMES="${VITE_ENABLED_THEMES:-}" +# APP_NAME is substituted verbatim; avoid '&' and '|' (sed replacement metachar and delimiter). +APP_NAME="${VITE_APP_NAME:-BEACON}" find /srv -name '*.js' -exec sed -i \ -e "s|__VITE_API_BASE__|${API_BASE}|g" \ -e "s|__VITE_WS_URL__|${WS_URL}|g" \ -e "s|__VITE_MAP_CENTER__|${MAP_CENTER}|g" \ -e "s|__VITE_MAP_ZOOM__|${MAP_ZOOM}|g" \ + -e "s|__VITE_DISABLED_TABS__|${DISABLED_TABS}|g" \ + -e "s|__VITE_ENABLED_THEMES__|${ENABLED_THEMES}|g" \ + -e "s|__VITE_APP_NAME__|${APP_NAME}|g" \ {} + exec "$@" diff --git a/.env.example b/.env.example index adc707a..69075ed 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,14 @@ # selection has no airport coords; otherwise the map fits bounds over the airports. Unset = world. # VITE_MAP_CENTER=52.5,-96.8 # VITE_MAP_ZOOM=3.2 + +# Hide tabs completely (comma list, case-insensitive). Unset = show all. +# Options: Packets,Channels,Map,Nodes,Observers,Routes,Traces,Analytics +# VITE_DISABLED_TABS=Map,Traces + +# Reveal normally-hidden themes in the picker (comma list of theme ids). Unset = none. +# Options: meshmapper_dark,meshmapper_light +# VITE_ENABLED_THEMES=meshmapper_dark,meshmapper_light + +# Custom top-left app name (default: BEACON). Avoid the & and | characters. +# VITE_APP_NAME=Beacon - MeshMapper diff --git a/docker/.env.example b/docker/.env.example index 732972e..32f0f0a 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -11,3 +11,14 @@ VITE_WS_URL=ws://localhost:8080/ws # decimal "lat,lon", VITE_MAP_ZOOM is 0-22. The example below is a Canada-wide view. VITE_MAP_CENTER=52.5,-96.8 VITE_MAP_ZOOM=3.2 + +# Hide tabs completely (comma list, case-insensitive). Leave empty to show all. +# Options: Packets,Channels,Map,Nodes,Observers,Routes,Traces,Analytics +VITE_DISABLED_TABS= + +# Reveal normally-hidden themes in the picker (comma list of theme ids). Leave empty for none. +# Options: meshmapper_dark,meshmapper_light +VITE_ENABLED_THEMES= + +# Custom app name shown in the top-left wordmark. Default: BEACON. Avoid the & and | characters. +VITE_APP_NAME= diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f449012..840b255 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -20,4 +20,8 @@ services: # Optional "All" map view; unset = world overview - VITE_MAP_CENTER=${VITE_MAP_CENTER:-} - VITE_MAP_ZOOM=${VITE_MAP_ZOOM:-} + # Optional branding/customization; unset = all tabs, hidden themes hidden, name "BEACON" + - VITE_DISABLED_TABS=${VITE_DISABLED_TABS:-} + - VITE_ENABLED_THEMES=${VITE_ENABLED_THEMES:-} + - VITE_APP_NAME=${VITE_APP_NAME:-BEACON} restart: unless-stopped diff --git a/public/themes.json b/public/themes.json index 29a089c..21a161f 100644 --- a/public/themes.json +++ b/public/themes.json @@ -334,5 +334,49 @@ "--palette-text-muted": "#6B8A8A", "--palette-text-dim": "#4D6A6A" } + }, + { + "id": "meshmapper_dark", + "name": "MeshMapper Dark", + "hidden": true, + "vars": { + "--palette-bg-base": "#121212", + "--palette-bg-surface": "#1E1E1E", + "--palette-bg-raised": "#2A2A2A", + "--palette-border": "#333333", + "--palette-border-subtle": "#242424", + "--palette-primary": "#00D2FF", + "--palette-primary-dim": "#00A5C8", + "--palette-secondary": "#4A9EFF", + "--palette-green": "#28A745", + "--palette-danger": "#DC3545", + "--palette-warn": "#FFC107", + "--palette-text-bright": "#FFFFFF", + "--palette-text-normal": "#E0E0E0", + "--palette-text-muted": "#AAAAAA", + "--palette-text-dim": "#777777" + } + }, + { + "id": "meshmapper_light", + "name": "MeshMapper Light", + "hidden": true, + "vars": { + "--palette-bg-base": "#F4F4F9", + "--palette-bg-surface": "#FFFFFF", + "--palette-bg-raised": "#FFFFFF", + "--palette-border": "#DDDDDD", + "--palette-border-subtle": "#EEEEEE", + "--palette-primary": "#007A94", + "--palette-primary-dim": "#005F73", + "--palette-secondary": "#0062CC", + "--palette-green": "#1E7E34", + "--palette-danger": "#C82333", + "--palette-warn": "#9A6A00", + "--palette-text-bright": "#1A1A1A", + "--palette-text-normal": "#333333", + "--palette-text-muted": "#666666", + "--palette-text-dim": "#767676" + } } ] diff --git a/src/App.tsx b/src/App.tsx index 7014c18..73637fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -28,7 +28,7 @@ import { ChannelList } from "./features/channels/ChannelList"; import { EmptyState } from "./components/EmptyState"; import { getPacketDetail } from "./api/client"; import { WsManager } from "./api/ws-manager"; -import { WS_URL, TABS } from "./lib/constants"; +import { WS_URL, ENABLED_TABS } from "./lib/constants"; // 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. @@ -123,7 +123,7 @@ function AppInner() { // unknown ?tab value falls back to Packets instead of rendering a blank pane. // "Stats" was renamed to "Analytics"; keep old ?tab=Stats links working. const tabParam = searchParams.get("tab") === "Stats" ? "Analytics" : searchParams.get("tab"); - const activeTab = (TABS as readonly string[]).includes(tabParam ?? "") ? (tabParam as string) : "Packets"; + const activeTab = ENABLED_TABS.includes(tabParam ?? "") ? (tabParam as string) : (ENABLED_TABS[0] ?? "Packets"); // Resolve the starting selection once from URL → storage → legacy key (see computeInitialSelection). const [initialSelection] = useState(() => computeInitialSelection(searchParams)); diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index 07205a1..bbaab79 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -9,7 +9,7 @@ import { Dropdown } from "./Dropdown"; import { BottomNav } from "./BottomNav"; import { BeaconWordmark } from "./BeaconWordmark"; import { getIatas } from "../api/client"; -import { TABS } from "../lib/constants"; +import { ENABLED_TABS, ENABLED_THEME_IDS, isThemeVisible, APP_NAME, GITHUB_URL } from "../lib/constants"; import type { WsManager } from "../api/ws-manager"; // header widgets: WS status, region picker, theme picker @@ -131,7 +131,7 @@ function RegionSelector() { className={`w-full flex items-center gap-2.5 px-3 py-1.5 text-left text-xs font-mono transition-colors ${ isAllRegions(selection) ? "text-text-bright bg-primary/10" - : "text-text-muted hover:text-text-normal hover:bg-white/3" + : "text-text-muted hover:text-text-normal hover:bg-text-normal/3" }`} onClick={() => setSelection(ALL_REGIONS)} > @@ -151,7 +151,7 @@ function RegionSelector() { key={r.slug} type="button" className={`w-full flex items-center gap-2.5 px-3 py-1.5 text-left text-xs font-mono transition-colors ${ - checked ? "text-text-bright bg-primary/10" : "text-text-muted hover:text-text-normal hover:bg-white/3" + checked ? "text-text-bright bg-primary/10" : "text-text-muted hover:text-text-normal hover:bg-text-normal/3" }`} onClick={() => toggleRegion(r.slug)} > @@ -172,7 +172,7 @@ function RegionSelector() { key={i.iata} type="button" className={`w-full flex items-center gap-2.5 px-3 py-1.5 text-left text-xs font-mono transition-colors ${ - checked ? "text-text-bright bg-primary/10" : "text-text-muted hover:text-text-normal hover:bg-white/3" + checked ? "text-text-bright bg-primary/10" : "text-text-muted hover:text-text-normal hover:bg-text-normal/3" }`} onClick={() => toggleIata(i.iata)} > @@ -206,7 +206,7 @@ function ThemePicker() { onClick={toggle} > ▾ @@ -215,14 +215,14 @@ function ThemePicker() { > {(close) => ( <> - {themes.map((t) => ( + {themes.filter((t) => isThemeVisible(t, ENABLED_THEME_IDS) || t.id === themeId).map((t) => (