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
+43 -18
View File
@@ -4,6 +4,7 @@ import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireAdminIdentity } from '../helpers/guards.ts';
import { logger } from '../../logging.ts';
import { getErrorMessage } from '../../utils/error.ts';
import { isValidHostname, normalizeHostname } from '../../utils/domain.ts';
export class SettingsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
@@ -23,6 +24,7 @@ export class SettingsHandler {
return {
cloudflareToken: cloudflareToken || '',
cloudflareZoneId: settingsMap['cloudflareZoneId'] || '',
adminUiDomain: settingsMap['adminUiDomain'] || '',
dcrouterMode: managedDcRouter.getMode(),
dcrouterManagedImage: managedDcRouter.getImage(),
dcrouterManagedOpsPort: managedDcRouter.getOpsPort(),
@@ -64,8 +66,10 @@ export class SettingsHandler {
const db = this.opsServerRef.oneboxRef.database;
const updates = dataArg.settings;
const normalizedUpdates = this.normalizeUpdates(updates);
// Store each setting as key-value pair
for (const [key, value] of Object.entries(updates)) {
for (const [key, value] of Object.entries(normalizedUpdates)) {
if (value !== undefined) {
if (db.isSecretSettingKey(key)) {
await db.setSecretSetting(key, String(value));
@@ -75,8 +79,8 @@ export class SettingsHandler {
}
}
if (this.hasExternalGatewaySetting(updates)) {
this.refreshDcRouterGateway().catch((error) => {
if (this.hasRouteSyncSetting(normalizedUpdates)) {
this.refreshGatewayRoutes(normalizedUpdates).catch((error) => {
logger.warn(`dcrouter gateway settings refresh failed: ${getErrorMessage(error)}`);
});
}
@@ -110,8 +114,23 @@ export class SettingsHandler {
);
}
private hasExternalGatewaySetting(settings: Partial<interfaces.data.ISettings>): boolean {
private normalizeUpdates(
settings: Partial<interfaces.data.ISettings>,
): Partial<interfaces.data.ISettings> {
const normalizedUpdates = { ...settings };
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'adminUiDomain')) {
const normalizedDomain = normalizeHostname(String(normalizedUpdates.adminUiDomain || ''));
if (!isValidHostname(normalizedDomain)) {
throw new plugins.typedrequest.TypedResponseError('Invalid Admin UI domain');
}
normalizedUpdates.adminUiDomain = normalizedDomain;
}
return normalizedUpdates;
}
private hasRouteSyncSetting(settings: Partial<interfaces.data.ISettings>): boolean {
return [
'adminUiDomain',
'dcrouterMode',
'dcrouterManagedImage',
'dcrouterManagedOpsPort',
@@ -127,23 +146,29 @@ export class SettingsHandler {
].some((key) => Object.prototype.hasOwnProperty.call(settings, key));
}
private async refreshDcRouterGateway(): Promise<void> {
private hasManagedDcRouterRuntimeSetting(settings: Partial<interfaces.data.ISettings>): boolean {
return [
'dcrouterMode',
'dcrouterManagedImage',
'dcrouterManagedOpsPort',
'dcrouterManagedHttpPort',
'dcrouterManagedHttpsPort',
'dcrouterManagedDataDir',
].some((key) => Object.prototype.hasOwnProperty.call(settings, key));
}
private async refreshGatewayRoutes(settings: Partial<interfaces.data.ISettings>): Promise<void> {
const onebox = this.opsServerRef.oneboxRef;
if (onebox.managedDcRouter.getMode() === 'managed') {
await onebox.managedDcRouter.restart();
} else {
await onebox.managedDcRouter.stop();
if (this.hasManagedDcRouterRuntimeSetting(settings)) {
if (onebox.managedDcRouter.getMode() === 'managed') {
await onebox.managedDcRouter.restart();
} else {
await onebox.managedDcRouter.stop();
}
}
await onebox.reverseProxy.reloadRoutes();
await onebox.externalGateway.syncDomains();
const services = onebox.database.getAllServices().filter((service) => service.domain);
await Promise.all(services.map(async (service) => {
try {
await onebox.externalGateway.syncServiceRoute(service);
} catch (error) {
logger.warn(`Failed to sync external gateway route for ${service.domain}: ${getErrorMessage(error)}`);
}
}));
await onebox.externalGateway.syncServiceRoutes();
}
}