From 8f7dbbf3c9bbfa29f04449ce3fb7d04662d4410b Mon Sep 17 00:00:00 2001 From: Dicken Date: Thu, 28 May 2026 19:56:36 +0200 Subject: [PATCH] =?UTF-8?q?Admin:=20Letzte=20Aktivit=C3=A4t=20pro=20User?= =?UTF-8?q?=20(nur=20echte=20Aktionen,=20kein=20Polling)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/db.js | 2 ++ backend/src/index.js | 17 +++++++++++++++++ backend/src/routes/admin.js | 2 +- frontend/src/App.jsx | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/src/db.js b/backend/src/db.js index 26ba3f3..9a3718c 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -250,6 +250,8 @@ if (msgCols2.length && !msgCols2.includes('read_by_recipient')) { const userCols2 = db.prepare("PRAGMA table_info(users)").all().map(r => r.name); if (!userCols2.includes('preferences')) db.exec("ALTER TABLE users ADD COLUMN preferences TEXT DEFAULT NULL"); +if (!userCols2.includes('last_active_at')) + db.exec("ALTER TABLE users ADD COLUMN last_active_at DATETIME DEFAULT NULL"); // ── Login-Sicherheit ────────────────────────────────────────────────────────── // Spalten für Account-Lockout in users-Tabelle diff --git a/backend/src/index.js b/backend/src/index.js index 7e95aca..f327729 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -7,6 +7,23 @@ require('./db'); const app = express(); app.use(express.json({ limit: '10mb' })); +// ── Aktivitäts-Tracking: nur bei echten Aktionen (POST/PUT/DELETE) ───────────── +app.use((req, res, next) => { + if (['POST','PUT','DELETE'].includes(req.method)) { + const auth = req.headers.authorization; + if (auth?.startsWith('Bearer ')) { + try { + const jwt = require('jsonwebtoken'); + const p = jwt.verify(auth.slice(7), process.env.JWT_SECRET || 'dev-secret'); + if (p?.id) { + db.prepare("UPDATE users SET last_active_at=datetime('now','localtime') WHERE id=?").run(p.id); + } + } catch {} + } + } + next(); +}); + // ── Security Headers ────────────────────────────────────────────────────────── app.use((_req, res, next) => { res.setHeader('Content-Security-Policy', [ diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index dc5b94e..ed84ef0 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -31,7 +31,7 @@ router.post('/restore', authenticate, requireAdmin, upload.single('database'), ( // GET /api/admin/users – alle Benutzer mit Statistiken router.get('/users', authenticate, requireAdmin, (req, res) => { - const users = db.prepare("SELECT id, username, role, created_at FROM users ORDER BY role DESC, username").all(); + const users = db.prepare("SELECT id, username, role, created_at, last_active_at FROM users ORDER BY role DESC, username").all(); const stats = users.map(u => { const archiv = db.prepare('SELECT COUNT(*) c FROM calculations WHERE user_id=?').get(u.id).c; const bestellung = db.prepare('SELECT COUNT(*) c FROM orders WHERE user_id=?').get(u.id).c; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 99ec8ca..be3bbf5 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1499,6 +1499,22 @@ function UserManagement({ toast }) { border:`1px solid ${u.role==='admin'?'rgba(255,230,109,0.3)':'rgba(78,205,196,0.3)'}`, borderRadius:4, padding:'1px 6px', }}>{u.role} + {u.last_active_at && ( + + ● {(() => { + const d = new Date(u.last_active_at.replace(' ','T')); + const now = new Date(); + const diff = Math.floor((now - d) / 60000); + if (diff < 2) return 'gerade eben'; + if (diff < 60) return `vor ${diff} min`; + if (diff < 1440) return `vor ${Math.floor(diff/60)} h`; + return d.toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); + })()} + + )}