feat(dashboard): impl server monitoring
This commit is contained in:
76
apps/backend/src/beszel.ts
Normal file
76
apps/backend/src/beszel.ts
Normal 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
|
||||
61
apps/backend/src/beszel/middleware.ts
Normal file
61
apps/backend/src/beszel/middleware.ts
Normal 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
|
||||
}
|
||||
}
|
||||
3
apps/backend/src/env.d.ts
vendored
3
apps/backend/src/env.d.ts
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user