Merge commit '6aedf21bf6dc495fdda89abadbd383fb3ea9e15e'

This commit is contained in:
2026-06-30 19:52:57 +02:00
8 changed files with 152 additions and 16 deletions
+68
View File
@@ -389,6 +389,31 @@ function Profile({ onMsg }) {
<label>Über mich<textarea value={p.bio} onChange={set('bio')} rows={5} placeholder="Kurzer Text über dich…" /></label>
<div className="actions"><button className="primary" onClick={save} disabled={busy}>Speichern</button></div>
</div>
<PasswordChange onMsg={onMsg} />
</div>
);
}
function PasswordChange({ onMsg }) {
const [cur, setCur] = useState('');
const [nxt, setNxt] = useState('');
const [busy, setBusy] = useState(false);
async function submit(e) {
e.preventDefault();
if (nxt.length < 6) { onMsg({ type: 'err', text: 'Neues Passwort zu kurz (min. 6 Zeichen).' }); return; }
setBusy(true);
try { await api.changePassword(cur, nxt); onMsg({ type: 'ok', text: 'Passwort geändert.' }); setCur(''); setNxt(''); }
catch (e) { onMsg({ type: 'err', text: e.message }); }
finally { setBusy(false); }
}
return (
<div className="profile-card">
<h2>Passwort ändern</h2>
<form onSubmit={submit}>
<label>Aktuelles Passwort<input type="password" value={cur} onChange={(e) => setCur(e.target.value)} autoComplete="current-password" /></label>
<label>Neues Passwort<input type="password" value={nxt} onChange={(e) => setNxt(e.target.value)} autoComplete="new-password" /></label>
<div className="actions"><button className="primary" disabled={busy}>Passwort setzen</button></div>
</form>
</div>
);
}
@@ -431,6 +456,49 @@ function Overview({ onMsg, go }) {
);
}
// ── System (nur Admin): core-Version, Update-Check, Admins, Plugins, Modell ──
function System({ onMsg }) {
const [s, setS] = useState(null);
const [upd, setUpd] = useState(null);
useEffect(() => {
api.system().then(setS).catch((e) => onMsg({ type: 'err', text: e.message }));
api.systemUpdate().then(setUpd).catch(() => {});
}, []);
if (!s) return <div className="empty"></div>;
const badge = !upd ? null
: upd.available ? <span className="status draft">core v{upd.vendored} bereit Rebuild nötig</span>
: upd.vendored ? <span className="status live">aktuell</span>
: <span className="muted">(kein Subtree)</span>;
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> {badge}</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>Admins <span className="count-pill">{(s.admins || []).length}</span></h3>
<ul className="syslist">{(s.admins || []).map((a) => (
<li key={a}><b>{a}</b><span className="muted">aus Config (fix)</span></li>
))}</ul>
<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. Updates werden serverseitig eingespielt (Update-Skript).</p>
</div>
</div>
);
}
// ── Autor:innen-Verwaltung (nur Admin) ──────────────────────────────────────
function Users({ onMsg, currentEmail }) {
const [list, setList] = useState(null);
+2
View File
@@ -46,7 +46,9 @@ export const api = {
getMe: () => call('/me'),
stats: () => call('/stats'),
system: () => call('/system'),
systemUpdate: () => call('/system/update'),
schema: () => call('/schema'),
changePassword: (current, next) => call('/account/password', { method: 'POST', body: JSON.stringify({ current, next }) }),
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 }) }),