From 4dde9d84088781accfa287e2a3e9b7c248c88ae4 Mon Sep 17 00:00:00 2001
From: MythEclipse
Date: Sun, 7 Jun 2026 23:44:57 +0700
Subject: [PATCH] refactor(web): implement nginx reverse proxy for telemetry
dashboard
Transition telemetry dashboard integration from an external URL approach to a local reverse proxy via Nginx. This simplifies client-side connectivity by routing all telemetry requests through the existing web server's domain.
- Configure Nginx to proxy `/telemetry/` sub-paths to the internal telemetry service
- Implement `sub_filter` in Nginx to rewrite root-relative paths for the SPA
- Remove `VITE_TELEMETRY_URL` build arguments and environment variable dependencies
- Simplify `TelemetryPage` to use a relative `/telemetry` base path
- Clean up deployment workflows and Dockerfile by removing unused telemetry build args
---
.github/workflows/deploy.yml | 1 -
apps/web/Dockerfile | 2 -
apps/web/nginx.conf | 57 ++++++++++++++++++++++++++
apps/web/src/pages/telemetry-page.tsx | 58 +++++++--------------------
4 files changed, 72 insertions(+), 46 deletions(-)
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index a27811c..305ed2d 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -77,7 +77,6 @@ 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 5bee7a4..71d6cd2 100644
--- a/apps/web/Dockerfile
+++ b/apps/web/Dockerfile
@@ -11,8 +11,6 @@ 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 11bce60..21e1872 100644
--- a/apps/web/nginx.conf
+++ b/apps/web/nginx.conf
@@ -21,6 +21,63 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
+ # ── Telemetry Dashboard Proxy (orange VPS via Tailscale) ──────────────
+ # Telemetry UI SPA assets (fingerprinted by Vite, cache aggressively)
+ location /telemetry/assets/ {
+ proxy_pass http://100.96.248.86:8181/assets/;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+
+ # Telemetry API calls (query proxy + target management)
+ location /telemetry/proxy/ {
+ proxy_pass http://100.96.248.86:8181/proxy/;
+ 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;
+ }
+
+ location /telemetry/api/ {
+ proxy_pass http://100.96.248.86:8181/api/;
+ 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 10s;
+ proxy_read_timeout 10s;
+ }
+
+ # Telemetry SPA — proxy to orange, rewrite root-relative paths to prefix
+ 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;
+
+ # Rewrite root-relative paths in HTML/JS to /telemetry/ prefix
+ sub_filter_types text/html application/javascript;
+ sub_filter_once off;
+ sub_filter 'src="/' 'src="/telemetry/';
+ sub_filter 'href="/' 'href="/telemetry/';
+ sub_filter "url('/" "url('/telemetry/";
+ sub_filter 'fetch("/' 'fetch("/telemetry/');
+ }
+
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 c2f3a1c..cf82ca7 100644
--- a/apps/web/src/pages/telemetry-page.tsx
+++ b/apps/web/src/pages/telemetry-page.tsx
@@ -1,21 +1,13 @@
import { useMemo, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
-import { BarChart3, ExternalLink, AlertTriangle } from "lucide-react";
+import { BarChart3, AlertTriangle } from "lucide-react";
/**
- * 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.
+ * Telemetry Dashboard — proxied through nginx at /telemetry/
+ * Nginx rewrites root-relative paths so the SPA works from the sub-path.
*/
-const TELEMETRY_URL =
- import.meta.env.VITE_TELEMETRY_URL ??
- "https://telemetry.zeavisedu.asepharyana.my.id";
+const TELEMETRY_BASE = "/telemetry";
const EMBED_PAGES = [
{ id: "dashboard", label: "Dashboard", path: "" },
@@ -29,21 +21,15 @@ export function TelemetryPage() {
const [iframeState, setIframeState] = useState<"loading" | "loaded" | "error">("loading");
const embedUrl = useMemo(() => {
- const base = `${TELEMETRY_URL}${currentPage.path}`;
- return base;
+ return `${TELEMETRY_BASE}${currentPage.path}`;
}, [currentPage]);
const handleIframeLoad = () => {
- // 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");
- }
+ setIframeState("loaded");
+ };
+
+ const handleIframeError = () => {
+ setIframeState("error");
};
const handleRetry = () => {
@@ -64,15 +50,6 @@ export function TelemetryPage() {
telemetry pipeline (Prometheus → ClickHouse).
-
-
- Open in New Tab
-
{/* Navigation tabs */}
@@ -119,11 +96,8 @@ export function TelemetryPage() {
Failed to load telemetry dashboard
- Cannot reach {TELEMETRY_URL}.
-
-
- Make sure the telemetry stack is running on the orange VPS
- and the domain/Tailscale IP is accessible.
+ The telemetry backend may be unreachable. Make sure the
+ telemetry stack is running on the orange VPS.