Squashed 'cms/core/' changes from 2f52ec7..f709b5d

f709b5d core: stage 6 (part) — /api/schema + /api/system + admin System panel

git-subtree-dir: cms/core
git-subtree-split: f709b5dc8ded7f138b21ac0aad4dd0855cb75aba
This commit is contained in:
2026-06-30 01:06:09 +02:00
parent 80eca1bc7c
commit 42110059c7
7 changed files with 126 additions and 1 deletions
+34
View File
@@ -104,6 +104,7 @@ function Dashboard({ email }) {
{me?.canModerate && <button className={view === 'moderation' ? 'active' : ''} onClick={() => setView('moderation')}>Moderation</button>}
{me?.isAdmin && <button className={view === 'forums' ? 'active' : ''} onClick={() => setView('forums')}>Foren</button>}
{me?.isAdmin && <button className={view === 'users' ? 'active' : ''} onClick={() => setView('users')}>Autor:innen</button>}
{me?.isAdmin && <button className={view === 'system' ? 'active' : ''} onClick={() => setView('system')}>System</button>}
</nav>
<span className="spacer" />
<span className="who">{email}</span>
@@ -121,6 +122,8 @@ function Dashboard({ email }) {
<Forums onMsg={setMsg} />
) : view === 'moderation' ? (
<Moderation onMsg={setMsg} />
) : view === 'system' ? (
<System onMsg={setMsg} />
) : (
<>
<aside>
@@ -445,6 +448,37 @@ function Overview({ onMsg, go }) {
);
}
// ── System (nur Admin): core-Version, Plugins, Content-Modell ────────────────
function System({ onMsg }) {
const [s, setS] = useState(null);
useEffect(() => { api.system().then(setS).catch((e) => onMsg({ type: 'err', text: e.message })); }, []);
if (!s) return <div className="empty"></div>;
return (
<div className="profile">
<div className="profile-card wide">
<h2>System</h2>
<div className="sysgrid">
<div><span className="muted">Core</span><b>{s.core.name} <span className="ver">v{s.core.version}</span></b></div>
<div><span className="muted">Site</span><b>{s.site || '—'}</b></div>
<div><span className="muted">Auth</span><b>{s.auth}</b></div>
<div><span className="muted">Hugo</span><b>{s.hugo}</b></div>
</div>
<h3>Plugins <span className="count-pill">{s.plugins.length}</span></h3>
{s.plugins.length
? <ul className="syslist">{s.plugins.map((p) => (
<li key={p.name}><b>{p.name}</b><span className="muted">{p.routes} Routen · {p.migrations} Migration(en)</span></li>
))}</ul>
: <p className="muted">Keine Plugins aktiv.</p>}
<h3>Content-Modell <span className="count-pill">{s.collections.length}</span></h3>
<ul className="syslist">{s.collections.map((c) => (
<li key={c.kind}><b>{c.label}</b><span className="muted">{c.kind} · {c.fields} Felder{c.statKey ? ` · ${c.statKey}` : ''}</span></li>
))}</ul>
<p className="muted who-mail">openbureau-core fährt diese Site. Plugins &amp; Content-Modell kommen aus der Site-Config.</p>
</div>
</div>
);
}
// ── Autor:innen-Verwaltung (nur Admin) ──────────────────────────────────────
function Users({ onMsg, currentEmail }) {
const [list, setList] = useState(null);
+2
View File
@@ -45,6 +45,8 @@ export const api = {
saveProfile: (p) => call('/profile', { method: 'PUT', body: JSON.stringify(p) }),
getMe: () => call('/me'),
stats: () => call('/stats'),
system: () => call('/system'),
schema: () => call('/schema'),
listUsers: () => call('/users'),
createUser: (email, password, role) => call('/users', { method: 'POST', body: JSON.stringify({ email, password, role }) }),
setPassword: (id, password) => call(`/users/${id}`, { method: 'PUT', body: JSON.stringify({ password }) }),
+10
View File
@@ -219,3 +219,13 @@ label.big input { font-family: var(--serif); font-weight: 600; }
.toast { position: fixed; bottom: 20px; right: 20px; padding: 11px 18px; border-radius: 11px; color: #fff; cursor: pointer; box-shadow: 0 10px 30px -12px rgba(0,0,0,.4); font-size: 13.5px; max-width: 380px; z-index: 50; }
.toast.ok { background: var(--ok); }
.toast.err { background: var(--accent); }
/* System-Panel */
.sysgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .75rem; margin: .5rem 0 1rem; }
.sysgrid > div { display: flex; flex-direction: column; gap: .15rem; }
.sysgrid .muted { font-size: .8rem; }
.sysgrid b { font-size: 1.05rem; }
.sysgrid .ver { opacity: .6; font-weight: 400; }
.syslist { list-style: none; padding: 0; margin: .25rem 0 1rem; }
.syslist li { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; padding: .4rem 0; border-bottom: 1px solid var(--line); }
.syslist li .muted { font-size: .85rem; }