mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 21:33:41 +00:00
refactor(backend): reorganize top-level layout
- ssh/ renamed to hosts/ (it covers SSH, RDP, VNC, Telnet, Docker, metrics) - serial/serial.ts and guacamole/ moved inside hosts/ - dashboard.ts and homepage.ts moved to services/ - swagger.ts moved to utils/ with adjusted scan globs Import paths and the generate:openapi script updated; ports and endpoints unchanged.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import type { Express, RequestHandler } from "express";
|
||||
import type { AuthenticatedRequest } from "../../types/index.js";
|
||||
import { createCurrentHostMetricsHistoryRepository } from "../database/repositories/factory.js";
|
||||
import { statsLogger } from "../utils/logger.js";
|
||||
|
||||
type HistoryRoutesDeps = {
|
||||
validateHostId: RequestHandler;
|
||||
canAccessHost: (
|
||||
userId: string,
|
||||
hostId: number,
|
||||
level: "read" | "write" | "execute" | "delete" | "share",
|
||||
) => Promise<boolean>;
|
||||
};
|
||||
|
||||
const RANGE_OFFSETS: Record<string, number> = {
|
||||
"1h": 1 * 60 * 60 * 1000,
|
||||
"6h": 6 * 60 * 60 * 1000,
|
||||
"24h": 24 * 60 * 60 * 1000,
|
||||
"7d": 7 * 24 * 60 * 60 * 1000,
|
||||
"30d": 30 * 24 * 60 * 60 * 1000,
|
||||
};
|
||||
|
||||
export function registerHostMetricsHistoryRoutes(
|
||||
app: Express,
|
||||
{ validateHostId, canAccessHost }: HistoryRoutesDeps,
|
||||
): void {
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/history/{id}:
|
||||
* get:
|
||||
* summary: Get historical metrics for a host
|
||||
* tags:
|
||||
* - Host Metrics
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* - in: query
|
||||
* name: range
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [1h, 6h, 24h, 7d, 30d]
|
||||
* - in: query
|
||||
* name: from
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* - in: query
|
||||
* name: to
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Array of metric history rows.
|
||||
* 403:
|
||||
* description: Access denied.
|
||||
* 404:
|
||||
* description: Host not found or no access.
|
||||
*/
|
||||
app.get("/metrics/history/:id", validateHostId, async (req, res) => {
|
||||
const hostId = Number(req.params.id);
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
try {
|
||||
const hasAccess = await canAccessHost(userId, hostId, "read");
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ error: "Access denied" });
|
||||
}
|
||||
|
||||
const { range, from, to } = req.query as Record<
|
||||
string,
|
||||
string | undefined
|
||||
>;
|
||||
|
||||
let fromTs: string;
|
||||
let toTs: string = new Date().toISOString();
|
||||
|
||||
if (range) {
|
||||
const offsetMs = RANGE_OFFSETS[range];
|
||||
if (!offsetMs) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Invalid range. Use 1h, 6h, 24h, 7d, or 30d" });
|
||||
}
|
||||
fromTs = new Date(Date.now() - offsetMs).toISOString();
|
||||
} else if (from && to) {
|
||||
const fromDate = new Date(from);
|
||||
const toDate = new Date(to);
|
||||
if (isNaN(fromDate.getTime()) || isNaN(toDate.getTime())) {
|
||||
return res.status(400).json({ error: "Invalid from/to date format" });
|
||||
}
|
||||
fromTs = fromDate.toISOString();
|
||||
toTs = toDate.toISOString();
|
||||
} else {
|
||||
fromTs = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
}
|
||||
|
||||
// SQLite CURRENT_TIMESTAMP stores 'YYYY-MM-DD HH:MM:SS' (no T/Z).
|
||||
// Normalize both sides to that format for reliable string comparison.
|
||||
const toSqlite = (iso: string) =>
|
||||
iso.replace("T", " ").replace(/\.\d{3}Z$/, "");
|
||||
const rows = (
|
||||
await createCurrentHostMetricsHistoryRepository().listRange(
|
||||
hostId,
|
||||
toSqlite(fromTs),
|
||||
toSqlite(toTs),
|
||||
)
|
||||
).map((row) => ({
|
||||
ts: row.ts,
|
||||
cpu_percent: row.cpuPercent,
|
||||
mem_percent: row.memPercent,
|
||||
disk_percent: row.diskPercent,
|
||||
net_rx_bytes: row.netRxBytes,
|
||||
net_tx_bytes: row.netTxBytes,
|
||||
}));
|
||||
|
||||
res.json({ rows, fromTs, toTs });
|
||||
} catch (error) {
|
||||
statsLogger.error("Failed to fetch metrics history", {
|
||||
operation: "metrics_history_fetch_error",
|
||||
hostId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
res.status(500).json({ error: "Failed to fetch metrics history" });
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user