Add a Network > Ports page: agent-reported ports plus manual openings
Summarizes every server's agent-reported listening ports in one cross-server table (grouped by protocol+port, addresses merged, loopback-only flagged) - previously this only existed per-server on each server's own detail page. Adds a second table for ports this app has no way to see on its own: manually-recorded openings on a router, edge firewall, or cloud security group, each with a label, external port/protocol, an optional link to a tracked server (with its own internal port when NAT changes it) or a freeform destination, a free-text source, and a comment. Viewer-readable; adding/editing/deleting needs operator or admin. The agent-port grouping logic (dedupe by protocol+port, detect loopback-only sockets) was shared with the existing per-server Ports card via a new agentPorts.ts service instead of duplicating it. Verified with a real HTTP-level test: a genuine Express app with the actual routers, a scratch SQLite DB, and forged admin/viewer sessions, covering grouping correctness, the server-name join, input validation, and role enforcement - the real dev DB was confirmed untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e48377348
commit
236b1da0dc
14 files changed
+2486
-39
No files matched your search
@@ -331,6 +331,33 @@ export const serverPorts = sqliteTable(
|
||||
(t) => [uniqueIndex("server_ports_unique").on(t.serverId, t.port, t.protocol)],
|
||||
);
|
||||
|
||||
// A manually-recorded port opening on something this app doesn't monitor directly — a router's port forward, an
|
||||
// edge firewall rule, a cloud provider's security group, etc. Distinct from serverPorts (which is what a server
|
||||
// itself, or a scan of it, reports): this is what someone tells the app is open further out on the network path,
|
||||
// for the same reason people keep a spreadsheet of "what did I open on the router and why."
|
||||
export const portForwards = sqliteTable("port_forwards", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
label: text("label").notNull(),
|
||||
externalPort: integer("external_port").notNull(),
|
||||
protocol: text("protocol").$type<"tcp" | "udp">().notNull().default("tcp"),
|
||||
// Optional link to a tracked server this forward points at; "destination" covers anything else (a bare IP,
|
||||
// an untracked device) or extra detail alongside a linked server.
|
||||
serverId: integer("server_id").references(() => servers.id, { onDelete: "set null" }),
|
||||
destination: text("destination"),
|
||||
// The port it's actually forwarded to, when NAT changes it (a router forwarding external 8443 to internal 443).
|
||||
internalPort: integer("internal_port"),
|
||||
// Free text: where this rule actually lives ("Home router", "OPNsense WAN rule", "Cloudflare Tunnel") — this
|
||||
// app has no integration with any firewall/router, so it can't verify or manage the rule, only record it.
|
||||
source: text("source"),
|
||||
comment: text("comment"),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`(current_timestamp)`),
|
||||
updatedAt: text("updated_at")
|
||||
.notNull()
|
||||
.default(sql`(current_timestamp)`),
|
||||
});
|
||||
|
||||
// ─── Domain registrations ───────────────────────────────────────────────────
|
||||
|
||||
export const domainOrigins = ["manual", "zone"] as const;
|
||||
|
||||
Reference in new issue
Block a user