@opentrust/dashboard 7.3.13 → 7.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import { resultsRouter } from "./routes/results.js";
17
17
  import { discoveryRouter } from "./routes/discovery.js";
18
18
  import { observationsRouter } from "./routes/observations.js";
19
19
  import { commandsRouter } from "./routes/commands.js";
20
+ import { systemRouter } from "./routes/system.js";
20
21
  import { errorHandler } from "./middleware/error-handler.js";
21
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
23
  const pkgPath = join(__dirname, "..", "package.json");
@@ -77,6 +78,7 @@ app.use("/api/results", resultsRouter);
77
78
  app.use("/api/discovery", discoveryRouter);
78
79
  app.use("/api/observations", observationsRouter);
79
80
  app.use("/api/commands", commandsRouter);
81
+ app.use("/api/system", systemRouter);
80
82
  app.use(errorHandler);
81
83
  app.listen(PORT, () => {
82
84
  console.log(`OpenTrust API running on port ${PORT}`);
@@ -0,0 +1,131 @@
1
+ import { Router } from "express";
2
+ import { readFileSync, writeFileSync, existsSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ export const systemRouter = Router();
6
+ const RUNTIME_DIR = join(homedir(), ".opentrust");
7
+ const LOGS_DIR = join(RUNTIME_DIR, "logs");
8
+ const SERVICES = [
9
+ { key: "core", name: "Core", port: 53666, healthUrl: "http://localhost:53666/health" },
10
+ { key: "dashboard", name: "Dashboard", port: 53667, healthUrl: "http://localhost:53667/health" },
11
+ { key: "gateway", name: "Gateway", port: 8900, healthUrl: "http://localhost:8900/health" },
12
+ ];
13
+ const VALID_SERVICE_KEYS = SERVICES.map((s) => s.key);
14
+ function readPid(key) {
15
+ const pidFile = join(RUNTIME_DIR, `${key}.pid`);
16
+ if (!existsSync(pidFile))
17
+ return null;
18
+ const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
19
+ if (isNaN(pid))
20
+ return null;
21
+ try {
22
+ process.kill(pid, 0);
23
+ return pid;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ async function checkHealth(url) {
30
+ try {
31
+ const ctrl = new AbortController();
32
+ const timer = setTimeout(() => ctrl.abort(), 2000);
33
+ const res = await fetch(url, { signal: ctrl.signal });
34
+ clearTimeout(timer);
35
+ return res.ok;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ // GET /api/system/services — service status overview
42
+ systemRouter.get("/services", async (_req, res, next) => {
43
+ try {
44
+ const results = await Promise.all(SERVICES.map(async (svc) => {
45
+ const pid = readPid(svc.key);
46
+ const healthy = pid ? await checkHealth(svc.healthUrl) : false;
47
+ const logFile = join(LOGS_DIR, `${svc.key}.log`);
48
+ let logSize = 0;
49
+ if (existsSync(logFile)) {
50
+ logSize = statSync(logFile).size;
51
+ }
52
+ return {
53
+ key: svc.key,
54
+ name: svc.name,
55
+ port: svc.port,
56
+ pid,
57
+ running: pid !== null,
58
+ healthy,
59
+ logSize,
60
+ };
61
+ }));
62
+ res.json({ success: true, data: results });
63
+ }
64
+ catch (err) {
65
+ next(err);
66
+ }
67
+ });
68
+ // GET /api/system/logs/:service?lines=200&offset=0 — read log tail
69
+ systemRouter.get("/logs/:service", (req, res, next) => {
70
+ try {
71
+ const { service } = req.params;
72
+ if (!VALID_SERVICE_KEYS.includes(service)) {
73
+ res.status(400).json({ success: false, error: `Invalid service. Must be one of: ${VALID_SERVICE_KEYS.join(", ")}` });
74
+ return;
75
+ }
76
+ const logFile = join(LOGS_DIR, `${service}.log`);
77
+ if (!existsSync(logFile)) {
78
+ res.json({ success: true, data: { service, lines: [], total: 0 } });
79
+ return;
80
+ }
81
+ const maxLines = Math.min(parseInt(req.query.lines) || 200, 2000);
82
+ const content = readFileSync(logFile, "utf-8");
83
+ const allLines = content.split("\n");
84
+ // Remove trailing empty line from split
85
+ if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
86
+ allLines.pop();
87
+ }
88
+ const total = allLines.length;
89
+ const offset = parseInt(req.query.offset);
90
+ let sliced;
91
+ if (!isNaN(offset) && offset >= 0) {
92
+ sliced = allLines.slice(offset, offset + maxLines);
93
+ }
94
+ else {
95
+ // Default: return last N lines
96
+ sliced = allLines.slice(-maxLines);
97
+ }
98
+ const stat = statSync(logFile);
99
+ res.json({
100
+ success: true,
101
+ data: {
102
+ service,
103
+ lines: sliced,
104
+ total,
105
+ size: stat.size,
106
+ lastModified: stat.mtime.toISOString(),
107
+ },
108
+ });
109
+ }
110
+ catch (err) {
111
+ next(err);
112
+ }
113
+ });
114
+ // DELETE /api/system/logs/:service — clear a log file
115
+ systemRouter.delete("/logs/:service", (req, res, next) => {
116
+ try {
117
+ const { service } = req.params;
118
+ if (!VALID_SERVICE_KEYS.includes(service)) {
119
+ res.status(400).json({ success: false, error: `Invalid service` });
120
+ return;
121
+ }
122
+ const logFile = join(LOGS_DIR, `${service}.log`);
123
+ if (existsSync(logFile)) {
124
+ writeFileSync(logFile, "", "utf-8");
125
+ }
126
+ res.json({ success: true });
127
+ }
128
+ catch (err) {
129
+ next(err);
130
+ }
131
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentrust/dashboard",
3
- "version": "7.3.13",
3
+ "version": "7.3.15",
4
4
  "type": "module",
5
5
  "description": "OpenTrust Dashboard — management panel for AI Agent security (API + embedded web)",
6
6
  "main": "dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "morgan": "^1.10.0",
20
20
  "nodemailer": "^8.0.1",
21
21
  "zod": "^3.23.0",
22
- "@opentrust/db": "7.3.13",
23
- "@opentrust/shared": "7.3.13"
22
+ "@opentrust/db": "7.3.15",
23
+ "@opentrust/shared": "7.3.15"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/cors": "^2.8.17",