Feature #15766 ยป recent_queries_fwlog.patch
| 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, '&').replace(/</g, '<')
|
||
|
.replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||
|
}
|
||
|
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">– 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;">×</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");
|
||
|
?>
|
||