feat(admin-ui): add configurable Admin UI domain routing

This commit is contained in:
2026-05-24 14:46:35 +00:00
parent a86d83f835
commit d91fda084b
11 changed files with 551 additions and 38 deletions
+241
View File
@@ -173,6 +173,47 @@ Deno.test('ExternalGatewayManager syncs service routes to dcrouter gatewayClient
assertEquals(syncRequest.requestData.enabled, true);
});
Deno.test('ExternalGatewayManager syncs Admin UI route to dcrouter gatewayClient API', async () => {
const oneboxRef = makeOneboxRef();
oneboxRef.database.settings.set('adminUiDomain', 'Onebox.Example.com');
oneboxRef.database.settings.set('serverIP', '203.0.113.10');
oneboxRef.database.settings.set('httpPort', '8080');
const requests: Array<{ method: string; requestData: Record<string, unknown> }> = [];
const manager = new ExternalGatewayManager(oneboxRef as any);
(manager as any).fireDcRouterRequest = async (
method: string,
requestData: Record<string, unknown>,
) => {
if (method === 'getGatewayClientContext') {
return {
context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } },
};
}
requests.push({ method, requestData });
if (method === 'exportCertificate') {
return { success: false };
}
return { success: true, action: 'created', routeId: 'admin-route' };
};
await manager.syncAdminUiRoute();
const syncRequest = requests.find((request) => request.method === 'syncGatewayClientRoute')!;
const route = syncRequest.requestData.route as any;
const ownership = syncRequest.requestData.ownership as any;
assertEquals(ownership, {
gatewayClientType: 'onebox',
gatewayClientId: 'onebox-token',
appId: 'onebox-admin-ui',
hostname: 'onebox.example.com',
});
assertEquals(route.match, { ports: [443], domains: ['onebox.example.com'] });
assertEquals(route.action.targets, [{ host: '203.0.113.10', port: 8080 }]);
assertEquals(syncRequest.requestData.enabled, true);
});
Deno.test('ExternalGatewayManager uses managed dcrouter local target in managed mode', async () => {
const oneboxRef = makeOneboxRef();
(oneboxRef as any).managedDcRouter = {
@@ -322,6 +363,206 @@ Deno.test('ExternalGatewayManager removes stale gateway routes during reconcilia
assertEquals((deletes[0].ownership as any).hostname, 'stale.example.com');
});
Deno.test('ExternalGatewayManager preserves configured Admin UI route during reconciliation', async () => {
const oneboxRef = makeOneboxRef();
oneboxRef.database.settings.set('adminUiDomain', 'onebox.example.com');
oneboxRef.database.settings.set('serverIP', '203.0.113.10');
oneboxRef.database.services.push({
id: 1,
name: 'active',
image: 'nginx:latest',
envVars: {},
port: 3000,
domain: 'active.example.com',
status: 'running',
createdAt: 1,
updatedAt: 1,
});
const deletes: Record<string, unknown>[] = [];
const manager = new ExternalGatewayManager(oneboxRef as any);
(manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
if (method === 'getGatewayClientContext') {
return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
}
if (method === 'syncGatewayClientRoute') {
if (requestData.delete) {
deletes.push(requestData);
return { success: true, action: 'deleted' };
}
return { success: true, action: 'updated' };
}
if (method === 'exportCertificate') {
return { success: false };
}
if (method === 'getGatewayClientDnsRecords') {
return {
records: [
{
id: 'admin-record',
domainId: 'domain-1',
name: 'onebox',
type: 'A',
value: '203.0.113.10',
ttl: 300,
source: 'route',
status: 'active',
gatewayClientType: 'onebox',
gatewayClientId: 'onebox-token',
appId: 'onebox-admin-ui',
hostname: 'onebox.example.com',
routeId: 'admin-route',
},
{
id: 'stale-record',
domainId: 'domain-1',
name: 'stale',
type: 'A',
value: '203.0.113.10',
ttl: 300,
source: 'route',
status: 'active',
gatewayClientType: 'onebox',
gatewayClientId: 'onebox-token',
appId: 'stale',
hostname: 'stale.example.com',
routeId: 'stale-route',
},
],
};
}
throw new Error(`Unexpected method: ${method}`);
};
await manager.syncServiceRoutes();
assertEquals(deletes.length, 1);
assertEquals((deletes[0].ownership as any).hostname, 'stale.example.com');
});
Deno.test('ExternalGatewayManager preserves legacy Admin UI route when setting is absent', async () => {
const oneboxRef = makeOneboxRef();
oneboxRef.database.settings.set('serverIP', '203.0.113.10');
const deletes: Record<string, unknown>[] = [];
const manager = new ExternalGatewayManager(oneboxRef as any);
(manager as any).fireDcRouterRequest = async (
method: string,
requestData: Record<string, unknown>,
) => {
if (method === 'getGatewayClientContext') {
return {
context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } },
};
}
if (method === 'syncGatewayClientRoute') {
if (requestData.delete) {
deletes.push(requestData);
return { success: true, action: 'deleted' };
}
return { success: true, action: 'updated' };
}
if (method === 'getGatewayClientDnsRecords') {
return {
records: [
{
id: 'legacy-admin-record',
domainId: 'domain-1',
name: 'onebox',
type: 'A',
value: '203.0.113.10',
ttl: 300,
source: 'route',
status: 'active',
gatewayClientType: 'onebox',
gatewayClientId: 'onebox-token',
appId: 'onebox',
hostname: 'onebox.example.com',
routeId: 'legacy-admin-route',
},
{
id: 'stale-record',
domainId: 'domain-1',
name: 'stale',
type: 'A',
value: '203.0.113.10',
ttl: 300,
source: 'route',
status: 'active',
gatewayClientType: 'onebox',
gatewayClientId: 'onebox-token',
appId: 'stale',
hostname: 'stale.example.com',
routeId: 'stale-route',
},
],
};
}
throw new Error(`Unexpected method: ${method}`);
};
await manager.syncServiceRoutes();
assertEquals(deletes.length, 1);
assertEquals((deletes[0].ownership as any).hostname, 'stale.example.com');
});
Deno.test('ExternalGatewayManager deletes old Admin UI route after domain change', async () => {
const oneboxRef = makeOneboxRef();
oneboxRef.database.settings.set('adminUiDomain', 'new.example.com');
oneboxRef.database.settings.set('serverIP', '203.0.113.10');
const deletes: Record<string, unknown>[] = [];
const manager = new ExternalGatewayManager(oneboxRef as any);
(manager as any).fireDcRouterRequest = async (
method: string,
requestData: Record<string, unknown>,
) => {
if (method === 'getGatewayClientContext') {
return {
context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } },
};
}
if (method === 'syncGatewayClientRoute') {
if (requestData.delete) {
deletes.push(requestData);
return { success: true, action: 'deleted' };
}
return { success: true, action: 'updated' };
}
if (method === 'exportCertificate') {
return { success: false };
}
if (method === 'getGatewayClientDnsRecords') {
return {
records: [
{
id: 'old-admin-record',
domainId: 'domain-1',
name: 'onebox',
type: 'A',
value: '203.0.113.10',
ttl: 300,
source: 'route',
status: 'active',
gatewayClientType: 'onebox',
gatewayClientId: 'onebox-token',
appId: 'onebox-admin-ui',
hostname: 'old.example.com',
routeId: 'old-admin-route',
},
],
};
}
throw new Error(`Unexpected method: ${method}`);
};
await manager.syncServiceRoutes();
assertEquals(deletes.length, 1);
assertEquals((deletes[0].ownership as any).hostname, 'old.example.com');
});
Deno.test('ExternalGatewayManager imports exported dcrouter certificates into Onebox', async () => {
const oneboxRef = makeOneboxRef();
const manager = new ExternalGatewayManager(oneboxRef as any);