Changelog: Build-Zeitpunkt als Timestamp, Benutzer ausblenden Toggle

This commit is contained in:
2026-05-29 09:35:30 +02:00
parent 3ca1ab44a6
commit c3047ac4d6
3 changed files with 36 additions and 2 deletions

View File

@@ -252,6 +252,8 @@ if (!userCols2.includes('preferences'))
db.exec("ALTER TABLE users ADD COLUMN preferences TEXT DEFAULT NULL"); db.exec("ALTER TABLE users ADD COLUMN preferences TEXT DEFAULT NULL");
if (!userCols2.includes('last_active_at')) if (!userCols2.includes('last_active_at'))
db.exec("ALTER TABLE users ADD COLUMN last_active_at DATETIME DEFAULT NULL"); db.exec("ALTER TABLE users ADD COLUMN last_active_at DATETIME DEFAULT NULL");
if (!userCols2.includes('hidden'))
db.exec("ALTER TABLE users ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0");
// ── Login-Sicherheit ────────────────────────────────────────────────────────── // ── Login-Sicherheit ──────────────────────────────────────────────────────────
// Spalten für Account-Lockout in users-Tabelle // Spalten für Account-Lockout in users-Tabelle

View File

@@ -32,7 +32,7 @@ router.post('/restore', authenticate, requireAdmin, upload.single('database'), (
// GET /api/admin/users alle Benutzer mit Statistiken // GET /api/admin/users alle Benutzer mit Statistiken
router.get('/users', authenticate, requireAdmin, (req, res) => { router.get('/users', authenticate, requireAdmin, (req, res) => {
const users = db.prepare(` const users = db.prepare(`
SELECT u.id, u.username, u.role, u.created_at, u.last_active_at, SELECT u.id, u.username, u.role, u.created_at, u.last_active_at, u.hidden,
CASE WHEN p.user_id IS NOT NULL THEN 1 ELSE 0 END as has_pushover CASE WHEN p.user_id IS NOT NULL THEN 1 ELSE 0 END as has_pushover
FROM users u FROM users u
LEFT JOIN pushover_settings p ON p.user_id = u.id LEFT JOIN pushover_settings p ON p.user_id = u.id
@@ -160,4 +160,13 @@ router.get('/login-log', authenticate, requireAdmin, (req, res) => {
`).all()); `).all());
}); });
// Toggle hidden
router.put('/users/:id/hidden', authenticate, requireAdmin, (req, res) => {
const user = db.prepare('SELECT * FROM users WHERE id=?').get(req.params.id);
if (!user) return res.status(404).json({ error: 'Nicht gefunden' });
const newHidden = user.hidden ? 0 : 1;
db.prepare('UPDATE users SET hidden=? WHERE id=?').run(newHidden, user.id);
res.json({ hidden: newHidden });
});
module.exports = router; module.exports = router;

View File

@@ -1259,7 +1259,7 @@ function ChangelogModal({ user, toast, onClose }) {
</div> </div>
<div style={{display:'flex',alignItems:'center',gap:6,flexShrink:0}}> <div style={{display:'flex',alignItems:'center',gap:6,flexShrink:0}}>
<span style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:9}}> <span style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:9}}>
{fmtDate(e.created_at)} {typeof __BUILD_TIME__ !== 'undefined' && __BUILD_TIME__ ? __BUILD_TIME__ : fmtDate(e.created_at)}
</span> </span>
{isAdmin && ( {isAdmin && (
<button onClick={()=>del(e.id)} <button onClick={()=>del(e.id)}
@@ -1681,6 +1681,15 @@ function UserManagement({ toast }) {
borderRadius:4, padding:'1px 6px', borderRadius:4, padding:'1px 6px',
}}>🔔 Push</span> }}>🔔 Push</span>
)} )}
{!!u.hidden && (
<span title="Für andere Benutzer unsichtbar" style={{
marginLeft:4, fontSize:9, fontFamily:'monospace',
color:'rgba(255,230,109,0.7)',
background:'rgba(255,230,109,0.08)',
border:'1px solid rgba(255,230,109,0.2)',
borderRadius:4, padding:'1px 6px',
}}>👁 versteckt</span>
)}
{u.last_active_at && ( {u.last_active_at && (
<span style={{ <span style={{
marginLeft:6, fontSize:9, fontFamily:'monospace', marginLeft:6, fontSize:9, fontFamily:'monospace',
@@ -1701,6 +1710,20 @@ function UserManagement({ toast }) {
<div style={{display:'flex',gap:5}}> <div style={{display:'flex',gap:5}}>
<button onClick={()=>{setResetId(u.id);setResetPw('');}} <button onClick={()=>{setResetId(u.id);setResetPw('');}}
style={{...S.btn('#ffe66d',true),fontSize:10}}>🔑 PW</button> style={{...S.btn('#ffe66d',true),fontSize:10}}>🔑 PW</button>
<button
onClick={async ()=>{
try {
const r = await api(`/admin/users/${u.id}/hidden`,{method:'PUT'});
setUsers(p=>p.map(x=>x.id===u.id?{...x,hidden:r.hidden}:x));
toast(r.hidden ? 'Benutzer ausgeblendet' : 'Benutzer wieder sichtbar');
} catch(e){ toast(e.message,'error'); }
}}
title={u.hidden ? 'Für andere Benutzer unsichtbar klicken zum Einblenden' : 'Für andere Benutzer sichtbar klicken zum Ausblenden'}
style={{...S.btn(u.hidden?'#ffe66d':'rgba(255,255,255,0.3)',true),fontSize:10,
opacity: u.role==='admin'?0.4:1,
cursor: u.role==='admin'?'not-allowed':'pointer'}}>
{u.hidden ? '👁 einblenden' : '👁 ausblenden'}
</button>
<button onClick={()=>del(u.id)} style={{...S.btn('#ff6b9d',true)}}> <button onClick={()=>del(u.id)} style={{...S.btn('#ff6b9d',true)}}>
<TrashIcon size={13} color="#ff6b9d"/> <TrashIcon size={13} color="#ff6b9d"/>
</button> </button>