镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat: configure dashboard base path at runtime - #641

Closed
ZAYEC77 wants to merge 9 commits into
tale:mainfrom
NullRefExcep:feature/configurable-base-path
Closed

ZAYEC77 wants to merge 9 commits into
tale:mainfrom
NullRefExcep:feature/configurable-base-path

Conversation

@ZAYEC77

@ZAYEC77 ZAYEC77 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Headplane currently fixes the dashboard prefix at build time. This adds server.base_path so the same build can serve a custom path. The default remains /admin.

server:
  base_path: /tools/web

The setting is also available through HEADPLANE_SERVER__BASE_PATH and NixOS. Routes, assets, cookies, OIDC URLs, browser SSH, live data, and Docker healthchecks use the configured path.

Production rebases React Router's asset manifest at startup while keeping chunk and CSS imports relative. The HTTP runtime serves the prepared manifest before static files; the application exports React Router's listener directly. An SSR meta tag supplies the path before hydration.

Closes #637.

Compatibility

Paths require a leading slash, non-empty URL-safe segments, and no trailing slash. / and ./.. segments are rejected. Custom paths require matching reverse-proxy and registered OIDC URLs. Replace the removed __INTERNAL_PREFIX build variable with server.base_path.

Validation

  • 285 unit tests and typechecking passed on Node 24.15.0 with pnpm 10.4.0.
  • 13 production/browser tests pass on the minimum supported Node 24.2.0 across /admin, /web, and /tools/web using one build. Routes, assets/fonts, manifests, and sessions/live data have separate cases with shared setup and cleanup. All previous checks remain, with added manifest HEAD and query-string checks. Chromium checks hydration, login, and client navigation under the nested path.
  • Documentation builds and changed-file formatting pass. Lint has an existing unused-parameter warning in app/utils/live-data.tsx; repository-wide formatting also flags two unchanged files.

The runtime adapts React Router's generated build and manifest; the production tests cover this integration when the router is upgraded.

@ZAYEC77
ZAYEC77 requested a review from tale as a code owner October 2, 2026 14:11
@github-actions github-actions Bot added Docs Improvements or additions to documentation UI/UX Related to the frontend UI Authentication Authentication & Permissions Config Related to Headplane specific configuration labels Oct 2, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Runtime server.base_path config — added to the full and partial server schemas with an arktype narrow regex that rejects empty/root paths, trailing slashes, //, and ./.. segments; mirrored in config.example.yaml, nix/options.nix, and the regenerated docs/NixOS-options.md, defaulting to build-time /admin.
  • Runtime basename override — app/server/app.ts sets the process prefix and feeds React Router a patched build (configureBuild in prod) so route matching, SSR handoff, redirects, and the client's window.__reactRouterContext.basename all reflect the configured path.
  • Asset-manifest prefixing — new app/server/assets.ts rewrites assets.url, entry/route module/imports/css, and client chunk modules; Vite base is now ./ in builds and app.ts serves the rewritten manifest dynamically (no-cache) ahead of the stale on-disk copy via dynamicPaths.
  • Client prefix accessor — new ~/utils/prefix getPrefix() replaces all client __PREFIX__ uses (favicon, SSE, SSH window.open, WASM/font URLs, preload); module-level constants were moved into functions to avoid pre-hydration evaluation.
  • Server plumbing — cookie path, OIDC redirect_uri/post-logout, OIDC state cookie, listener basename, and the Docker listen-file healthcheck URL all use config.server.base_path.
  • Tests/docs — new base-path unit + platform integration tests (prod build booted under /admin, /web, /tools/web), an OIDC callback/logout unit, and updated docs/changelog.

I verified the new unit tests (75 passed), the platform integration test (3/3 passed on a real production build), lint (0 errors), and formatting. The pnpm run typecheck failures are pre-existing in untouched test files (describe.sequential on Vitest 5) and unrelated to this PR.

One carry-forward risk (not blocking): the approach depends on React Router internals — the window.__reactRouterContext.basename handoff and the spread-patchable server build object. The integration test is the right guard for that.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This incremental review covers commit 567e5e2f, the only commit since the prior pullfrog review at 1f5bd192.

  • Client prefix now sourced from an SSR-emitted meta tag — app/utils/prefix.ts getPrefix() reads meta[name="headplane-base-path"] in the browser instead of window.__reactRouterContext.basename, and app/root.tsx renders that tag during SSR so the runtime prefix is available before hydration and for SSE, WASM/font, SSH, and favicon URLs.
  • Asset-manifest unit tests — new tests/unit/config/assets.test.ts asserts configureBuild rebases entry/route/module/css/client URLs and leaves the source build unmutated.
  • Prefix unit tests — new tests/unit/config/prefix.test.ts covers the SSR value, the meta-tag read before hydration, and the build-time fallback.
  • Browser integration coverage — extended tests/integration/platform/base-path.test.ts with a Playwright navigation check (hydration, API-key login, client navigation, no stray /admin requests, no console errors) and installed Chromium in CI.
  • Dependency/docs — playwright devDependency + lockfile, nix/package.nix hash bump, and docs/CONTRIBUTING.md browser-test instructions.

I verified the two new unit suites (5 tests passing) and that pnpm install --frozen-lockfile --lockfile-only is consistent. The remaining typecheck failures are pre-existing describe.sequential errors in untouched files; the extra playwright module-not-found errors are local to this environment (dependencies were installed before the PR lockfile) and CI installs it via build.sh's pnpm install --frozen-lockfile.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@ZAYEC77 ZAYEC77 mentioned this pull request Oct 2, 2026

@tale tale left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left 1 nit but there's some bigger structural issues that I don't mind cleaning up.

  • There's quite a few AI generated comments across your changes, I'd appreciate if they were trimmed up and necessary ones were hand-written.
  • We can just drop the full __PREFIX and PREFIX calculation stuff in vite.config.ts, it's still leftover for some reason
  • I'm not a fan of the tests and listener, your AI agent has written some older style TS. We are on Node 24 (soon to be 26 in a few weeks), we have modern APIs.

Comment thread app/server/config/config-schema.ts Outdated
Comment on lines +34 to +39
const basePath = type("string").narrow(
(value, ctx) =>
(/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(value) &&
!value.split("/").some((segment) => segment === "." || segment === "..")) ||
ctx.mustBe("an absolute path of non-empty URL-safe segments without a trailing slash"),
);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly sure that arktype has a type available to us instead of needing to do a custom narrow. You might have to upgrade the version of arktype.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This incremental review covers the commits since the prior pullfrog review at 567e5e2f: the upstream main merge (7d41cf42) and the follow-up refactor (df722994) that addresses the maintainer's requested changes.

  • Removed the build-time prefix entirely — __PREFIX__ is gone from app/globals.d.ts and the Vite/Vitest defines, and the __INTERNAL_PREFIX fallback is gone from react-router.config.ts and the Vite/Vitest PREFIX helpers; server.base_path now defaults to the literal /admin in the schema, cookie/OIDC fallbacks, and getPrefix().
  • Swapped the custom narrow for ArkType's native regex in config-schema.ts, preserving the same validation semantics (leading slash, URL-safe non-empty segments, no trailing slash, no ./..).
  • Modernized the runtime and tests to Node 24 APIs — URL.parse, import.meta.dirname, mkdtempDisposable/await using, AbortSignal.timeout, TextDecoderStream, and getSetCookie(); composeListener is now a single async handler with unified error handling.
  • Trimmed comments across assets.ts, main.ts, app.ts, and the tests, and documented the __INTERNAL_PREFIX removal in CHANGELOG.md and the native-mode guide.

Verified the base-path, prefix, assets, and OIDC unit suites (86 tests) pass, and confirmed the new regex accepts and rejects exactly the cases the unit tests assert. pnpm run typecheck reports only the local playwright-module-not-found errors (the dependency is installed in CI, not in this environment).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This incremental review covers the commits since the prior pullfrog review at df722994: the Nix dependency-hash refresh (4d723b49, carried over) and the documentation and comment trim (4c14d57f).

  • Removed the remaining generated comments in app/server/app.ts, app/utils/prefix.ts, vite.config.ts, and tests/integration/platform/base-path.test.ts.
  • Consolidated the custom-path documentation — docs/configuration/index.md ## Dashboard Path now owns the validation rules and the reverse-proxy/OIDC/cookie migration guidance, while docs/install/native-mode.md and docs/features/sso.md were slimmed to link to it (anchors and __INTERNAL_PREFIX removal verified).
  • Reworded config.example.yaml and docs/CONTRIBUTING.md for brevity.

No behavioral surface in this delta; the runtime, asset-manifest, and config paths were reviewed and approved in the prior runs and are unchanged here.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This incremental review covers db377681, the only commit since the prior pullfrog review at 4c14d57f.

  • Consolidated runtime manifest serving into the listener chain — replaced the dynamicPaths bypass with an assetManifest option so composeListener intercepts GET/HEAD on the generated manifest path and returns the rewritten manifest no-cache ahead of the static handler.
  • Simplified the app entrypoint — app/server/app.ts now exports React Router's createRequestListener({ build: runtimeBuild }) directly; the hand-written wrapping listener that served the manifest and delegated is gone.
  • Streamlined composeListener — a single async handler built on URL.parse, unified try/catch (500 / res.destroy), and stream/promises pipeline for static files.
  • Restructured the platform integration test into per-path describe.each blocks backed by an AsyncDisposableStack, splitting route/asset/manifest/session assertions into separate tests and adding manifest HEAD and query-string cases.

The move is behavior-preserving: production still serves the rewritten manifest before static files (main.ts passes assetManifest from runtimeBuild.assets.url), dev still relies on Vite's manifest, and no dangling dynamicPaths references remain.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@ZAYEC77

ZAYEC77 commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

I left 1 nit but there's some bigger structural issues that I don't mind cleaning up.

  • There's quite a few AI generated comments across your changes, I'd appreciate if they were trimmed up and necessary ones were hand-written.
  • We can just drop the full __PREFIX and PREFIX calculation stuff in vite.config.ts, it's still leftover for some reason
  • I'm not a fan of the tests and listener, your AI agent has written some older style TS. We are on Node 24 (soon to be 26 in a few weeks), we have modern APIs.

@tale thank you for review
I tried to take your comments into account

@ZAYEC77
ZAYEC77 requested a review from tale October 15, 2026 19:26

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This incremental review covers fa46f75b, the only commit since the prior pullfrog review at db377681 — a merge of upstream main into the feature branch.

  • Merged newer upstream main (7d41cf42..b19c638c) while preserving every server.base_path change. The conflict areas all landed correctly: the auth cookie path (context.ts), OIDC redirect/end-session URLs (provider.ts), the OIDC state cookie with upstream's new signing secret, the runtime manifest interception in composeListener, and setServerPrefix in app.ts.
  • Adopted upstream APIs in the base-path code paths — alignOriginScheme origin rewriting in runtime/http.ts, the signed createOidcStateCookie(config), and the autoLink/SSHError renames in app/routes/ssh/page.tsx.
  • Adapted the platform integration test to the merged UI and CSRF behavior: updated login/sort selectors (Sign in to Headplane, API key, Sign in with API key, Name), sent an Origin header on the login POST so it exercises alignOriginScheme, and added a wrong-cookie_secret parse assertion to the OIDC state-cookie test.

Verified the full unit suite (304 tests) passes on the merged tree, and confirmed the base-path plumbing is intact across the resolved files. pnpm run typecheck reports only the local playwright module-not-found error (the dependency installs from the PR lockfile in CI).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@tale

tale commented Oct 27, 2026

Copy link
Copy Markdown
Owner

I'm resolving these conflicts and I'll merge it, LGTM!

@tale

tale commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Merged in #675, which builds on your commits. Thanks!

@tale tale closed this Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Authentication Authentication & Permissions Config Related to Headplane specific configuration Docs Improvements or additions to documentation UI/UX Related to the frontend UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update base path

2 participants