feat(dashboard): impl server monitoring

This commit is contained in:
2025-10-25 01:09:53 +00:00
parent 189a6c4401
commit 220d25ccab
9 changed files with 339 additions and 3 deletions

View File

@@ -0,0 +1,76 @@
import { Hono } from "hono"
import { type BeszelContext, beszelAuth } from "./beszel/middleware"
const beszel = new Hono<BeszelContext>()
// Apply middleware to all beszel routes
beszel.use("*", beszelAuth())
interface BeszelSystemInfo {
name: string
info: {
cpu: number
ram: number
disk: number
}
}
interface BeszelApiSystem {
name: string
info: {
cpu: number
mp: number // memory percentage
dp: number // disk percentage
}
}
beszel.get("/systems", async (c) => {
try {
const beszelHost = process.env.BESZEL_HOST
const token = c.get("beszelToken")
if (!beszelHost) {
return c.json({ error: "BESZEL_HOST environment variable not set" }, 500)
}
const response = await fetch(`http://${beszelHost}/api/collections/systems/records`, {
headers: {
Authorization: token,
},
})
if (!response.ok) {
return new Response(
JSON.stringify({
error: "Failed to fetch Beszel data",
status: response.status,
}),
{
status: response.status,
headers: { "Content-Type": "application/json" },
},
)
}
const data = (await response.json()) as { items: BeszelApiSystem[] }
const systems: BeszelSystemInfo[] = data.items.map((system) => ({
name: system.name,
info: {
cpu: system.info.cpu,
ram: system.info.mp,
disk: system.info.dp,
},
}))
return c.json({
lastUpdated: new Date().toISOString(),
systems,
totalSystems: systems.length,
})
} catch (error) {
return c.json({ error: "Internal server error", message: String(error) }, 500)
}
})
export default beszel

View File

@@ -0,0 +1,61 @@
import type { MiddlewareHandler } from "hono"
interface BeszelAuthResponse {
token: string
}
export function beszelAuth(): MiddlewareHandler {
let cachedToken: string | null = null
const authenticate = async (): Promise<string> => {
if (cachedToken) {
return cachedToken
}
const beszelHost = process.env.BESZEL_HOST
const beszelEmail = process.env.BESZEL_EMAIL
const beszelPassword = process.env.BESZEL_PASSWORD
if (!beszelHost || !beszelEmail || !beszelPassword) {
throw new Error(
"Beszel configuration missing. Set BESZEL_HOST, BESZEL_EMAIL, and BESZEL_PASSWORD environment variables.",
)
}
const response = await fetch(`http://${beszelHost}/api/collections/users/auth-with-password`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
identity: beszelEmail,
password: beszelPassword,
}),
})
if (!response.ok) {
throw new Error(`Beszel authentication failed: ${response.status}`)
}
const data = (await response.json()) as BeszelAuthResponse
cachedToken = data.token
return cachedToken
}
return async (c, next) => {
try {
const token = await authenticate()
c.set("beszelToken", token)
await next()
} catch (error) {
return c.json({ error: "Authentication failed", message: String(error) }, 500)
}
}
}
export type BeszelContext = {
Variables: {
beszelToken: string
}
}

View File

@@ -5,5 +5,8 @@ declare namespace NodeJS {
ADP_KEY_ID: string
ADP_KEY_PATH: string
GEMINI_API_KEY: string
BESZEL_HOST?: string
BESZEL_EMAIL?: string
BESZEL_PASSWORD?: string
}
}

View File

@@ -3,6 +3,7 @@ import { cors } from "hono/cors"
import { logger } from "hono/logger"
import weather from "./weather"
import tfl from "./tfl"
import beszel from "./beszel"
const app = new Hono()
@@ -23,6 +24,9 @@ app.route("/api/weather", weather)
// Mount TfL routes
app.route("/api/tfl", tfl)
// Mount Beszel routes
app.route("/api/beszel", beszel)
export default {
port: 8000,
fetch: app.fetch,