feat: make each plugin an npm workspace built by a termix-plugin CLI

This commit is contained in:
LukeGus committed 2026-09-22 15:09:50 -05:00
1 parent 4758016f41
commit 5796186a1c
448 files changed
+3087 -2003

No files matched your search

@@ -0,0 +1,14 @@
import type { PluginContext } from "@termix/plugin-sdk/backend";
import {
startNetworkTopologyService,
stopNetworkTopologyService,
} from "./routes.js";
export async function activate(ctx: PluginContext) {
startNetworkTopologyService();
ctx.log.info("Network Topology router registered at /network-topology");
}
export async function deactivate() {
stopNetworkTopologyService();
}
@@ -0,0 +1,188 @@
import express from "express";
import { AuthManager } from "../../../../src/backend/utils/auth-manager.js";
import type { AuthenticatedRequest } from "../../../../src/types/index.js";
import { databaseLogger } from "../../../../src/backend/utils/logger.js";
import { createCurrentNetworkTopologyRepository } from "../../../../src/backend/database/repositories/factory.js";
import {
registerNetworkTopologyRouter,
unregisterNetworkTopologyRouter,
} from "../../../../src/backend/database/routes/network-topology-dispatch.js";
export 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 function startNetworkTopologyService(): void {
registerNetworkTopologyRouter(router);
}
export function stopNetworkTopologyService(): void {
unregisterNetworkTopologyRouter();
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,37 @@
/**
* Frontend half of the network-topology plugin.
*
* NetworkGraphCard renders in two modes: as a full tab (network_graph) and
* embedded as a dashboard card. Both usages are wired today through the
* existing tabUtils.tsx / DashboardTab.tsx switch statements the same way
* fleet-inventory's tab is, not through register() below -- per
* src/ui/shell/pluginLoader.ts's own comment, "there is no plugin loader
* yet" for the frontend half.
*
* register()/unregister() still follow the same shape every other plugin's
* frontend entry does, so this is ready the moment a real frontend plugin
* loader exists.
*/
export const id = "network-topology";
export const tabId = "network_graph";
export async function register({ registerRailItem, registerTabComponent, icons }) {
registerTabComponent?.(tabId, () =>
import("./NetworkGraphCard.tsx").then((m) => ({
default: m.NetworkGraphCard,
})),
);
registerRailItem?.({
id: tabId,
icon: icons.Network,
labelKey: "nav.networkGraph",
kind: "tab",
});
}
export async function unregister({ unregisterRailItem, unregisterTabComponent }) {
unregisterRailItem?.(tabId);
unregisterTabComponent?.(tabId);
}