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:
72
app/admin/components/AdminDashboard.tsx
Normal file
72
app/admin/components/AdminDashboard.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import PageBuilder from './PageBuilder';
|
||||
import SettingsEditor from './SettingsEditor';
|
||||
|
||||
interface Props {
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
type Tab = 'pages' | 'settings';
|
||||
|
||||
export default function AdminDashboard({ onLogout }: Props) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('pages');
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
async function handleLogout() {
|
||||
await fetch('/api/admin/auth', { method: 'DELETE' });
|
||||
onLogout();
|
||||
}
|
||||
|
||||
async function handleReload() {
|
||||
setSaving(true);
|
||||
try {
|
||||
await fetch('/api/admin/reload', { method: 'POST' });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="admin-dashboard">
|
||||
<header className="admin-header">
|
||||
<div className="admin-header-left">
|
||||
<h1>Immich Folio</h1>
|
||||
<nav className="admin-tabs">
|
||||
<button
|
||||
className={`admin-tab ${activeTab === 'pages' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('pages')}
|
||||
>
|
||||
Pages
|
||||
</button>
|
||||
<button
|
||||
className={`admin-tab ${activeTab === 'settings' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('settings')}
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="admin-header-right">
|
||||
<button
|
||||
className="admin-btn admin-btn-ghost"
|
||||
onClick={handleReload}
|
||||
disabled={saving}
|
||||
title="Reload config & clear cache"
|
||||
>
|
||||
↻ Reload
|
||||
</button>
|
||||
<button className="admin-btn admin-btn-ghost" onClick={handleLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="admin-main">
|
||||
{activeTab === 'pages' && <PageBuilder />}
|
||||
{activeTab === 'settings' && <SettingsEditor />}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user