feat(web): externalize telemetry URL via environment variables

Replace the hardcoded Nginx proxy configuration with a dynamic environment variable approach. This allows the telemetry dashboard to be loaded via a public URL instead of relying on a local Tailscale proxy.

- Add `VITE_TELEMETRY_URL` build argument to Dockerfile
- Pass `VITE_TELEMETRY_URL` through GitHub Actions deployment workflow
- Remove `/telemetry/` location block from nginx.conf
- Update `TelemetryPage` to use the new environment variable and improve iframe loading state management
This commit is contained in:
MythEclipse
2026-06-07 23:31:15 +07:00
parent cd93d1a06d
commit 9602a64e18
4 changed files with 53 additions and 40 deletions
+1
View File
@@ -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 }}
+2
View File
@@ -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
-17
View File
@@ -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;
}
+50 -23
View File
@@ -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() {
<Card className="overflow-hidden rounded-2xl border-slate-200 shadow-sm">
<CardContent className="p-0 relative">
{/* Loading indicator */}
{!iframeLoaded && !iframeError && (
<div className="flex items-center justify-center h-[600px] bg-slate-50">
{iframeState === "loading" && (
<div className="flex items-center justify-center h-150 bg-slate-50">
<div className="text-center space-y-3">
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-[#48A111] border-r-transparent" />
<p className="text-sm text-muted-foreground">
@@ -93,30 +111,39 @@ export function TelemetryPage() {
)}
{/* Error state */}
{iframeError && (
<div className="flex items-center justify-center h-[600px] bg-slate-50">
{iframeState === "error" && (
<div className="flex items-center justify-center h-150 bg-slate-50">
<div className="text-center space-y-3 max-w-md px-4">
<div className="text-4xl"></div>
<AlertTriangle className="h-10 w-10 text-amber-500 mx-auto" />
<p className="text-sm font-semibold text-red-600">
Failed to load telemetry dashboard
</p>
<p className="text-sm text-muted-foreground">
The telemetry backend on orange VPS may be unreachable.
Ensure Tailscale is connected and the telemetry stack is
running.
Cannot reach <code className="text-xs bg-slate-200 px-1.5 py-0.5 rounded">{TELEMETRY_URL}</code>.
</p>
<p className="text-sm text-muted-foreground">
Make sure the telemetry stack is running on the orange VPS
and the domain/Tailscale IP is accessible.
</p>
<button
onClick={handleRetry}
className="inline-flex items-center gap-1 rounded-full bg-[#48A111] px-4 py-2 text-sm font-semibold text-white hover:bg-[#306D29] transition-colors"
>
Retry
</button>
</div>
</div>
)}
{/* Iframe */}
{/* Iframe — hidden until loaded or explicitly shown */}
<iframe
id="telemetry-iframe"
src={embedUrl}
title="Telemetry Dashboard"
className={`w-full border-0 ${iframeLoaded ? "block" : "hidden"}`}
className={`w-full border-0 ${iframeState === "loaded" ? "block" : "hidden"}`}
style={{ height: "calc(100vh - 280px)", minHeight: "600px" }}
onLoad={handleIframeLoad}
onError={handleIframeError}
onError={() => setIframeState("error")}
allow="same-origin"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
/>