Fix: Recover proxied client IPs and fit audit filters on phones
The MFC web server now resolves the real client address from the WebSocket upgrade request when the TCP peer is loopback, so audit records and login rate limiting stop reporting the reverse proxy's 127.0.0.1. X-Forwarded-For is preferred over X-Real-IP because its last hop is the address the local proxy appended; a client-supplied header list cannot forge it. Forwarded headers are ignored for non-loopback peers so a client connecting directly over the internet cannot spoof its identity or rotate addresses to bypass throttling. FRP deployments can keep using the existing Proxy Protocol v2 support instead. Collapse the audit log search conditions behind a toolbar toggle that starts closed and switches to an accent-colored "Hide filters" state while the panel is open. On portrait phones the panel uses one control per row, and From/To move their border and padding onto a wrapper so the iOS WebKit datetime-local width miscalculation cannot overflow the viewport. Co-Authored-By: deepseek-flash
This commit is contained in:
1 parent
ca671d6453
commit
bbb3d8c2fa
2 files changed
+156
-5
No files matched your search
+80
-5
@@ -391,6 +391,10 @@
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.audit-filters.collapsed { display: none; }
|
||||
/* Filter labels are grid items; without this a wide intrinsic child
|
||||
(notably datetime-local on mobile) can blow out the grid track. */
|
||||
.audit-filters > * { min-width: 0; }
|
||||
.audit-filters label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -409,8 +413,37 @@
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.audit-filters input:focus, .audit-filters select:focus { border-color: #e67e22; }
|
||||
/* iOS Safari mis-sizes temporal controls: with horizontal padding on
|
||||
the input itself, width:100% adds the padding again (WebKit #301648),
|
||||
and iOS 17 ignores width entirely on the native inline-flex control.
|
||||
Move the visual box to a wrapper and keep the input padding-free so
|
||||
it can never exceed its parent. */
|
||||
.audit-date-wrap {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
padding: 0 10px;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 6px;
|
||||
background: rgba(15, 52, 96, 0.7);
|
||||
}
|
||||
.audit-date-wrap:focus-within { border-color: #e67e22; }
|
||||
.audit-date-wrap input[type="datetime-local"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
padding: 8px 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
.audit-filters label.audit-warn {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@@ -430,6 +463,22 @@
|
||||
.audit-search-btn:hover { transform: translateY(-1px); }
|
||||
.audit-reset-btn { background: rgba(255,255,255,0.08); color: #ccc; }
|
||||
.audit-reset-btn:hover { background: rgba(255,255,255,0.15); }
|
||||
/* Filters visibility toggle: collapsed by default; the same button
|
||||
stays in the toolbar and flips to an "open" style (accent tint +
|
||||
caret pointing up) so it reads as "click to collapse". */
|
||||
.view-btn.audit-filter-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border: 1px solid rgba(230, 126, 34, 0.45);
|
||||
border-radius: 8px;
|
||||
color: #e67e22;
|
||||
background: rgba(230, 126, 34, 0.12);
|
||||
}
|
||||
.view-btn.audit-filter-toggle:hover { background: rgba(230, 126, 34, 0.22); color: #fff; }
|
||||
.view-btn.audit-filter-toggle.open { background: rgba(230, 126, 34, 0.30); color: #fff; }
|
||||
.audit-filter-toggle .caret { display: inline-block; font-size: 10px; transition: transform 0.2s; }
|
||||
.audit-filter-toggle.open .caret { transform: rotate(180deg); }
|
||||
.audit-status { color: #888; font-size: 12px; margin-bottom: 10px; min-height: 16px; }
|
||||
.audit-status.warn { color: #e67e22; }
|
||||
.audit-table-wrap {
|
||||
@@ -470,8 +519,11 @@
|
||||
.audit-level.warn { background: rgba(230, 126, 34, 0.18); color: #e67e22; }
|
||||
.audit-empty { padding: 40px; text-align: center; color: #666; }
|
||||
@media (max-width: 640px) {
|
||||
.audit-filters { grid-template-columns: 1fr 1fr; }
|
||||
.audit-actions { grid-column: 1 / -1; }
|
||||
/* One control per row: two columns plus the intrinsic width of
|
||||
datetime-local inputs overflows a portrait phone viewport. */
|
||||
.audit-filters { grid-template-columns: minmax(0, 1fr); padding: 14px; }
|
||||
.audit-actions { grid-column: 1 / -1; flex-wrap: wrap; }
|
||||
.audit-actions button { flex: 1 1 auto; }
|
||||
.audit-table td, .audit-table th { padding: 8px; }
|
||||
}
|
||||
|
||||
@@ -1787,15 +1839,24 @@
|
||||
<h2>Audit Log <span id="audit-count" class="device-count">0</span></h2>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button class="view-btn audit-filter-toggle" id="audit-filter-toggle" type="button"
|
||||
aria-expanded="false" aria-controls="audit-filters"
|
||||
onclick="toggleAuditFilters()">
|
||||
<span class="label">Filters</span><span class="caret">▾</span>
|
||||
</button>
|
||||
<button class="view-btn" onclick="queryAudit(0)" title="Reload newest records">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audit-filters">
|
||||
<div class="audit-filters collapsed" id="audit-filters">
|
||||
<label>From
|
||||
<input type="datetime-local" id="audit-from" onkeydown="if(event.key==='Enter')queryAudit(0)">
|
||||
<span class="audit-date-wrap">
|
||||
<input type="datetime-local" id="audit-from" onkeydown="if(event.key==='Enter')queryAudit(0)">
|
||||
</span>
|
||||
</label>
|
||||
<label>To
|
||||
<input type="datetime-local" id="audit-to" onkeydown="if(event.key==='Enter')queryAudit(0)">
|
||||
<span class="audit-date-wrap">
|
||||
<input type="datetime-local" id="audit-to" onkeydown="if(event.key==='Enter')queryAudit(0)">
|
||||
</span>
|
||||
</label>
|
||||
<label>Event (exact or prefix)
|
||||
<input type="text" id="audit-event" list="audit-event-options" placeholder="e.g. web.* or web.login.failure"
|
||||
@@ -4542,6 +4603,20 @@
|
||||
if (auditBtn) auditBtn.classList.toggle('visible', admin && !!serverCaps.auditLog);
|
||||
}
|
||||
|
||||
// Search conditions start collapsed (especially important on portrait
|
||||
// phones); the toolbar button stays put and switches to a highlighted
|
||||
// "up" state while the panel is open, meaning clicking again folds it.
|
||||
function toggleAuditFilters() {
|
||||
const panel = document.getElementById('audit-filters');
|
||||
const btn = document.getElementById('audit-filter-toggle');
|
||||
if (!panel || !btn) return;
|
||||
const collapsed = panel.classList.toggle('collapsed');
|
||||
btn.classList.toggle('open', !collapsed);
|
||||
btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
||||
const label = btn.querySelector('.label');
|
||||
if (label) label.textContent = collapsed ? 'Filters' : 'Hide filters';
|
||||
}
|
||||
|
||||
function openAuditPage() {
|
||||
if (currentUserRole !== 'admin' || !serverCaps.auditLog) return;
|
||||
fillAuditEventOptions();
|
||||
|
||||
Reference in new issue
Block a user