feat(web): add telemetry dashboard page with nginx proxy and nav integration
This commit is contained in:
@@ -21,6 +21,23 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
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 / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { DiseaseDetailPage } from "@/pages/disease-detail-page";
|
|||||||
import { DiagnosisDetailPage } from "@/pages/diagnosis-detail-page";
|
import { DiagnosisDetailPage } from "@/pages/diagnosis-detail-page";
|
||||||
import { ExpertReviewsPage } from "@/pages/expert-reviews-page";
|
import { ExpertReviewsPage } from "@/pages/expert-reviews-page";
|
||||||
import { DiagnosesPage } from "@/pages/diagnoses-page";
|
import { DiagnosesPage } from "@/pages/diagnoses-page";
|
||||||
|
import { TelemetryPage } from "@/pages/telemetry-page";
|
||||||
import { MainLayout } from "@/components/layout/main-layout";
|
import { MainLayout } from "@/components/layout/main-layout";
|
||||||
import { trackPageView } from "./lib/telemetry";
|
import { trackPageView } from "./lib/telemetry";
|
||||||
|
|
||||||
@@ -85,6 +86,14 @@ const router = createBrowserRouter([
|
|||||||
</MainLayout>
|
</MainLayout>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/telemetry",
|
||||||
|
element: (
|
||||||
|
<MainLayout>
|
||||||
|
<TelemetryPage />
|
||||||
|
</MainLayout>
|
||||||
|
),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function PageViewTracker() {
|
function PageViewTracker() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const NAV_ITEMS = [
|
|||||||
{ path: "/diagnoses", label: "Diagnosa" },
|
{ path: "/diagnoses", label: "Diagnosa" },
|
||||||
{ path: "/catalog", label: "Pustaka", altPath: "/library" },
|
{ path: "/catalog", label: "Pustaka", altPath: "/library" },
|
||||||
{ path: "/expert/reviews", label: "Review" },
|
{ path: "/expert/reviews", label: "Review" },
|
||||||
|
{ path: "/telemetry", label: "Telemetry" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function MobileNav({ open, onClose }: Props) {
|
export function MobileNav({ open, onClose }: Props) {
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export function Navbar() {
|
|||||||
>
|
>
|
||||||
Review
|
Review
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/telemetry"
|
||||||
|
className={navLinkClassName(isActive("/telemetry"))}
|
||||||
|
>
|
||||||
|
Telemetry
|
||||||
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { BarChart3, ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
|
const TELEMETRY_BASE = "/telemetry";
|
||||||
|
|
||||||
|
const EMBED_PAGES = [
|
||||||
|
{ id: "dashboard", label: "Dashboard", path: "" },
|
||||||
|
{ id: "explorer", label: "Explorer", path: "/explorer" },
|
||||||
|
{ id: "targets", label: "Targets", path: "/targets" },
|
||||||
|
{ id: "tenants", label: "Tenants", path: "/tenants" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function TelemetryPage() {
|
||||||
|
const [currentPage, setCurrentPage] = useState(EMBED_PAGES[0]);
|
||||||
|
const [iframeLoaded, setIframeLoaded] = useState(false);
|
||||||
|
const [iframeError, setIframeError] = useState(false);
|
||||||
|
|
||||||
|
const embedUrl = useMemo(() => {
|
||||||
|
const base = `${TELEMETRY_BASE}${currentPage.path}`;
|
||||||
|
return base;
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
const handleIframeLoad = () => {
|
||||||
|
setIframeLoaded(true);
|
||||||
|
setIframeError(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIframeError = () => {
|
||||||
|
setIframeLoaded(false);
|
||||||
|
setIframeError(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl md:text-[28px] font-extrabold text-[#214B11] flex items-center gap-3">
|
||||||
|
<BarChart3 className="h-7 w-7 text-[#48A111]" />
|
||||||
|
Telemetry Dashboard
|
||||||
|
</h1>
|
||||||
|
<p className="text-[15px] text-muted-foreground mt-1">
|
||||||
|
System metrics, service health, and performance monitoring from the
|
||||||
|
telemetry pipeline (Prometheus → ClickHouse).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href={embedUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 rounded-full bg-[#48A111] px-4 py-2 text-sm font-semibold text-white hover:bg-[#306D29] transition-colors"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-4 w-4" />
|
||||||
|
Open in New Tab
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation tabs */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{EMBED_PAGES.map((page) => (
|
||||||
|
<button
|
||||||
|
key={page.id}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
setIframeLoaded(false);
|
||||||
|
setIframeError(false);
|
||||||
|
}}
|
||||||
|
className={`rounded-full px-4 py-2 text-sm font-medium transition-colors ${
|
||||||
|
currentPage.id === page.id
|
||||||
|
? "bg-[#48A111] text-white shadow-sm"
|
||||||
|
: "bg-white text-slate-600 hover:bg-slate-100 border border-slate-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{page.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Iframe container */}
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
Loading telemetry dashboard...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error state */}
|
||||||
|
{iframeError && (
|
||||||
|
<div className="flex items-center justify-center h-[600px] bg-slate-50">
|
||||||
|
<div className="text-center space-y-3 max-w-md px-4">
|
||||||
|
<div className="text-4xl">⚠️</div>
|
||||||
|
<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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Iframe */}
|
||||||
|
<iframe
|
||||||
|
src={embedUrl}
|
||||||
|
title="Telemetry Dashboard"
|
||||||
|
className={`w-full border-0 ${iframeLoaded ? "block" : "hidden"}`}
|
||||||
|
style={{ height: "calc(100vh - 280px)", minHeight: "600px" }}
|
||||||
|
onLoad={handleIframeLoad}
|
||||||
|
onError={handleIframeError}
|
||||||
|
allow="same-origin"
|
||||||
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user