| 424 |
424 |
//]]>
|
| 425 |
425 |
</script>
|
| 426 |
426 |
|
|
427 |
<!-- BEGIN: Recent Queries for Advanced Log Filter (RedMine #15766) -->
|
|
428 |
<script type="text/javascript">
|
|
429 |
//<![CDATA[
|
|
430 |
(function() {
|
|
431 |
var STORAGE_KEY = 'pf_fwlog_recent_queries_v1';
|
|
432 |
var MAX_ITEMS = 5;
|
|
433 |
|
|
434 |
// Advanced Log Filter text fields: storage-key -> DOM id
|
|
435 |
var TEXT_FIELDS = {
|
|
436 |
src: 'filterlogentries_sourceipaddress',
|
|
437 |
dst: 'filterlogentries_destinationipaddress',
|
|
438 |
time: 'filterlogentries_time',
|
|
439 |
sport: 'filterlogentries_sourceport',
|
|
440 |
proto: 'filterlogentries_protocol',
|
|
441 |
qty: 'filterlogentries_qty',
|
|
442 |
iface: 'filterlogentries_interfaces',
|
|
443 |
dport: 'filterlogentries_destinationport',
|
|
444 |
flags: 'filterlogentries_protocolflags',
|
|
445 |
tracker: 'filterlogentries_tracker'
|
|
446 |
};
|
|
447 |
var CHECK_FIELDS = { pass: 'actpass', block: 'actblock' };
|
|
448 |
|
|
449 |
function byId(id) { return document.getElementById(id); }
|
|
450 |
|
|
451 |
function readForm() {
|
|
452 |
var q = {};
|
|
453 |
for (var k in TEXT_FIELDS) {
|
|
454 |
var el = byId(TEXT_FIELDS[k]);
|
|
455 |
q[k] = el ? String(el.value || '').trim() : '';
|
|
456 |
}
|
|
457 |
for (var c in CHECK_FIELDS) {
|
|
458 |
var ce = byId(CHECK_FIELDS[c]);
|
|
459 |
q[c] = !!(ce && ce.checked);
|
|
460 |
}
|
|
461 |
return q;
|
|
462 |
}
|
|
463 |
|
|
464 |
function isEmpty(q) {
|
|
465 |
for (var k in TEXT_FIELDS) { if (q[k]) { return false; } }
|
|
466 |
return !q.pass && !q.block;
|
|
467 |
}
|
|
468 |
|
|
469 |
// Only the fields that identify the query (ignore qty) form its identity.
|
|
470 |
function signature(q) {
|
|
471 |
var parts = [];
|
|
472 |
for (var k in TEXT_FIELDS) { if (k !== 'qty') { parts.push(k + '=' + (q[k] || '')); } }
|
|
473 |
parts.push('pass=' + (q.pass ? 1 : 0));
|
|
474 |
parts.push('block=' + (q.block ? 1 : 0));
|
|
475 |
return parts.join('|');
|
|
476 |
}
|
|
477 |
|
|
478 |
function label(q) {
|
|
479 |
var bits = [];
|
|
480 |
if (q.src) { bits.push('src ' + q.src); }
|
|
481 |
if (q.dst) { bits.push('dst ' + q.dst); }
|
|
482 |
if (q.sport) { bits.push('sport ' + q.sport); }
|
|
483 |
if (q.dport) { bits.push('dport ' + q.dport); }
|
|
484 |
if (q.proto) { bits.push('proto ' + q.proto); }
|
|
485 |
if (q.iface) { bits.push('if ' + q.iface); }
|
|
486 |
if (q.flags) { bits.push('flags ' + q.flags); }
|
|
487 |
if (q.tracker) { bits.push('tracker ' + q.tracker); }
|
|
488 |
if (q.time) { bits.push('time ' + q.time); }
|
|
489 |
var act = [];
|
|
490 |
if (q.pass) { act.push('Pass'); }
|
|
491 |
if (q.block) { act.push('Block'); }
|
|
492 |
if (act.length) { bits.push('[' + act.join('+') + ']'); }
|
|
493 |
return bits.length ? bits.join(', ') : '(empty)';
|
|
494 |
}
|
|
495 |
|
|
496 |
function load() {
|
|
497 |
try {
|
|
498 |
var raw = window.localStorage.getItem(STORAGE_KEY);
|
|
499 |
var arr = raw ? JSON.parse(raw) : [];
|
|
500 |
return Array.isArray(arr) ? arr : [];
|
|
501 |
} catch (e) { return []; }
|
|
502 |
}
|
|
503 |
|
|
504 |
function save(arr) {
|
|
505 |
try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); } catch (e) {}
|
|
506 |
}
|
|
507 |
|
|
508 |
function remember(q) {
|
|
509 |
if (isEmpty(q)) { return; }
|
|
510 |
var sig = signature(q);
|
|
511 |
var arr = load().filter(function(item) { return signature(item) !== sig; });
|
|
512 |
arr.unshift(q);
|
|
513 |
if (arr.length > MAX_ITEMS) { arr = arr.slice(0, MAX_ITEMS); }
|
|
514 |
save(arr);
|
|
515 |
}
|
|
516 |
|
|
517 |
function applyQuery(q) {
|
|
518 |
for (var k in TEXT_FIELDS) {
|
|
519 |
var el = byId(TEXT_FIELDS[k]);
|
|
520 |
if (el) { el.value = (q[k] != null ? q[k] : ''); }
|
|
521 |
}
|
|
522 |
for (var c in CHECK_FIELDS) {
|
|
523 |
var ce = byId(CHECK_FIELDS[c]);
|
|
524 |
if (ce) { ce.checked = !!q[c]; }
|
|
525 |
}
|
|
526 |
// Populate only: do NOT auto-submit. Bring Apply Filter into view and focus it.
|
|
527 |
var btn = byId('filterlogentries_submit');
|
|
528 |
if (btn) {
|
|
529 |
if (btn.scrollIntoView) { btn.scrollIntoView({block: 'center'}); }
|
|
530 |
btn.focus();
|
|
531 |
}
|
|
532 |
}
|
|
533 |
|
|
534 |
function esc(s) {
|
|
535 |
return String(s).replace(/&/g, '&').replace(/</g, '<')
|
|
536 |
.replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
537 |
}
|
|
538 |
|
|
539 |
function render() {
|
|
540 |
var host = byId('filter-form');
|
|
541 |
if (!host) { return; }
|
|
542 |
var old = byId('pf-recentq'); if (old) { old.parentNode.removeChild(old); }
|
|
543 |
|
|
544 |
var arr = load();
|
|
545 |
var wrap = document.createElement('div');
|
|
546 |
wrap.id = 'pf-recentq';
|
|
547 |
wrap.style.margin = '0 0 10px 0';
|
|
548 |
wrap.style.padding = '8px 10px';
|
|
549 |
wrap.style.borderLeft = '3px solid #337ab7';
|
|
550 |
|
|
551 |
var html = '<b>Recent Queries</b>';
|
|
552 |
if (!arr.length) {
|
|
553 |
html += ' <span class="text-muted">– none yet; run a filter to populate this list.</span>';
|
|
554 |
wrap.innerHTML = html;
|
|
555 |
} else {
|
|
556 |
html += ' <a href="#" id="pf-recentq-clear" class="text-muted" style="margin-left:8px;">(clear all)</a>';
|
|
557 |
html += '<ul class="list-unstyled" style="margin:6px 0 0 0;">';
|
|
558 |
for (var i = 0; i < arr.length; i++) {
|
|
559 |
html += '<li style="padding:2px 0;">' +
|
|
560 |
'<a href="#" class="pf-rq-apply" data-idx="' + i + '" title="Populate the filter with this query">' +
|
|
561 |
'<i class="fa-solid fa-clock-rotate-left"></i> ' + esc(label(arr[i])) + '</a>' +
|
|
562 |
' <a href="#" class="pf-rq-del text-muted" data-idx="' + i + '" title="Remove" style="margin-left:6px;">×</a>' +
|
|
563 |
'</li>';
|
|
564 |
}
|
|
565 |
html += '</ul>';
|
|
566 |
wrap.innerHTML = html;
|
|
567 |
}
|
|
568 |
host.insertBefore(wrap, host.firstChild);
|
|
569 |
|
|
570 |
var applyLinks = wrap.getElementsByClassName('pf-rq-apply');
|
|
571 |
for (var a = 0; a < applyLinks.length; a++) {
|
|
572 |
applyLinks[a].addEventListener('click', function(ev) {
|
|
573 |
ev.preventDefault();
|
|
574 |
var idx = parseInt(this.getAttribute('data-idx'), 10);
|
|
575 |
var list = load();
|
|
576 |
if (list[idx]) { applyQuery(list[idx]); }
|
|
577 |
});
|
|
578 |
}
|
|
579 |
var delLinks = wrap.getElementsByClassName('pf-rq-del');
|
|
580 |
for (var d = 0; d < delLinks.length; d++) {
|
|
581 |
delLinks[d].addEventListener('click', function(ev) {
|
|
582 |
ev.preventDefault();
|
|
583 |
var idx = parseInt(this.getAttribute('data-idx'), 10);
|
|
584 |
var list = load();
|
|
585 |
list.splice(idx, 1);
|
|
586 |
save(list);
|
|
587 |
render();
|
|
588 |
});
|
|
589 |
}
|
|
590 |
var clear = byId('pf-recentq-clear');
|
|
591 |
if (clear) {
|
|
592 |
clear.addEventListener('click', function(ev) {
|
|
593 |
ev.preventDefault();
|
|
594 |
save([]);
|
|
595 |
render();
|
|
596 |
});
|
|
597 |
}
|
|
598 |
}
|
|
599 |
|
|
600 |
function init() {
|
|
601 |
// Only run on the Advanced Log Filter view (fields absent in raw/simple mode).
|
|
602 |
if (!byId('filterlogentries_sourceipaddress')) { return; }
|
|
603 |
remember(readForm());
|
|
604 |
render();
|
|
605 |
}
|
|
606 |
|
|
607 |
// pfSense runs queued functions once the DOM + jQuery are ready.
|
|
608 |
if (typeof events !== 'undefined' && events.push) {
|
|
609 |
events.push(init);
|
|
610 |
} else if (document.addEventListener) {
|
|
611 |
document.addEventListener('DOMContentLoaded', init);
|
|
612 |
}
|
|
613 |
})();
|
|
614 |
//]]>
|
|
615 |
</script>
|
|
616 |
<!-- END: Recent Queries for Advanced Log Filter -->
|
|
617 |
|
| 427 |
618 |
<?php include("foot.inc");
|
| 428 |
619 |
?>
|