Add system updates page to admin section

Add a new API endpoint and UI section for checking system updates, including internationalized locale strings and necessary dependency updates.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1e6de894-7c78-4557-b01a-a2c1c01f4155
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
riyadhafraa
2026-05-17 14:59:36 +00:00
parent 191e072353
commit b5efd9eb12
12 changed files with 568 additions and 3 deletions
+2
View File
@@ -35,6 +35,7 @@
"pino-http": "^10",
"playwright-core": "^1.59.1",
"sanitize-html": "^2.17.3",
"semver": "^7.6.3",
"socket.io": "^4.8.3",
"web-push": "^3.6.7",
"zod": "catalog:"
@@ -50,6 +51,7 @@
"@types/nodemailer": "^8.0.0",
"@types/pdfkit": "^0.17.6",
"@types/sanitize-html": "^2.16.1",
"@types/semver": "^7.5.8",
"@types/web-push": "^3.6.4",
"esbuild": "^0.27.3",
"esbuild-plugin-pino": "^2.3.3",
+2
View File
@@ -17,6 +17,7 @@ import rolesRouter from "./roles";
import auditRouter from "./audit";
import executiveMeetingsRouter from "./executive-meetings";
import pushRouter from "./push";
import systemRouter from "./system";
const router: IRouter = Router();
@@ -38,5 +39,6 @@ router.use(rolesRouter);
router.use(auditRouter);
router.use(executiveMeetingsRouter);
router.use(pushRouter);
router.use(systemRouter);
export default router;
+113
View File
@@ -0,0 +1,113 @@
import { Router, type IRouter } from "express";
import semver from "semver";
import { requireAdmin } from "../middlewares/auth";
import versionFile from "../../../../version.json" with { type: "json" };
import { GetSystemVersionResponse } from "@workspace/api-zod";
const router: IRouter = Router();
const CURRENT_VERSION: string = versionFile.version;
const CURRENT_BUILD: string = versionFile.build;
const LATEST_URL = process.env.LATEST_VERSION_URL ?? "";
const FETCH_TIMEOUT_MS = 5000;
type LatestPayload = {
version?: unknown;
build?: unknown;
};
router.get("/system/version", requireAdmin, async (_req, res): Promise<void> => {
const checkedAt = new Date().toISOString();
if (!LATEST_URL) {
res.json(
GetSystemVersionResponse.parse({
current: { version: CURRENT_VERSION, build: CURRENT_BUILD },
latest: null,
status: "not-configured",
errorMessage: null,
checkedAt,
configured: false,
}),
);
return;
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try {
const response = await fetch(LATEST_URL, {
signal: controller.signal,
headers: { Accept: "application/json" },
});
if (!response.ok) {
res.json(
GetSystemVersionResponse.parse({
current: { version: CURRENT_VERSION, build: CURRENT_BUILD },
latest: null,
status: "error",
errorMessage: `HTTP ${response.status}`,
checkedAt,
configured: true,
}),
);
return;
}
const body = (await response.json()) as LatestPayload;
const rawVersion = typeof body.version === "string" ? body.version : "";
const rawBuild = typeof body.build === "string" ? body.build : "";
const cleaned = semver.valid(semver.coerce(rawVersion));
const localCleaned = semver.valid(semver.coerce(CURRENT_VERSION));
if (!cleaned || !localCleaned) {
res.json(
GetSystemVersionResponse.parse({
current: { version: CURRENT_VERSION, build: CURRENT_BUILD },
latest: { version: rawVersion, build: rawBuild },
status: "error",
errorMessage: "Invalid version format",
checkedAt,
configured: true,
}),
);
return;
}
// semver.gt: strictly greater → update available. Equal or older →
// up to date (we never tell the user to "downgrade" — the desktop
// build is the source of truth for that scenario).
const status = semver.gt(cleaned, localCleaned)
? "update-available"
: "up-to-date";
res.json(
GetSystemVersionResponse.parse({
current: { version: CURRENT_VERSION, build: CURRENT_BUILD },
latest: { version: rawVersion, build: rawBuild },
status,
errorMessage: null,
checkedAt,
configured: true,
}),
);
} catch (err) {
const message =
err instanceof Error
? err.name === "AbortError"
? "Request timed out"
: err.message
: "Unknown error";
res.json(
GetSystemVersionResponse.parse({
current: { version: CURRENT_VERSION, build: CURRENT_BUILD },
latest: null,
status: "error",
errorMessage: message,
checkedAt,
configured: true,
}),
);
} finally {
clearTimeout(timer);
}
});
export default router;