feat(admin-ui): add configurable Admin UI domain routing
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user