diff --git a/src/controller/ObservationController.ts b/src/controller/ObservationController.ts index 15fc69f..d99de82 100644 --- a/src/controller/ObservationController.ts +++ b/src/controller/ObservationController.ts @@ -246,7 +246,7 @@ export class ObservationController { updateLastObservationData(flattenedObservations); this.latestObservation.set(flattenedObservations); - filterClients(req.wsInstance.getWss(), "/observations").forEach( + filterClients(req.wsInstance.getWss(), "/observations", undefined).forEach( (client: WebSocket) => { const filteredObservations = flattenedObservations?.filter( (observation: Observation) => diff --git a/src/controller/ServerStatusController.ts b/src/controller/ServerStatusController.ts index b11d4cf..09fb974 100644 --- a/src/controller/ServerStatusController.ts +++ b/src/controller/ServerStatusController.ts @@ -1,55 +1,6 @@ import type { Request, Response } from "express"; -import expressWs from "express-ws"; -import { loadavg } from "os"; -import pidusage from "pidusage"; - -import type { WebSocket } from "@/types/ws"; -import { eventType } from "@/utils/eventTypeConstant"; -import { filterClients } from "@/utils/wsUtils"; export class ServerStatusController { - static init(ws: expressWs.Instance) { - const server = ws.getWss(); - let intervalId: NodeJS.Timeout | number | undefined = undefined; - let clients: WebSocket[] = []; - server.on("connection", () => { - clients = filterClients(server, "/logger"); - if (!intervalId && clients.length !== 0) { - intervalId = setInterval(() => { - pidusage(process.pid, (err, stat) => { - if (err) { - console.log(err); - return null; - } - - if (!server.clients?.size) { - clearInterval(intervalId); - intervalId = undefined; - } - - const data = { - type: eventType.Resource, - cpu: Number(stat.cpu).toFixed(2), - memory: Number(stat.memory / 1024 / 1024).toFixed(2), - uptime: stat.elapsed, - load: loadavg()[0] || 0, - }; - - clients.forEach((client) => { - client.send(JSON.stringify(data)); - }); - }); - }, 1000); - } - - clients?.forEach((client) => { - client.on("close", () => { - clients = filterClients(server, "/logger"); - }); - }); - }); - } - static render(req: Request, res: Response) { res.render("pages/serverStatus", { req }); } diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index 20adfd7..cc7786b 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -105,7 +105,7 @@ export const jwtAuthNoVerify = (): RequestHandler => { req.user.id = payload.sub; } } catch (error: any) { - console.log(error); + console.warn(`JWT verification failed: ${error.code}`); } } next(); diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index bf5f6df..d880c97 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -34,7 +34,9 @@ export const errorHandler: ErrorRequestHandler = (err, req, res, next) => { }; const server = req.wsInstance.getWss(); - filterClients(server, "/logger").forEach((c) => c.send(JSON.stringify(data))); + filterClients(server, "/logger", true).forEach((c) => + c.send(JSON.stringify(data)), + ); if (nodeEnv === "development") { console.error(err); diff --git a/src/middleware/morganWithWs.ts b/src/middleware/morganWithWs.ts index 10ac6e4..c39de1f 100644 --- a/src/middleware/morganWithWs.ts +++ b/src/middleware/morganWithWs.ts @@ -1,6 +1,7 @@ import type { Request } from "express"; import morgan from "morgan"; +import type { WebSocket } from "@/types/ws"; import { eventType } from "@/utils/eventTypeConstant"; import { filterClients } from "@/utils/wsUtils"; @@ -14,9 +15,9 @@ export const morganWithWs = morgan(function (tokens, req: Request, res) { }; const server = req.wsInstance.getWss("/logger"); - filterClients(server, "/logger").forEach((client) => - client.send(JSON.stringify({ type: eventType.Request, ...data })), - ); + filterClients(server, "/logger", true).forEach((client: WebSocket) => { + client.send(JSON.stringify({ type: eventType.Request, ...data })); + }); return Object.values(data).join(" "); }); diff --git a/src/public/assets/styles/style.css b/src/public/assets/styles/style.css deleted file mode 100644 index 254f185..0000000 --- a/src/public/assets/styles/style.css +++ /dev/null @@ -1,3 +0,0 @@ -.btn { - @apply text-base px-4 py-2 border-2; -} diff --git a/src/public/static/css/styles.css b/src/public/static/css/styles.css new file mode 100644 index 0000000..c6569e7 --- /dev/null +++ b/src/public/static/css/styles.css @@ -0,0 +1,15 @@ +#server-status-dot[data-status="red"] .animate-ping { + background-color: #f87171 !important; /* Tailwind CSS bg-red-400 */ +} + +#server-status-dot[data-status="red"] .relative { + background-color: #dc2626 !important; /* Tailwind CSS bg-red-600 */ +} + +#server-status-dot[data-status="green"] .animate-ping { + background-color: #34d399 !important; /* Tailwind CSS bg-green-400 */ +} + +#server-status-dot[data-status="green"] .relative { + background-color: #059669 !important; /* Tailwind CSS bg-green-600 */ +} diff --git a/src/public/assets/js/ws.js b/src/public/static/js/ws.js similarity index 96% rename from src/public/assets/js/ws.js rename to src/public/static/js/ws.js index b3a0a74..ed792ca 100644 --- a/src/public/assets/js/ws.js +++ b/src/public/static/js/ws.js @@ -93,7 +93,7 @@ function connect() { var ws = new WebSocket(url); ws.onopen = function () { console.log("Connected to server"); - serverStatusDot.classList.add("bg-green-500"); + serverStatusDot.setAttribute("data-status", "green"); serverStatusText.innerText = "Connected"; }; const isFirstLog = { @@ -134,8 +134,7 @@ function connect() { e.reason, ); - serverStatusDot.classList.remove("bg-green-500"); - serverStatusDot.classList.add("bg-red-500"); + serverStatusDot.setAttribute("data-status", "red"); serverStatusText.innerText = "Disconnected"; cpuUsage.innerText = `---`; diff --git a/src/router/serverStatusRouter.ts b/src/router/serverStatusRouter.ts index 64fdec6..9e5bdc0 100644 --- a/src/router/serverStatusRouter.ts +++ b/src/router/serverStatusRouter.ts @@ -1,12 +1,9 @@ import express from "express"; import { ServerStatusController } from "@/controller/ServerStatusController"; -import { jwtAuth } from "@/middleware/auth"; const router = express.Router(); -router.use(jwtAuth()); - router.get("", ServerStatusController.render); export { router as serverStatusRouter }; diff --git a/src/server.ts b/src/server.ts index 4b962b5..cecacfb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,7 +10,6 @@ import path from "path"; import swaggerUi from "swagger-ui-express"; import { OpenidConfigController } from "@/controller/OpenidConfigController"; -import { ServerStatusController } from "@/controller/ServerStatusController"; import { randomString } from "@/lib/crypto"; import { errorHandler } from "@/middleware/errorHandler"; import { getWs } from "@/middleware/getWs"; @@ -35,6 +34,7 @@ import { sentryEnv, sentryTracesSampleRate, } from "@/utils/configs"; +import { sendStatus } from "@/utils/serverStatusUtil"; export function initServer() { const appBase = express(); @@ -74,6 +74,7 @@ export function initServer() { app.use(flash()); app.use(getWs(ws)); + app.use(morganWithWs); app.use( helmet({ @@ -87,9 +88,6 @@ export function initServer() { app.use(express.json({ limit: "50mb" })); app.use(express.urlencoded({ extended: true })); - // logger - app.use(morganWithWs); - if (nodeEnv === "debug") { app.use(requestLogger); } @@ -118,7 +116,12 @@ export function initServer() { ); app.ws("/logger", (ws: WebSocket, req) => { + ws.user = req.user; ws.route = "/logger"; + const timeout = sendStatus(ws); + ws.on("close", () => { + clearInterval(timeout); + }); }); app.ws("/observations/:ip", (ws: WebSocket, req) => { ws.route = "/observations"; @@ -131,8 +134,5 @@ export function initServer() { app.use(Sentry.Handlers.errorHandler()); app.use(errorHandler); - // Server status monitor - ServerStatusController.init(ws); - return app; } diff --git a/src/types/ws.ts b/src/types/ws.ts index 6e33ad3..c03ebb8 100644 --- a/src/types/ws.ts +++ b/src/types/ws.ts @@ -1,6 +1,8 @@ +import { User } from "./user"; import type { WebSocket as InitialWebSocket } from "ws"; export interface WebSocket extends InitialWebSocket { - route?: string; params?: Record; + route?: string; + user?: User; } diff --git a/src/utils/serverStatusUtil.ts b/src/utils/serverStatusUtil.ts new file mode 100644 index 0000000..d42a0b9 --- /dev/null +++ b/src/utils/serverStatusUtil.ts @@ -0,0 +1,35 @@ +import { cpus, loadavg } from "os"; +import pidusage from "pidusage"; + +import { WebSocket } from "@/types/ws"; +import { eventType } from "@/utils/eventTypeConstant"; + +const State = { + type: eventType.Resource, + cpu: "0.00", + memory: "0.00", + uptime: 0, + load: "0.00", +}; + +setInterval(() => { + pidusage(process.pid, (err, stat) => { + if (err) { + console.log(err); + return null; + } + + State["cpu"] = Number(stat.cpu).toFixed(2); + State["memory"] = Number(stat.memory / 1024 / 1024).toFixed(2); + State["uptime"] = stat.elapsed; + State["load"] = (loadavg()?.[1] / cpus()?.length)?.toFixed(2) ?? "0.00"; // 5 minutes load average + }); +}, 1000); + +function sendStatus(client: WebSocket) { + return setInterval(() => { + client.send(JSON.stringify(State)); + }, 1000); +} + +export { State, sendStatus }; diff --git a/src/utils/wsUtils.ts b/src/utils/wsUtils.ts index 42fb803..78379e5 100644 --- a/src/utils/wsUtils.ts +++ b/src/utils/wsUtils.ts @@ -2,9 +2,17 @@ import type { Server } from "ws"; import type { WebSocket } from "@/types/ws"; -export const filterClients = (ws: Server, path: string) => { - // console.log("CLEINT", ws.clients) - return Array.from(ws?.clients || []).filter( - (client: WebSocket) => client.route === path, - ); +export const filterClients = ( + ws: Server, + path: string, + isAuthenticated: boolean | undefined, +) => { + return Array.from(ws?.clients || []).filter((client: WebSocket) => { + if (isAuthenticated === undefined) { + return client.route === path; + } + return ( + client.route === path && Boolean(client?.user?.id) === isAuthenticated + ); + }); }; diff --git a/src/views/pages/serverStatus.ejs b/src/views/pages/serverStatus.ejs index bd79014..af0c7fd 100644 --- a/src/views/pages/serverStatus.ejs +++ b/src/views/pages/serverStatus.ejs @@ -11,12 +11,12 @@

Server Status :  + + + + -   +   Disconnected

@@ -48,6 +48,8 @@ + <% if (req?.user?.id ?? false) { %> +
# Error Logs
+ + <% } else { %> + # Logs +

Log In to view request logs.

+ <% } %> + <%- include("../partials/footer") %> - + diff --git a/src/views/partials/head.ejs b/src/views/partials/head.ejs index d3b7c68..9ac8598 100644 --- a/src/views/partials/head.ejs +++ b/src/views/partials/head.ejs @@ -12,6 +12,8 @@ + +