Bans page: users-style table with badges, client-side pagination, manual ban modal (POST /api/bans with ip/reason/hours), unban row buttons

This commit is contained in:
dev
2026-09-12 00:59:32 +03:00
parent 7dd816cfce
commit 5aee2ce3f5
3 changed files with 140 additions and 37 deletions
+18 -2
View File
@@ -77,14 +77,17 @@ function ipOf(req) {
}
async function banIP(req, reason, ms) {
const ip = ipOf(req);
await banIpAddr(ipOf(req), reason, ms, req);
}
async function banIpAddr(ip, reason, ms, actorReq) {
const until = new Date(Date.now() + ms);
banMemory.set(ip, { reason, banned_until: until.toISOString() });
await pool.query(
'INSERT INTO banned_ips (ip, reason, banned_until) VALUES ($1, $2, $3) ON CONFLICT (ip) DO UPDATE SET reason = $2, banned_until = $3',
[ip, reason, until.toISOString()]
);
await logAudit(req, 'ip.ban', { ip, reason });
await logAudit(actorReq, 'ip.ban', { ip, reason });
console.log(`IP banned: ${ip} (${reason})`);
}
@@ -703,6 +706,19 @@ app.get('/api/bans', requireAuth, requireAdmin, async (_, res) => {
res.json(rows);
});
app.post('/api/bans', requireAuth, requireAdmin, async (req, res) => {
const ip = String(req.body?.ip || '').trim();
if (!ip || ip.length > 64 || !/^[0-9a-fA-F:.]+$/.test(ip)) {
return res.status(400).json({ error: 'Некорректный IP' });
}
const reason = (typeof req.body?.reason === 'string' && req.body.reason.trim())
? req.body.reason.trim().slice(0, 100)
: 'manual';
const hours = Math.min(Math.max(parseInt(req.body?.hours, 10) || 24, 1), 24 * 30);
await banIpAddr(ip, reason, hours * 60 * 60 * 1000, req);
res.json({ ok: true, ip, reason, banned_until: banMemory.get(ip).banned_until });
});
app.delete('/api/bans/:ip', requireAuth, requireAdmin, async (req, res) => {
const ip = String(req.params.ip || '').trim();
if (!ip || ip.length > 64 || !/^[0-9a-fA-F:.]+$/.test(ip)) {