From ea9dba0c58b04acf5c2918eb0d58840e26be2437 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Wed, 1 Jul 2026 15:53:05 +0000 Subject: [PATCH] Monitor page: Uptime Kuma iframe + deploy log; expose /deploy/ via nginx Co-Authored-By: Claude Opus 4.8 (1M context) --- nginx.conf | 7 +++ src/App.tsx | 3 +- src/pages/AdminMonitor.tsx | 119 +++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/pages/AdminMonitor.tsx diff --git a/nginx.conf b/nginx.conf index 3c3b582..bfa3901 100644 --- a/nginx.conf +++ b/nginx.conf @@ -25,6 +25,13 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + location /deploy/ { + proxy_pass http://10.10.10.105:9000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /sw.js { add_header Cache-Control "no-store, no-cache, must-revalidate"; try_files $uri =404; diff --git a/src/App.tsx b/src/App.tsx index b3a3fda..da4e2e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { Login } from './pages/Login' import { Dashboard } from './pages/Dashboard' import { AppFrame } from './pages/AppFrame' import { AdminUsers } from './pages/AdminUsers' +import { AdminMonitor } from './pages/AdminMonitor' // The portal is only ever the top-level frame. If it finds itself loaded inside // an iframe, it means an app's path fell back to the portal (that app isn't @@ -42,7 +43,7 @@ export default function App() { } /> } /> : } /> - } /> + : } /> } /> )} diff --git a/src/pages/AdminMonitor.tsx b/src/pages/AdminMonitor.tsx new file mode 100644 index 0000000..e0b26fb --- /dev/null +++ b/src/pages/AdminMonitor.tsx @@ -0,0 +1,119 @@ +import { useEffect, useState } from 'react' +import { Sidebar } from '../components/Sidebar' +import type { User } from '../types' + +interface DeployEntry { + repo: string + host: string + started: string + finished?: string + status: 'running' | 'success' | 'failed' + output?: string + error?: string +} + +export function AdminMonitor({ user }: { user: User }) { + const [tab, setTab] = useState<'kuma' | 'deploys'>('kuma') + const [deploys, setDeploys] = useState([]) + const [loading, setLoading] = useState(false) + + async function fetchDeploys() { + setLoading(true) + try { + const res = await fetch('/deploy/deploys', { credentials: 'include' }) + if (res.ok) setDeploys(await res.json()) + } finally { + setLoading(false) + } + } + + useEffect(() => { + if (tab === 'deploys') fetchDeploys() + }, [tab]) + + return ( +
+ +
+
+ {(['kuma', 'deploys'] as const).map(t => ( + + ))} +
+ + {tab === 'kuma' && ( +