diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 305ed2d..a27811c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -77,6 +77,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | VITE_API_BASE_URL=${{ env.VITE_API_BASE_URL }} + VITE_TELEMETRY_URL=${{ vars.VITE_TELEMETRY_URL || 'https://telemetry.zeavisedu.asepharyana.my.id' }} cache-from: type=gha,scope=${{ matrix.service.name }} cache-to: type=gha,mode=max,scope=${{ matrix.service.name }} diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 71d6cd2..5bee7a4 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -11,6 +11,8 @@ COPY packages/shared packages/shared COPY apps/web apps/web ARG VITE_API_BASE_URL ENV VITE_API_BASE_URL=$VITE_API_BASE_URL +ARG VITE_TELEMETRY_URL +ENV VITE_TELEMETRY_URL=$VITE_TELEMETRY_URL RUN bun run --cwd packages/shared build RUN bun run --cwd apps/web build diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf index 5d9fa05..11bce60 100644 --- a/apps/web/nginx.conf +++ b/apps/web/nginx.conf @@ -21,23 +21,6 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } - # Proxy telemetry dashboard to orange VPS (via Tailscale) - location /telemetry/ { - proxy_pass http://100.96.248.86:8181/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 5s; - proxy_send_timeout 35s; - proxy_read_timeout 35s; - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 4k; - proxy_busy_buffers_size 8k; - } - location / { try_files $uri $uri/ /index.html; } diff --git a/apps/web/src/pages/telemetry-page.tsx b/apps/web/src/pages/telemetry-page.tsx index d4d2176..c2f3a1c 100644 --- a/apps/web/src/pages/telemetry-page.tsx +++ b/apps/web/src/pages/telemetry-page.tsx @@ -1,8 +1,21 @@ import { useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; -import { BarChart3, ExternalLink } from "lucide-react"; +import { BarChart3, ExternalLink, AlertTriangle } from "lucide-react"; -const TELEMETRY_BASE = "/telemetry"; +/** + * The Telemetry Iframe Page + * + * Options for TELEMETRY_URL (set via VITE_TELEMETRY_URL env var): + * - https://telemetry.zeavisedu.asepharyana.my.id (public, via Coolify/Traefik — BEST) + * - http://100.96.248.86:8181 (direct Tailscale IP — internal only) + * + * The public URL is accessible from anywhere and needs no Tailscale. + * The Tailscale IP only works if the browser is on the Tailscale network. + */ + +const TELEMETRY_URL = + import.meta.env.VITE_TELEMETRY_URL ?? + "https://telemetry.zeavisedu.asepharyana.my.id"; const EMBED_PAGES = [ { id: "dashboard", label: "Dashboard", path: "" }, @@ -13,22 +26,28 @@ const EMBED_PAGES = [ export function TelemetryPage() { const [currentPage, setCurrentPage] = useState(EMBED_PAGES[0]); - const [iframeLoaded, setIframeLoaded] = useState(false); - const [iframeError, setIframeError] = useState(false); + const [iframeState, setIframeState] = useState<"loading" | "loaded" | "error">("loading"); const embedUrl = useMemo(() => { - const base = `${TELEMETRY_BASE}${currentPage.path}`; + const base = `${TELEMETRY_URL}${currentPage.path}`; return base; }, [currentPage]); const handleIframeLoad = () => { - setIframeLoaded(true); - setIframeError(false); + // Only transition to loaded if we got a real page, not an error page + try { + const iframe = document.getElementById("telemetry-iframe") as HTMLIFrameElement | null; + if (iframe?.contentWindow?.location?.href) { + setIframeState("loaded"); + } + } catch { + // Cross-origin access error — means iframe loaded from different origin, which is normal + setIframeState("loaded"); + } }; - const handleIframeError = () => { - setIframeLoaded(false); - setIframeError(true); + const handleRetry = () => { + setIframeState("loading"); }; return ( @@ -63,8 +82,7 @@ export function TelemetryPage() { key={page.id} onClick={() => { setCurrentPage(page); - setIframeLoaded(false); - setIframeError(false); + setIframeState("loading"); }} className={`rounded-full px-4 py-2 text-sm font-medium transition-colors ${ currentPage.id === page.id @@ -81,8 +99,8 @@ export function TelemetryPage() { {/* Loading indicator */} - {!iframeLoaded && !iframeError && ( -
+ {iframeState === "loading" && ( +

@@ -93,30 +111,39 @@ export function TelemetryPage() { )} {/* Error state */} - {iframeError && ( -

+ {iframeState === "error" && ( +
-
⚠️
+

Failed to load telemetry dashboard

- The telemetry backend on orange VPS may be unreachable. - Ensure Tailscale is connected and the telemetry stack is - running. + Cannot reach {TELEMETRY_URL}.

+

+ Make sure the telemetry stack is running on the orange VPS + and the domain/Tailscale IP is accessible. +

+
)} - {/* Iframe */} + {/* Iframe — hidden until loaded or explicitly shown */}