feat: audit log, device tags, maintenance windows (v0.11.3 Beta)
- Audit log: middleware captures all write operations, paginated admin UI in Settings - Device tags: full CRUD, device assignment, filter bar and chips on Devices page - Maintenance windows: alert suppression during planned downtime, Settings management UI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6a4ab1fc5f
commit
49ab1b002e
@@ -0,0 +1,61 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { query } from '../config/database';
|
||||
import { requireAuth, requireAdmin } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
router.use(requireAuth);
|
||||
router.use(requireAdmin);
|
||||
|
||||
// GET /api/audit-log?page=1&limit=50&userId=&entityType=&search=&from=&to=
|
||||
router.get('/', async (req: Request, res: Response) => {
|
||||
const page = Math.max(1, parseInt(String(req.query.page || '1'), 10));
|
||||
const limit = Math.min(100, Math.max(1, parseInt(String(req.query.limit || '50'), 10)));
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const conditions: string[] = [];
|
||||
const params: unknown[] = [];
|
||||
|
||||
if (req.query.userId) {
|
||||
params.push(parseInt(String(req.query.userId), 10));
|
||||
conditions.push(`user_id = $${params.length}`);
|
||||
}
|
||||
if (req.query.entityType) {
|
||||
params.push(String(req.query.entityType));
|
||||
conditions.push(`entity_type = $${params.length}`);
|
||||
}
|
||||
if (req.query.search) {
|
||||
params.push(`%${String(req.query.search)}%`);
|
||||
conditions.push(`(summary ILIKE $${params.length} OR username ILIKE $${params.length} OR ip_address ILIKE $${params.length})`);
|
||||
}
|
||||
if (req.query.from) {
|
||||
params.push(String(req.query.from));
|
||||
conditions.push(`created_at >= $${params.length}`);
|
||||
}
|
||||
if (req.query.to) {
|
||||
params.push(String(req.query.to));
|
||||
conditions.push(`created_at <= $${params.length}`);
|
||||
}
|
||||
|
||||
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
|
||||
const [rows, countRows] = await Promise.all([
|
||||
query<{
|
||||
id: number; user_id: number | null; username: string | null;
|
||||
method: string; path: string; entity_type: string | null;
|
||||
entity_id: number | null; summary: string; ip_address: string | null;
|
||||
status_code: number | null; created_at: string;
|
||||
}>(`SELECT * FROM audit_log ${where} ORDER BY created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`,
|
||||
[...params, limit, offset]
|
||||
),
|
||||
query<{ count: string }>(`SELECT COUNT(*) as count FROM audit_log ${where}`, params),
|
||||
]);
|
||||
|
||||
res.json({
|
||||
rows,
|
||||
total: parseInt(countRows[0]?.count || '0', 10),
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user