fix(proxy): show registry provider proxies in dashboard (#2963)

Integrated into release/v3.8.8
This commit is contained in:
terence71-glitch
2026-05-30 18:14:53 -07:00
committed by GitHub
parent 4536aabe23
commit e2ad12d090
3 changed files with 86 additions and 6 deletions

View File

@@ -1619,6 +1619,19 @@ export default function ProviderDetailPage() {
}
}, [providerId, isSearchProvider]);
const fetchProxyConfig = useCallback(async () => {
try {
const res = await fetch("/api/settings/proxy", { cache: "no-store" });
if (res.ok) {
setProxyConfig(await res.json());
} else {
setProxyConfig(null);
}
} catch {
// Proxy indicators are best-effort.
}
}, []);
const fetchConnections = useCallback(async () => {
try {
const [connectionsRes, nodesRes] = await Promise.all([
@@ -1680,11 +1693,8 @@ export default function ProviderDetailPage() {
fetchConnections();
fetchAliases();
// Load proxy config for visual indicators (provider-level button)
fetch("/api/settings/proxy")
.then((r) => (r.ok ? r.json() : null))
.then((c) => setProxyConfig(c))
.catch(() => {});
}, [fetchConnections, fetchAliases]);
void fetchProxyConfig();
}, [fetchConnections, fetchAliases, fetchProxyConfig]);
const handleZedImport = useCallback(async () => {
if (importingZed) return;
@@ -4895,7 +4905,10 @@ export default function ProviderDetailPage() {
level={proxyTarget.level}
levelId={proxyTarget.id}
levelLabel={proxyTarget.label}
onSaved={() => void loadConnProxies(connections)}
onSaved={() => {
void fetchProxyConfig();
void loadConnProxies(connections);
}}
/>
)}
{/* Import Progress Modal */}

View File

@@ -160,6 +160,30 @@ export async function GET(request: Request) {
return Response.json({ level, id, proxy });
}
if (level === "provider" && id) {
const assignments = await getProxyAssignments({ scope: "provider" });
const assignment = assignments.find((entry) => entry.scopeId === id);
if (assignment?.proxyId) {
const proxyData = await getProxyById(assignment.proxyId, { includeSecrets: true });
if (proxyData) {
return Response.json({
level,
id,
proxy: {
type: proxyData.type,
host: proxyData.host,
port: proxyData.port,
username: proxyData.username,
password: proxyData.password,
},
});
}
}
const proxy = await getProxyForLevel(level, id);
return Response.json({ level, id, proxy });
}
if (level) {
const proxy = await getProxyForLevel(level, id);
return Response.json({ level, id, proxy });
@@ -167,6 +191,24 @@ export async function GET(request: Request) {
// Get full config
const config = await getProxyConfig();
const providerAssignments = await getProxyAssignments({ scope: "provider" });
if (providerAssignments.length > 0) {
config.providers = { ...(config.providers || {}) };
for (const assignment of providerAssignments) {
if (!assignment.scopeId || !assignment.proxyId) {
continue;
}
const proxyData = await getProxyById(assignment.proxyId, { includeSecrets: true });
if (!proxyData) continue;
config.providers[assignment.scopeId] = {
type: proxyData.type,
host: proxyData.host,
port: proxyData.port,
username: proxyData.username,
password: proxyData.password,
};
}
}
return Response.json(config);
} catch (error) {
return createErrorResponseFromUnknown(error, "Failed to load proxy config");

View File

@@ -291,6 +291,22 @@ test("settings proxy route covers full config, resolve, validation, delete and g
const fullConfig = await settingsProxyRoute.GET(
new Request("http://localhost/api/settings/proxy")
);
const registryProviderProxy = await localDb.createProxy({
name: "Registry Provider Proxy",
type: "http",
host: "registry-provider.local",
port: 8080,
source: "dashboard-custom",
});
await localDb.assignProxyToScope("provider", "anthropic", registryProviderProxy.id);
const registryProviderGet = await settingsProxyRoute.GET(
new Request("http://localhost/api/settings/proxy?level=provider&id=anthropic")
);
const fullConfigWithRegistryProvider = await settingsProxyRoute.GET(
new Request("http://localhost/api/settings/proxy")
);
const deleted = await settingsProxyRoute.DELETE(
new Request("http://localhost/api/settings/proxy?level=provider&id=openai", {
method: "DELETE",
@@ -310,6 +326,8 @@ test("settings proxy route covers full config, resolve, validation, delete and g
const providerGetBody = (await providerGet.json()) as any;
const resolveBody = (await resolveGet.json()) as any;
const fullConfigBody = (await fullConfig.json()) as any;
const registryProviderGetBody = (await registryProviderGet.json()) as any;
const fullConfigWithRegistryProviderBody = (await fullConfigWithRegistryProvider.json()) as any;
const deletedBody = (await deleted.json()) as any;
const resolveAfterDeleteBody = (await resolveAfterDelete.json()) as any;
const missingLevelBody = (await missingLevel.json()) as any;
@@ -329,6 +347,13 @@ test("settings proxy route covers full config, resolve, validation, delete and g
assert.equal(resolveBody.proxy.host, "provider.local");
assert.equal(fullConfig.status, 200);
assert.equal(fullConfigBody.global.host, "global.local");
assert.equal(registryProviderGet.status, 200);
assert.equal(registryProviderGetBody.proxy.host, "registry-provider.local");
assert.equal(fullConfigWithRegistryProvider.status, 200);
assert.equal(
fullConfigWithRegistryProviderBody.providers.anthropic.host,
"registry-provider.local"
);
assert.equal(deleted.status, 200);
assert.equal(Object.prototype.hasOwnProperty.call(deletedBody.providers, "openai"), false);
assert.equal(resolveAfterDelete.status, 200);