Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow users to see server status without login #131

Merged
merged 6 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading