2

Polish the requests log table layout

This commit is contained in:
2026-03-12 18:22:54 +01:00
parent 2db01f6f51
commit 200fc83831

View File

@@ -930,15 +930,17 @@ const queryLogHTML = `<!doctype html>
.status.review { background: #78350f; } .status.review { background: #78350f; }
.status.allowed { background: #14532d; } .status.allowed { background: #14532d; }
.status.observed { background: #1e293b; } .status.observed { background: #1e293b; }
.status-code { display: inline-flex; align-items: center; justify-content: center; min-width: 3.5rem; padding: .15rem .45rem; border-radius: 999px; font-size: .8rem; font-weight: 700; background: #1e293b; color: #e2e8f0; }
.status-code.client-error { background: #713f12; color: #fde68a; }
.status-code.server-error { background: #9a3412; color: #fdba74; }
.method { display: inline-block; padding: .2rem .45rem; border-radius: 999px; font-size: .78rem; font-weight: 700; } .method { display: inline-block; padding: .2rem .45rem; border-radius: 999px; font-size: .78rem; font-weight: 700; }
.method.get { background: #14532d; color: #dcfce7; } .method.get { background: #14532d; color: #dcfce7; }
.method.post { background: #78350f; color: #fef3c7; } .method.post { background: #78350f; color: #fef3c7; }
.method.head { background: #0c4a6e; color: #e0f2fe; } .method.head { background: #0c4a6e; color: #e0f2fe; }
.method.other { background: #334155; color: #e2e8f0; } .method.other { background: #334155; color: #e2e8f0; }
.actions { display: flex; gap: .35rem; flex-wrap: nowrap; white-space: nowrap; } .actions { display: block; }
.action-link, button { display: inline-flex; align-items: center; justify-content: center; gap: .35rem; border-radius: .45rem; padding: .3rem .6rem; font-size: .9rem; } .actions .muted { display: block; text-align: center; }
.action-link, button { white-space: nowrap; } button { display: block; width: 100%; min-width: 7rem; align-items: center; justify-content: center; gap: .35rem; border-radius: .45rem; padding: .3rem .6rem; font-size: .9rem; white-space: nowrap; }
.action-link { background: #1e293b; color: #e2e8f0; text-decoration: none; }
button { background: #2563eb; color: white; border: 0; cursor: pointer; } button { background: #2563eb; color: white; border: 0; cursor: pointer; }
button.secondary { background: #475569; } button.secondary { background: #475569; }
button.danger { background: #dc2626; } button.danger { background: #dc2626; }
@@ -1014,9 +1016,9 @@ const queryLogHTML = `<!doctype html>
<thead> <thead>
<tr> <tr>
<th class="col-time">Time</th> <th class="col-time">Time</th>
<th class="col-source">Source</th>
<th class="col-ip">IP</th> <th class="col-ip">IP</th>
<th class="col-method">Method</th> <th class="col-method">Method</th>
<th class="col-source">Source</th>
<th class="col-request">Request</th> <th class="col-request">Request</th>
<th class="col-status">Status</th> <th class="col-status">Status</th>
<th class="col-state">State</th> <th class="col-state">State</th>
@@ -1119,13 +1121,24 @@ const queryLogHTML = `<!doctype html>
function renderActions(item) { function renderActions(item) {
const actions = item.actions || {}; const actions = item.actions || {};
const buttons = ['<a class="action-link" href="/ips/' + encodeURIComponent(item.client_ip) + '">Open</a>'];
if (actions.can_unblock) { if (actions.can_unblock) {
buttons.push('<button class="secondary" data-ip="' + escapeHtml(item.client_ip) + '" onclick="sendAction(this.dataset.ip, \'unblock\', \'Reason for manual unblock\')">Unblock</button>'); return '<div class="actions"><button class="secondary" data-ip="' + escapeHtml(item.client_ip) + '" onclick="sendAction(this.dataset.ip, \'unblock\', \'Reason for manual unblock\')">Unblock</button></div>';
} else if (actions.can_block) {
buttons.push('<button class="danger" data-ip="' + escapeHtml(item.client_ip) + '" onclick="sendAction(this.dataset.ip, \'block\', \'Reason for manual block\')">Block</button>');
} }
return '<div class="actions">' + buttons.join('') + '</div>'; if (actions.can_block) {
return '<div class="actions"><button class="danger" data-ip="' + escapeHtml(item.client_ip) + '" onclick="sendAction(this.dataset.ip, \'block\', \'Reason for manual block\')">Block</button></div>';
}
return '<div class="actions"><span class="muted">—</span></div>';
}
function statusCodeClass(status) {
const code = Number(status || 0);
if (code >= 500) {
return 'server-error';
}
if (code >= 400) {
return 'client-error';
}
return '';
} }
function applyToggles() { function applyToggles() {
@@ -1217,15 +1230,15 @@ const queryLogHTML = `<!doctype html>
function renderEvents(payload) { function renderEvents(payload) {
const items = Array.isArray(payload.items) ? payload.items : []; const items = Array.isArray(payload.items) ? payload.items : [];
const rows = items.map(item => { const rows = items.map(item => {
const requestLabel = ((item.host || '') ? (item.host + item.uri) : (item.uri || '—')); const requestLabel = item.uri || '—';
return [ return [
'<tr>', '<tr>',
' <td class="col-time">' + escapeHtml(formatDate(item.occurred_at)) + '</td>', ' <td class="col-time">' + escapeHtml(formatDate(item.occurred_at)) + '</td>',
' <td class="col-source">' + escapeHtml(item.source_name || '—') + '</td>',
' <td class="col-ip mono"><div class="ip-cell">' + renderBotChip(item.bot) + '<a href="/ips/' + encodeURIComponent(item.client_ip) + '">' + escapeHtml(item.client_ip || '—') + '</a></div></td>', ' <td class="col-ip mono"><div class="ip-cell">' + renderBotChip(item.bot) + '<a href="/ips/' + encodeURIComponent(item.client_ip) + '">' + escapeHtml(item.client_ip || '—') + '</a></div></td>',
' <td class="col-method"><span class="method ' + escapeHtml(methodClass(item.method)) + '">' + escapeHtml(item.method || 'OTHER') + '</span></td>', ' <td class="col-method"><span class="method ' + escapeHtml(methodClass(item.method)) + '">' + escapeHtml(item.method || 'OTHER') + '</span></td>',
' <td class="col-source">' + escapeHtml(item.source_name || '—') + '</td>',
' <td class="col-request mono"><span class="request-text" title="' + escapeHtml(requestLabel) + '">' + escapeHtml(requestLabel) + '</span></td>', ' <td class="col-request mono"><span class="request-text" title="' + escapeHtml(requestLabel) + '">' + escapeHtml(requestLabel) + '</span></td>',
' <td class="col-status">' + escapeHtml(String(item.status || 0)) + '</td>', ' <td class="col-status"><span class="status-code ' + escapeHtml(statusCodeClass(item.status)) + '">' + escapeHtml(String(item.status || 0)) + '</span></td>',
' <td class="col-state"><span class="status ' + escapeHtml(item.current_state || 'observed') + '">' + escapeHtml(item.current_state || 'observed') + '</span></td>', ' <td class="col-state"><span class="status ' + escapeHtml(item.current_state || 'observed') + '">' + escapeHtml(item.current_state || 'observed') + '</span></td>',
' <td class="col-reason"><span class="reason-text" title="' + escapeHtml(item.decision_reason || '—') + '">' + escapeHtml(item.decision_reason || '—') + '</span></td>', ' <td class="col-reason"><span class="reason-text" title="' + escapeHtml(item.decision_reason || '—') + '">' + escapeHtml(item.decision_reason || '—') + '</span></td>',
' <td class="col-actions">' + renderActions(item) + '</td>', ' <td class="col-actions">' + renderActions(item) + '</td>',