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:
Jakub Zych
2026-09-27 15:21:48 +02:00
parent 8c3e131111
commit 5f9353841b
79 changed files with 10745 additions and 147 deletions

111
admin/src/app/icons.ts Normal file
View File

@@ -0,0 +1,111 @@
// Navigation icons (D-11). The registry stores lucide names; ported Winter
// plugins may still send icon-* names, which map onto lucide equivalents.
// Named imports only: a namespace import would bundle every lucide icon.
import type { Component } from 'vue'
import {
Archive,
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowUpRight,
CassetteTape,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
Circle,
CircleAlert,
Disc,
Disc3,
Image,
Library,
List,
LogOut,
MicVocal,
Minus,
Palette,
PanelLeftClose,
PanelLeftOpen,
Plus,
Puzzle,
Search,
SearchX,
Settings,
ShieldCheck,
Square,
Sun,
Tags,
Trash2,
User,
UserMinus,
UserPlus,
Users,
UsersRound,
X,
} from '@lucide/vue'
export const icons: Readonly<Record<string, Component>> = {
archive: Archive,
'arrow-down': ArrowDown,
'arrow-left': ArrowLeft,
'arrow-right': ArrowRight,
'arrow-up': ArrowUp,
'arrow-up-right': ArrowUpRight,
'cassette-tape': CassetteTape,
check: Check,
'chevron-down': ChevronDown,
'chevron-left': ChevronLeft,
'chevron-right': ChevronRight,
circle: Circle,
'circle-alert': CircleAlert,
disc: Disc,
'disc-3': Disc3,
image: Image,
library: Library,
list: List,
'log-out': LogOut,
'mic-vocal': MicVocal,
minus: Minus,
palette: Palette,
'panel-left-close': PanelLeftClose,
'panel-left-open': PanelLeftOpen,
plus: Plus,
puzzle: Puzzle,
search: Search,
'search-x': SearchX,
settings: Settings,
'shield-check': ShieldCheck,
sun: Sun,
tags: Tags,
'trash-2': Trash2,
user: User,
'user-minus': UserMinus,
'user-plus': UserPlus,
users: Users,
'users-round': UsersRound,
x: X,
}
/** Winter backend icon classes mapped to lucide names. */
export const winterIcons: Readonly<Record<string, string>> = {
'icon-archive': 'archive',
'icon-circle': 'circle',
'icon-list-ul': 'list',
'icon-tags': 'tags',
'icon-user': 'user',
'icon-search': 'search',
'icon-cog': 'settings',
'icon-users': 'users',
}
/** Neutral icon for names neither lucide map knows. */
export const fallbackIcon: Component = Square
export function iconFor(name: string | null | undefined): Component {
if (!name) {
return fallbackIcon
}
const key = name.trim()
return icons[key] ?? icons[winterIcons[key] ?? ''] ?? fallbackIcon
}