Skip to content

Commit

Permalink
Allow users to see server status without login (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
sainak authored May 30, 2024
1 parent 6ebb4f1 commit b1cbb8d
Show file tree
Hide file tree
Showing 15 changed files with 100 additions and 83 deletions.
2 changes: 1 addition & 1 deletion src/controller/ObservationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
49 changes: 0 additions & 49 deletions src/controller/ServerStatusController.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 3 additions & 1 deletion src/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions src/middleware/morganWithWs.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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(" ");
});
3 changes: 0 additions & 3 deletions src/public/assets/styles/style.css

This file was deleted.

15 changes: 15 additions & 0 deletions src/public/static/css/styles.css
Original file line number Diff line number Diff line change
@@ -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 */
}
5 changes: 2 additions & 3 deletions src/public/assets/js/ws.js → src/public/static/js/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = `---`;
Expand Down
3 changes: 0 additions & 3 deletions src/router/serverStatusRouter.ts
Original file line number Diff line number Diff line change
@@ -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 };
14 changes: 7 additions & 7 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -35,6 +34,7 @@ import {
sentryEnv,
sentryTracesSampleRate,
} from "@/utils/configs";
import { sendStatus } from "@/utils/serverStatusUtil";

export function initServer() {
const appBase = express();
Expand Down Expand Up @@ -74,6 +74,7 @@ export function initServer() {
app.use(flash());

app.use(getWs(ws));
app.use(morganWithWs);

app.use(
helmet({
Expand All @@ -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);
}
Expand Down Expand Up @@ -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";
Expand All @@ -131,8 +134,5 @@ export function initServer() {
app.use(Sentry.Handlers.errorHandler());
app.use(errorHandler);

// Server status monitor
ServerStatusController.init(ws);

return app;
}
4 changes: 3 additions & 1 deletion src/types/ws.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { User } from "./user";
import type { WebSocket as InitialWebSocket } from "ws";

export interface WebSocket extends InitialWebSocket {
route?: string;
params?: Record<string, string>;
route?: string;
user?: User;
}
35 changes: 35 additions & 0 deletions src/utils/serverStatusUtil.ts
Original file line number Diff line number Diff line change
@@ -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 };
18 changes: 13 additions & 5 deletions src/utils/wsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
});
};
20 changes: 14 additions & 6 deletions src/views/pages/serverStatus.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

<h1 class="flex items-center text-xl">
Server Status :&nbsp;
<span id="server-status-dot" class="flex h-4 w-4 items-center justify-center" data-status="red">
<span class="center absolute inline-flex h-4 w-4 animate-ping rounded-full"></span>
<span class="relative inline-flex h-3 w-3 rounded-full"></span>
</span>

<span
id="server-status-dot"
class="inline-block w-4 h-4 bg-red-500 rounded-full animate-ping"
></span
>&nbsp;
&nbsp;
<span id="server-status-text"> Disconnected</span>
</h1>

Expand Down Expand Up @@ -48,6 +48,8 @@
</div>
</div>
</div>
<% if (req?.user?.id ?? false) { %>
<div id="errors" class="my-16">
<a href="#error-log" class="block text-xl my-4 font-bold"
># Error Logs</a
Expand Down Expand Up @@ -113,8 +115,14 @@
</tbody>
</table>
</div>
<% } else { %>
<a class="block text-xl my-4 font-bold"># Logs</a>
<p>Log In to view request logs.</p>
<% } %>
</main>
<%- include("../partials/footer") %>
<script src="/assets/js/ws.js"></script>
<script src="/static/js/ws.js"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions src/views/partials/head.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.tailwindcss.com"></script>

<link rel="stylesheet" href="/static/css/styles.css" />

<script>
tailwind.config = {
theme: {
Expand Down

0 comments on commit b1cbb8d

Please sign in to comment.