Project

General

Profile

Feature #15766 ยป recent_queries_fwlog.patch

Alexander Snyder, 07/22/2026 09:05 PM

View differences:

usr/local/www/status_logs_filter.php
//]]>
</script>
<!-- BEGIN: Recent Queries for Advanced Log Filter (RedMine #15766) -->
<script type="text/javascript">
//<![CDATA[
(function() {
var STORAGE_KEY = 'pf_fwlog_recent_queries_v1';
var MAX_ITEMS = 5;
// Advanced Log Filter text fields: storage-key -> DOM id
var TEXT_FIELDS = {
src: 'filterlogentries_sourceipaddress',
dst: 'filterlogentries_destinationipaddress',
time: 'filterlogentries_time',
sport: 'filterlogentries_sourceport',
proto: 'filterlogentries_protocol',
qty: 'filterlogentries_qty',
iface: 'filterlogentries_interfaces',
dport: 'filterlogentries_destinationport',
flags: 'filterlogentries_protocolflags',
tracker: 'filterlogentries_tracker'
};
var CHECK_FIELDS = { pass: 'actpass', block: 'actblock' };
function byId(id) { return document.getElementById(id); }
function readForm() {
var q = {};
for (var k in TEXT_FIELDS) {
var el = byId(TEXT_FIELDS[k]);
q[k] = el ? String(el.value || '').trim() : '';
}
for (var c in CHECK_FIELDS) {
var ce = byId(CHECK_FIELDS[c]);
q[c] = !!(ce && ce.checked);
}
return q;
}
function isEmpty(q) {
for (var k in TEXT_FIELDS) { if (q[k]) { return false; } }
return !q.pass && !q.block;
}
// Only the fields that identify the query (ignore qty) form its identity.
function signature(q) {
var parts = [];
for (var k in TEXT_FIELDS) { if (k !== 'qty') { parts.push(k + '=' + (q[k] || '')); } }
parts.push('pass=' + (q.pass ? 1 : 0));
parts.push('block=' + (q.block ? 1 : 0));
return parts.join('|');
}
function label(q) {
var bits = [];
if (q.src) { bits.push('src ' + q.src); }
if (q.dst) { bits.push('dst ' + q.dst); }
if (q.sport) { bits.push('sport ' + q.sport); }
if (q.dport) { bits.push('dport ' + q.dport); }
if (q.proto) { bits.push('proto ' + q.proto); }
if (q.iface) { bits.push('if ' + q.iface); }
if (q.flags) { bits.push('flags ' + q.flags); }
if (q.tracker) { bits.push('tracker ' + q.tracker); }
if (q.time) { bits.push('time ' + q.time); }
var act = [];
if (q.pass) { act.push('Pass'); }
if (q.block) { act.push('Block'); }
if (act.length) { bits.push('[' + act.join('+') + ']'); }
return bits.length ? bits.join(', ') : '(empty)';
}
function load() {
try {
var raw = window.localStorage.getItem(STORAGE_KEY);
var arr = raw ? JSON.parse(raw) : [];
return Array.isArray(arr) ? arr : [];
} catch (e) { return []; }
}
function save(arr) {
try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); } catch (e) {}
}
function remember(q) {
if (isEmpty(q)) { return; }
var sig = signature(q);
var arr = load().filter(function(item) { return signature(item) !== sig; });
arr.unshift(q);
if (arr.length > MAX_ITEMS) { arr = arr.slice(0, MAX_ITEMS); }
save(arr);
}
function applyQuery(q) {
for (var k in TEXT_FIELDS) {
var el = byId(TEXT_FIELDS[k]);
if (el) { el.value = (q[k] != null ? q[k] : ''); }
}
for (var c in CHECK_FIELDS) {
var ce = byId(CHECK_FIELDS[c]);
if (ce) { ce.checked = !!q[c]; }
}
// Populate only: do NOT auto-submit. Bring Apply Filter into view and focus it.
var btn = byId('filterlogentries_submit');
if (btn) {
if (btn.scrollIntoView) { btn.scrollIntoView({block: 'center'}); }
btn.focus();
}
}
function esc(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function render() {
var host = byId('filter-form');
if (!host) { return; }
var old = byId('pf-recentq'); if (old) { old.parentNode.removeChild(old); }
var arr = load();
var wrap = document.createElement('div');
wrap.id = 'pf-recentq';
wrap.style.margin = '0 0 10px 0';
wrap.style.padding = '8px 10px';
wrap.style.borderLeft = '3px solid #337ab7';
var html = '<b>Recent Queries</b>';
if (!arr.length) {
html += ' <span class="text-muted">&ndash; none yet; run a filter to populate this list.</span>';
wrap.innerHTML = html;
} else {
html += ' <a href="#" id="pf-recentq-clear" class="text-muted" style="margin-left:8px;">(clear all)</a>';
html += '<ul class="list-unstyled" style="margin:6px 0 0 0;">';
for (var i = 0; i < arr.length; i++) {
html += '<li style="padding:2px 0;">' +
'<a href="#" class="pf-rq-apply" data-idx="' + i + '" title="Populate the filter with this query">' +
'<i class="fa-solid fa-clock-rotate-left"></i> ' + esc(label(arr[i])) + '</a>' +
' <a href="#" class="pf-rq-del text-muted" data-idx="' + i + '" title="Remove" style="margin-left:6px;">&times;</a>' +
'</li>';
}
html += '</ul>';
wrap.innerHTML = html;
}
host.insertBefore(wrap, host.firstChild);
var applyLinks = wrap.getElementsByClassName('pf-rq-apply');
for (var a = 0; a < applyLinks.length; a++) {
applyLinks[a].addEventListener('click', function(ev) {
ev.preventDefault();
var idx = parseInt(this.getAttribute('data-idx'), 10);
var list = load();
if (list[idx]) { applyQuery(list[idx]); }
});
}
var delLinks = wrap.getElementsByClassName('pf-rq-del');
for (var d = 0; d < delLinks.length; d++) {
delLinks[d].addEventListener('click', function(ev) {
ev.preventDefault();
var idx = parseInt(this.getAttribute('data-idx'), 10);
var list = load();
list.splice(idx, 1);
save(list);
render();
});
}
var clear = byId('pf-recentq-clear');
if (clear) {
clear.addEventListener('click', function(ev) {
ev.preventDefault();
save([]);
render();
});
}
}
function init() {
// Only run on the Advanced Log Filter view (fields absent in raw/simple mode).
if (!byId('filterlogentries_sourceipaddress')) { return; }
remember(readForm());
render();
}
// pfSense runs queued functions once the DOM + jQuery are ready.
if (typeof events !== 'undefined' && events.push) {
events.push(init);
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', init);
}
})();
//]]>
</script>
<!-- END: Recent Queries for Advanced Log Filter -->
<?php include("foot.inc");
?>
    (1-1/1)