feat: add visual admin panel with page builder and settings editor

- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
This commit is contained in:
ralksta
2026-05-29 17:04:37 +02:00
parent 5887706775
commit 9e52d7a0b8
18 changed files with 3127 additions and 1 deletions

View File

@@ -19,6 +19,11 @@ export * from './theme';
let _config: AppConfig | null = null;
let _fallbackSecret: string | null = null;
/** Invalidate the cached config so the next getConfig() call re-reads YAML files. */
export function invalidateConfigCache(): void {
_config = null;
}
/** Converts raw YAML grid overrides into a typed partial GridConfig. */
export function buildSubpageGrid(raw?: {
columns?: number;
@@ -95,6 +100,7 @@ export function getConfig(): AppConfig {
map: false,
transitions: false,
albumOverrides: {},
albumDescriptions: {},
albumPasswords: {},
cacheTtl: env.CACHE_TTL * 1000,
rateLimitRpm: env.RATE_LIMIT_RPM,
@@ -107,10 +113,13 @@ export function getConfig(): AppConfig {
const theme = resolveTheme(settings.theme);
const albumOverrides: Record<string, string> = {};
const albumDescriptions: Record<string, string> = {};
const albumPasswords: Record<string, string> = {};
function processAlbumEntry(
entry: string | Record<string, string | { title: string; password?: string }>,
entry:
| string
| Record<string, string | { title: string; description?: string; password?: string }>,
context: string,
): string {
if (typeof entry === 'string') {
@@ -123,6 +132,7 @@ export function getConfig(): AppConfig {
albumOverrides[validatedUuid] = value;
} else {
if (value.title) albumOverrides[validatedUuid] = value.title;
if (value.description) albumDescriptions[validatedUuid] = value.description;
if (value.password) albumPasswords[validatedUuid] = value.password;
}
return validatedUuid;
@@ -271,6 +281,7 @@ export function getConfig(): AppConfig {
map: settings.map === true,
transitions: settings.transitions !== false,
albumOverrides,
albumDescriptions,
albumPasswords,
cacheTtl: env.CACHE_TTL * 1000,
rateLimitRpm: env.RATE_LIMIT_RPM,