fix: grep nginx logs directly in /logs/:ip endpoint

Records now works without running a scan first. If logs aren't cached in memory from a scan, the endpoint greps the nginx access logs directly for [Client IP] matches and returns the last 500 lines.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 16:21:43 +00:00
parent 239e2df8c1
commit c4cf7688d0

View File

@@ -490,12 +490,29 @@ app.get('/api/f2b/poll', (req, res) => {
}); });
// ── Routes: Nginx log viewer ────────────────────────────────────────────────── // ── Routes: Nginx log viewer ──────────────────────────────────────────────────
app.get('/logs/:ip', (req, res) => { app.get('/logs/:ip', async (req, res) => {
const ip = req.params.ip; const ip = req.params.ip;
const logs = ipLogs.get(ip) || [];
const hist = banHistory.get(ip); const hist = banHistory.get(ip);
const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
let logs = ipLogs.get(ip) || [];
// If no logs in memory, grep nginx logs directly
if (logs.length === 0) {
try {
const files = fs.readdirSync(LOG_DIR)
.filter(f => f.startsWith('proxy-host-') && f.endsWith('_access.log'))
.map(f => path.join(LOG_DIR, f));
const ipEscaped = ip.replace(/\./g, '\\.');
const grepCmd = `grep -h "\\[Client ${ipEscaped}\\]" ${files.join(' ')} 2>/dev/null || true`;
const output = await run(grepCmd);
logs = output.trim().split('\n').filter(l => l.trim()).slice(-500); // last 500 lines
} catch (e) {
logs = [`[Error reading logs: ${e.message}]`];
}
}
let badge = ''; let badge = '';
if (hist) badge = `<div class="hi" style="margin:.5rem 0">&#9888; Previously banned ${hist.banCount}x &mdash; last ${new Date(hist.lastSeen).toLocaleString()}</div>`; if (hist) badge = `<div class="hi" style="margin:.5rem 0">&#9888; Previously banned ${hist.banCount}x &mdash; last ${new Date(hist.lastSeen).toLocaleString()}</div>`;
@@ -508,8 +525,8 @@ app.get('/logs/:ip', (req, res) => {
<div class="box"> <div class="box">
<div class="box-title">// LOG ENTRIES</div> <div class="box-title">// LOG ENTRIES</div>
${badge} ${badge}
<p class="muted">Entries in current scan window: ${logs.length}</p> <p class="muted">Entries found: ${logs.length}</p>
<pre>${logs.map(esc).join('\n') || '(no entries — run a scan first)'}</pre> <pre>${logs.map(esc).join('\n') || '(no entries found)'}</pre>
</div> </div>
<div class="prompt">_ <span class="blink">&#9608;</span></div> <div class="prompt">_ <span class="blink">&#9608;</span></div>
<footer>F2B Control Center | :${process.env.PORT || 4000}</footer> <footer>F2B Control Center | :${process.env.PORT || 4000}</footer>