feat(10-01): serve the embedded admin SPA at backend.uri with cookie login
- backend.uri prefix (default /backend) mounts the admin API at {prefix}/api/v1
and the embedded SPA shell at {prefix} with an api/ JSON 404 fallback
- cookie transport: an X-Requested-With login sets the HttpOnly summer_admin
cookie and returns no token; the backend guard reads the cookie after Bearer
- CSRF wrapper refuses cookie-only POST/PUT/DELETE without X-Requested-With
- boardwalk package embeds boardwalk/dist, rewrites index.html once per prefix
and sets cache and security headers
- framework admin OpenAPI pipeline (swag, swagger2openapi, openapi-typescript)
with prefix-relative paths and typed envelopes for the tracer routes
- admin/ Vite SPA: login, plugin rail, section panel and read-only list
through the openapi-fetch client typed by the generated schema
This commit is contained in:
42
admin/src/app/controllerRoutes.ts
Normal file
42
admin/src/app/controllerRoutes.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// Controller IDs map one to one onto SPA paths (D-10):
|
||||
// vendor.plugin.controller <-> /vendor/plugin/controller.
|
||||
export interface ControllerParams {
|
||||
vendor: string
|
||||
plugin: string
|
||||
controller: string
|
||||
}
|
||||
|
||||
const SEGMENT = /^[A-Za-z0-9_-]+$/
|
||||
|
||||
export function parseControllerId(id: string): ControllerParams | null {
|
||||
const parts = id.split('.')
|
||||
if (parts.length !== 3 || !parts.every((part) => SEGMENT.test(part))) {
|
||||
return null
|
||||
}
|
||||
const [vendor, plugin, controller] = parts as [string, string, string]
|
||||
return { vendor, plugin, controller }
|
||||
}
|
||||
|
||||
export function controllerPath(id: string): string | null {
|
||||
const params = parseControllerId(id)
|
||||
return params ? `/${params.vendor}/${params.plugin}/${params.controller}` : null
|
||||
}
|
||||
|
||||
export function controllerIdFromParams(params: ControllerParams): string {
|
||||
return `${params.vendor}.${params.plugin}.${params.controller}`
|
||||
}
|
||||
|
||||
export function controllerIdFromPath(path: string): string | null {
|
||||
const parts = path.replace(/^\/+|\/+$/g, '').split('/')
|
||||
if (parts.length < 3) {
|
||||
return null
|
||||
}
|
||||
const id = parts.slice(0, 3).join('.')
|
||||
return parseControllerId(id) ? id : null
|
||||
}
|
||||
|
||||
/** vendor.plugin prefix of a controller ID, used to find the owning plugin. */
|
||||
export function pluginKey(id: string): string | null {
|
||||
const params = parseControllerId(id)
|
||||
return params ? `${params.vendor}.${params.plugin}` : null
|
||||
}
|
||||
Reference in New Issue
Block a user