feat: move network topology into a first-party plugin

This commit is contained in:
LukeGus committed 2026-09-22 10:45:15 -05:00
1 parent e372b0eda7
commit 5fbd5fb7e2
16 files changed
+447 -24

No files matched your search

+2 -2
View File
@@ -18,7 +18,7 @@ import sessionLogRoutes from "./routes/session-log-routes.js";
import guacamoleDispatch from "./routes/guacamole-dispatch.js";
import sessionSharingRoutes from "../hosts/session-sharing/routes.js";
import collabRoutes from "../hosts/collab/routes.js";
import networkTopologyRoutes from "./routes/network-topology.js";
import networkTopologyDispatch from "./routes/network-topology-dispatch.js";
import rbacRoutes from "./routes/rbac.js";
import openTabsRoutes from "./routes/open-tabs.js";
import userPreferencesRoutes from "./routes/user-preferences.js";
@@ -1761,7 +1761,7 @@ app.use("/session_logs", sessionLogRoutes);
app.use("/guacamole", guacamoleDispatch);
app.use("/session-sharing", sessionSharingRoutes);
app.use("/collab", collabRoutes);
app.use("/network-topology", networkTopologyRoutes);
app.use("/network-topology", networkTopologyDispatch);
app.use("/rbac", rbacRoutes);
app.use("/open-tabs", openTabsRoutes);
app.use("/user-preferences", userPreferencesRoutes);
@@ -0,0 +1,47 @@
// Mounted at /network-topology. Network Topology ships as a first-party
// in-process plugin (plugins/network-topology) rather than a route file
// imported directly here, so this module exists to keep /network-topology/*
// working at its original path without handing the plugin a reference to the
// shared app instance. The plugin runtime registers its router via
// registerNetworkTopologyRouter when it activates, and clears it on
// deactivate, so a disabled plugin falls back to the 404 below.
//
// Unlike /plugin-api, this dispatcher applies no auth of its own: it forwards
// to exactly one first-party router (the Network Topology plugin's own), and
// every route in that router already applies authenticateJWT itself, the same
// as every other router mounted alongside it here (/users, /host, /snippets,
// ...). /plugin-api gates at the mount point because it forwards to arbitrary
// third-party plugin code that cannot be assumed to gate itself; that
// reasoning does not apply to a single first-party router that already does.
import type { Request, Response, NextFunction, Router } from "express";
import { databaseLogger } from "../../utils/logger.js";
let activeRouter: Router | null = null;
export function registerNetworkTopologyRouter(router: Router): void {
activeRouter = router;
databaseLogger.info("Registered Network Topology plugin router", {
operation: "network_topology_dispatch_register",
});
}
export function unregisterNetworkTopologyRouter(): void {
if (!activeRouter) return;
activeRouter = null;
databaseLogger.info("Unregistered Network Topology plugin router", {
operation: "network_topology_dispatch_register",
});
}
export default function networkTopologyDispatch(
req: Request,
res: Response,
next: NextFunction,
): void {
if (!activeRouter) {
res.status(404).json({ error: "Network Topology is not available" });
return;
}
activeRouter(req, res, next);
}
@@ -1,177 +0,0 @@
import express from "express";
import { AuthManager } from "../../utils/auth-manager.js";
import type { AuthenticatedRequest } from "../../../types/index.js";
import { databaseLogger } from "../../utils/logger.js";
import { createCurrentNetworkTopologyRepository } from "../repositories/factory.js";
const router = express.Router();
const authManager = AuthManager.getInstance();
const authenticateJWT = authManager.createAuthMiddleware();
/**
* @openapi
* /network-topology:
* get:
* summary: Get network topology for authenticated user
* description: Retrieves the saved network topology graph (nodes and edges) for the current user. Returns null if no topology exists.
* tags:
* - Network Topology
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Network topology retrieved successfully
* content:
* application/json:
* schema:
* type: object
* nullable: true
* properties:
* nodes:
* type: array
* description: Array of graph nodes (hosts and groups)
* items:
* type: object
* properties:
* data:
* type: object
* description: Node data including id, label, type, etc.
* position:
* type: object
* description: Node position coordinates
* properties:
* x:
* type: number
* y:
* type: number
* edges:
* type: array
* description: Array of graph edges (connections)
* items:
* type: object
* properties:
* data:
* type: object
* description: Edge data including source and target node ids
* 401:
* description: User not authenticated
* 500:
* description: Server error
*/
router.get(
"/",
authenticateJWT,
async (req: express.Request, res: express.Response) => {
try {
const userId = (req as AuthenticatedRequest).userId;
if (!userId) {
return res.status(401).json({ error: "User not authenticated" });
}
const record =
await createCurrentNetworkTopologyRepository().findByUserId(userId);
if (record) {
const topologyStr = record.topology;
const topology = topologyStr ? JSON.parse(topologyStr) : null;
return res.json(topology);
} else {
return res.json(null);
}
} catch (error) {
databaseLogger.error("Failed to fetch network topology", error, {
operation: "network_topology_fetch",
userId: (req as AuthenticatedRequest).userId,
});
return res.status(500).json({
error: "Failed to fetch network topology",
details: (error as Error).message,
});
}
},
);
/**
* @openapi
* /network-topology:
* post:
* summary: Save network topology for authenticated user
* description: Saves or updates the network topology graph. Uses upsert logic - creates new record if none exists, updates existing record otherwise.
* tags:
* - Network Topology
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - topology
* properties:
* topology:
* type: object
* description: Network topology data containing nodes and edges
* properties:
* nodes:
* type: array
* description: Array of graph nodes (hosts and groups)
* edges:
* type: array
* description: Array of graph edges (connections)
* responses:
* 200:
* description: Topology saved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* 400:
* description: Invalid topology data
* 401:
* description: User not authenticated
* 500:
* description: Server error
*/
router.post(
"/",
authenticateJWT,
async (req: express.Request, res: express.Response) => {
try {
const userId = (req as AuthenticatedRequest).userId;
if (!userId) {
return res.status(401).json({ error: "User not authenticated" });
}
const { topology } = req.body;
if (!topology) {
return res.status(400).json({ error: "Topology data is required" });
}
const topologyStr =
typeof topology === "string" ? topology : JSON.stringify(topology);
await createCurrentNetworkTopologyRepository().upsertForUser(
userId,
topologyStr,
);
return res.json({ success: true });
} catch (error) {
databaseLogger.error("Failed to save network topology", error, {
operation: "network_topology_save",
userId: (req as AuthenticatedRequest).userId,
});
return res.status(500).json({
error: "Failed to save network topology",
details: (error as Error).message,
});
}
},
);
export default router;