diff --git a/services/web-ui/public/screens-admin.jsx b/services/web-ui/public/screens-admin.jsx index e2e8c79..6b98a5e 100644 --- a/services/web-ui/public/screens-admin.jsx +++ b/services/web-ui/public/screens-admin.jsx @@ -85,6 +85,7 @@ function Users() { const [tab, setTab] = React.useState("users"); const [showInvite, setShowInvite] = React.useState(false); const [editingUser, setEditingUser] = React.useState(null); + const [resetUser, setResetUser] = React.useState(null); const [menuFor, setMenuFor] = React.useState(null); // row id whose menu is open const refreshUsers = React.useCallback(() => { @@ -139,15 +140,7 @@ function Users() { .catch(e => alert('Delete failed: ' + e.message)); }; - const resetPassword = (u) => { - setMenuFor(null); - const pw = prompt(`Reset password for ${u.username}\n\nNew password (≥ 8 characters):`); - if (!pw) return; - if (pw.length < 8) { alert('Password must be at least 8 characters.'); return; } - window.ZAMPP_API.fetch('/users/' + u.id, { method: 'PATCH', body: JSON.stringify({ password: pw }) }) - .then(() => alert('Password reset for ' + u.username)) - .catch(e => alert('Reset failed: ' + e.message)); - }; + const resetPassword = (u) => { setMenuFor(null); setResetUser(u); }; const changeRole = (u, newRole) => { if (u.role === newRole) return; @@ -253,6 +246,12 @@ function Users() { onSaved={() => { setEditingUser(null); refreshUsers(); }} /> )} + {resetUser && ( + setResetUser(null)} + onSaved={() => setResetUser(null)} + /> + )} ); } @@ -293,6 +292,77 @@ function EditUserModal({ user, onClose, onSaved }) { ); } +function PasswordResetModal({ user, onClose, onSaved }) { + const [pw, setPw] = React.useState(''); + const [pw2, setPw2] = React.useState(''); + const [show, setShow] = React.useState(false); + const [saving, setSaving] = React.useState(false); + const [err, setErr] = React.useState(null); + const [done, setDone] = React.useState(false); + + const valid = pw.length >= 8 && pw === pw2; + const submit = () => { + if (!valid) return; + setSaving(true); setErr(null); + window.ZAMPP_API.fetch('/users/' + user.id, { method: 'PATCH', body: JSON.stringify({ password: pw }) }) + .then(() => { setSaving(false); setDone(true); setTimeout(onSaved, 1200); }) + .catch(e => { setSaving(false); setErr(e.message); }); + }; + + return ( +
+
e.stopPropagation()}> +
+
Reset password · @{user.username}
+ +
+
+ {done ? ( +
+ Password updated. +
+ ) : (<> +
+ +
+ setPw(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter' && valid) submit(); }} + style={{ paddingRight: 36 }} /> + +
+
0 && pw.length < 8 ? 'var(--danger)' : 'var(--text-3)', marginTop: 4 }}> + Minimum 8 characters +
+
+
+ + setPw2(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter' && valid) submit(); }} /> + {pw2.length > 0 && pw !== pw2 && ( +
Passwords do not match
+ )} +
+ {err &&
{err}
} + )} +
+ {!done && ( +
+ + +
+ )} +
+
+ ); +} + function GroupsPanel({ groups, users, onChange }) { const [creating, setCreating] = React.useState(false); const [newName, setNewName] = React.useState(''); @@ -428,7 +498,6 @@ function GroupsPanel({ groups, users, onChange }) { function Tokens() { const [burned, setBurned] = React.useState(14340); const [rate, setRate] = React.useState(2.4); - const [showCalc, setShowCalc] = React.useState(false); React.useEffect(() => { const i = setInterval(() => { @@ -776,8 +845,8 @@ function Containers() { {containers !== null && containers.length === 0 && (
🐳
-
No container data available
-
Container metrics endpoint not yet wired
+
No containers returned
+
Confirm /var/run/docker.sock is mounted in the mam-api container
)} {containers !== null && containers.length > 0 && ( @@ -1226,7 +1295,6 @@ function GpuSettingsCard() { }; if (!cfg) return
Loading…
; - const set = (k, v) => setCfg(c => ({ ...c, [k]: v })); const gpuEnabled = cfg.gpu_transcode_enabled === 'true' || cfg.gpu_transcode_enabled === true;