feat(api): add resolveApiExampleUrl function and related tests for dynamic API URL resolution

This commit is contained in:
KodekaTeam committed 2026-09-13 11:47:37 +07:00
1 parent c51c17e590
commit 14411e0cf8
3 files changed
+131 -1

No files matched your search

+22 -1
View File
@@ -7,10 +7,13 @@ import SearchField from "@/components/SearchField";
import TablePagination from "@/components/TablePagination";
import {
apiKeys as apiKeysApi,
getApiBaseUrl,
settingsApi,
type ApiKeyExpiryOption,
type ApiKeyRecord,
type CreateApiKeyBody,
} from "@/lib/api";
import { resolveApiExampleUrl } from "@/lib/api-example-url";
import { useTablePagination } from "@/lib/use-table-pagination";
import {
Activity,
@@ -172,6 +175,7 @@ export default function ApiKeysPage() {
const [generatedKey, setGeneratedKey] = useState<GeneratedKeyState | null>(
null,
);
const [panelUrl, setPanelUrl] = useState<string | null>(null);
const [sessionRawKeys, setSessionRawKeys] = useState<Record<string, string>>(
{},
);
@@ -212,8 +216,16 @@ export default function ApiKeysPage() {
useEffect(() => {
void loadKeys();
void loadPanelUrl();
}, []);
const apiExampleUrl = resolveApiExampleUrl({
panelUrl,
browserOrigin:
typeof window === "undefined" ? null : window.location.origin,
apiBaseUrl: getApiBaseUrl(),
});
async function loadKeys() {
setLoading(true);
setError("");
@@ -347,6 +359,15 @@ export default function ApiKeysPage() {
});
}
async function loadPanelUrl() {
try {
const response = await settingsApi.get();
setPanelUrl(response.data.general.panelUrl);
} catch {
// The active browser origin remains a valid same-origin API fallback.
}
}
async function removeKey(id: string) {
setRemovingId(id);
setError("");
@@ -1136,7 +1157,7 @@ export default function ApiKeysPage() {
fontFamily: "JetBrains Mono, monospace",
}}
>
{`curl -H "x-api-key: ${generatedKey.rawKey}" http://localhost:4000/api/v1/servers`}
{`curl -H "x-api-key: ${generatedKey.rawKey}" ${apiExampleUrl}`}
</code>
</div>
+62
View File
@@ -0,0 +1,62 @@
type ApiExampleUrlOptions = {
panelUrl?: string | null;
browserOrigin?: string | null;
apiBaseUrl?: string | null;
};
function normalizeHttpUrl(value: string | null | undefined) {
const trimmed = value?.trim();
if (!trimmed) return null;
try {
const parsed = new URL(trimmed);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
return null;
}
return parsed;
} catch {
return null;
}
}
function isLoopbackHost(hostname: string) {
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
return (
normalized === "localhost" ||
normalized === "127.0.0.1" ||
normalized === "::1"
);
}
export function resolveApiExampleUrl({
panelUrl,
browserOrigin,
apiBaseUrl,
}: ApiExampleUrlOptions) {
const storedPanelUrl = normalizeHttpUrl(panelUrl);
const runtimeOrigin = normalizeHttpUrl(browserOrigin);
const storedPanelIsStaleLocalDefault =
storedPanelUrl &&
runtimeOrigin &&
isLoopbackHost(storedPanelUrl.hostname) &&
!isLoopbackHost(runtimeOrigin.hostname);
const publicOrigin = storedPanelIsStaleLocalDefault
? runtimeOrigin
: storedPanelUrl || runtimeOrigin;
if (publicOrigin) {
return new URL("/api/v1/servers", publicOrigin.origin).toString();
}
const configuredApiBase = normalizeHttpUrl(apiBaseUrl);
if (configuredApiBase) {
return new URL(
`${configuredApiBase.pathname.replace(/\/+$/, "")}/servers`,
configuredApiBase.origin,
).toString();
}
return "http://localhost:3000/api/v1/servers";
}
+47
View File
@@ -0,0 +1,47 @@
import assert from "node:assert/strict";
import test from "node:test";
import { resolveApiExampleUrl } from "../src/lib/api-example-url";
test("uses the Panel Access URL stored in settings", () => {
assert.equal(
resolveApiExampleUrl({
panelUrl: "https://panel.example.com/",
browserOrigin: "http://192.168.1.20:3000",
apiBaseUrl: "http://localhost:4000/api/v1",
}),
"https://panel.example.com/api/v1/servers",
);
});
test("uses the active browser origin when the stored panel URL is a stale loopback default", () => {
assert.equal(
resolveApiExampleUrl({
panelUrl: "http://localhost:3000",
browserOrigin: "http://192.168.1.20:3000",
apiBaseUrl: "http://localhost:4000/api/v1",
}),
"http://192.168.1.20:3000/api/v1/servers",
);
});
test("keeps the stored loopback URL during local access", () => {
assert.equal(
resolveApiExampleUrl({
panelUrl: "http://localhost:3000",
browserOrigin: "http://127.0.0.1:3000",
apiBaseUrl: "http://localhost:4000/api/v1",
}),
"http://localhost:3000/api/v1/servers",
);
});
test("falls back to the configured API base when no public panel origin is available", () => {
assert.equal(
resolveApiExampleUrl({
panelUrl: "not-a-url",
browserOrigin: null,
apiBaseUrl: "https://api.example.com/api/v1/",
}),
"https://api.example.com/api/v1/servers",
);
});