feat(remote-ingress): support auto-deriving ports for remote ingress edges and expose manual/derived port breakdown in API and UI

This commit is contained in:
2026-02-17 14:17:18 +00:00
parent 31a6510d8b
commit 49606ae007
9 changed files with 183 additions and 22 deletions

View File

@@ -47,6 +47,11 @@ export class RemoteIngressManager {
for (const key of keys) {
const edge = await this.storageManager.getJSON<IRemoteIngress>(key);
if (edge) {
// Migration: old edges without autoDerivePorts default to true
if ((edge as any).autoDerivePorts === undefined) {
edge.autoDerivePorts = true;
await this.storageManager.setJSON(key, edge);
}
this.edges.set(edge.id, edge);
}
}
@@ -91,13 +96,28 @@ export class RemoteIngressManager {
/**
* Get the effective listen ports for an edge.
* Returns manual listenPorts if non-empty, otherwise derives ports from tagged routes.
* Manual ports are always included. Auto-derived ports are added (union) when autoDerivePorts is true.
*/
public getEffectiveListenPorts(edge: IRemoteIngress): number[] {
if (edge.listenPorts && edge.listenPorts.length > 0) {
return edge.listenPorts;
}
return this.derivePortsForEdge(edge.id, edge.tags);
const manualPorts = edge.listenPorts || [];
const shouldDerive = edge.autoDerivePorts !== false;
if (!shouldDerive) return [...manualPorts].sort((a, b) => a - b);
const derivedPorts = this.derivePortsForEdge(edge.id, edge.tags);
return [...new Set([...manualPorts, ...derivedPorts])].sort((a, b) => a - b);
}
/**
* Get manual and derived port breakdown for an edge (used in API responses).
* Derived ports exclude any ports already present in the manual list.
*/
public getPortBreakdown(edge: IRemoteIngress): { manual: number[]; derived: number[] } {
const manual = edge.listenPorts || [];
const shouldDerive = edge.autoDerivePorts !== false;
if (!shouldDerive) return { manual, derived: [] };
const manualSet = new Set(manual);
const allDerived = this.derivePortsForEdge(edge.id, edge.tags);
const derived = allDerived.filter((p) => !manualSet.has(p));
return { manual, derived };
}
/**
@@ -107,6 +127,7 @@ export class RemoteIngressManager {
name: string,
listenPorts: number[] = [],
tags?: string[],
autoDerivePorts: boolean = true,
): Promise<IRemoteIngress> {
const id = plugins.uuid.v4();
const secret = plugins.crypto.randomBytes(32).toString('hex');
@@ -118,6 +139,7 @@ export class RemoteIngressManager {
secret,
listenPorts,
enabled: true,
autoDerivePorts,
tags: tags || [],
createdAt: now,
updatedAt: now,
@@ -150,6 +172,7 @@ export class RemoteIngressManager {
updates: {
name?: string;
listenPorts?: number[];
autoDerivePorts?: boolean;
enabled?: boolean;
tags?: string[];
},
@@ -161,6 +184,7 @@ export class RemoteIngressManager {
if (updates.name !== undefined) edge.name = updates.name;
if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
if (updates.autoDerivePorts !== undefined) edge.autoDerivePorts = updates.autoDerivePorts;
if (updates.enabled !== undefined) edge.enabled = updates.enabled;
if (updates.tags !== undefined) edge.tags = updates.tags;
edge.updatedAt = Date.now();