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:
45
admin/src/components/shell/SectionPanel.vue
Normal file
45
admin/src/components/shell/SectionPanel.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
import type { NavigationEntry } from '../../api/types'
|
||||
import { controllerIdFromPath, controllerPath } from '../../app/controllerRoutes'
|
||||
import { iconFor } from '../../app/icons'
|
||||
import { t } from '../../app/i18n'
|
||||
|
||||
const props = defineProps<{ entry: NavigationEntry }>()
|
||||
const route = useRoute()
|
||||
|
||||
const currentController = computed(() => controllerIdFromPath(route.path))
|
||||
|
||||
const items = computed(() =>
|
||||
props.entry.sideMenu.flatMap((item) => {
|
||||
const to = controllerPath(item.controller)
|
||||
if (!to) {
|
||||
return []
|
||||
}
|
||||
return [{ item, to, icon: iconFor(item.icon), current: currentController.value === item.controller }]
|
||||
}),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside
|
||||
:aria-label="t('backend::lang.nav.sections')"
|
||||
class="flex w-panel shrink-0 flex-col gap-0.5 border-r border-border bg-surface px-3 py-3.5"
|
||||
>
|
||||
<div class="mb-3.5 flex h-10 items-center px-2">
|
||||
<span class="text-[16px] font-bold">{{ entry.label }}</span>
|
||||
</div>
|
||||
<RouterLink
|
||||
v-for="link in items"
|
||||
:key="link.item.code"
|
||||
:to="link.to"
|
||||
:aria-current="link.current ? 'page' : undefined"
|
||||
:class="link.current ? 'bg-sel font-semibold text-text' : 'font-medium text-muted hover:bg-hover hover:text-text'"
|
||||
class="flex h-nav items-center gap-3 rounded-control px-3 no-underline"
|
||||
>
|
||||
<component :is="link.icon" :size="18" aria-hidden="true" />
|
||||
<span>{{ link.item.label }}</span>
|
||||
</RouterLink>
|
||||
</aside>
|
||||
</template>
|
||||
Reference in New Issue
Block a user