- plugins/syslog: RFC 5424 and RFC 3164 parsing, RFC 6587 TCP framing. - Lines are queued as they arrive and stored every 5 s through a Holding, so a storage stall delays them rather than losing them. - A new sender is an offered source, counted and not kept, until an admin adds it on the Syslog page (name, host); it can be ignored instead. - The listener follows the plugin's settings (port, UDP, TCP), rebinding within seconds of a save; a port it can't open is said on the page. - Hourly ring per source: age window and byte cap from Settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
3.7 KiB
Python
74 lines
3.7 KiB
Python
"""The Syslog page (#5999): whether the listener is open or why not, the
|
|
sources kept, and the senders offered to an admin to add or ignore."""
|
|
from datetime import datetime, timedelta, timezone
|
|
from types import SimpleNamespace as NS
|
|
|
|
from tests._templates import render_plugin_template
|
|
from tests.test_style_tokens import hex_colours
|
|
|
|
|
|
def _listener(**over):
|
|
base = {"port": 5514, "protocols": ["UDP", "TCP"], "listening": True, "error": None,
|
|
"bound_at": datetime.now(timezone.utc) - timedelta(hours=2), "received": 1204,
|
|
"dropped": 0, "unoffered": 0, "connections": 1, "senders_full": False, "max_senders": 200}
|
|
return {**base, **over}
|
|
|
|
|
|
def _source(name, state, level=6, line="nfsd: server not responding", host=None):
|
|
now = datetime.now(timezone.utc)
|
|
return {"id": f"id-{name}", "address": "192.168.0.7", "state": state, "name": name,
|
|
"hostname_seen": "synnas", "host": host, "first_seen_at": now - timedelta(days=1),
|
|
"last_seen_at": now - timedelta(minutes=3), "lines_total": 1204,
|
|
"severity": "err" if level == 3 else "info", "level": level, "last_line": line,
|
|
"suggested_host": "h1"}
|
|
|
|
|
|
async def _render(app, role, **ctx):
|
|
from quart import session
|
|
ctx = {"listener": _listener(), "sources": [], "offered": [], "ignored": [],
|
|
"hosts": [NS(id="h1", name="SynNAS", address="192.168.0.7")],
|
|
"now": datetime.now(timezone.utc), **ctx}
|
|
async with app.test_request_context("/plugins/syslog/"):
|
|
session["user_role"] = role
|
|
page = await render_plugin_template(app, "syslog", "syslog/index.html", **ctx)
|
|
return " ".join(page.split())
|
|
|
|
|
|
async def test_an_offered_sender_can_be_added_or_ignored_by_an_admin(app):
|
|
page = await _render(app, "admin", offered=[_source("synnas", "offered", level=3)])
|
|
assert "Sending, not added" in page and "calls itself synnas" in page
|
|
assert 'action="/plugins/syslog/sources/id-synnas/add"' in page
|
|
assert 'action="/plugins/syslog/sources/id-synnas/ignore"' in page
|
|
assert '<option value="h1" selected>SynNAS</option>' in page # the host at its address
|
|
assert 'class="tone-crit">err</span> nfsd: server not responding' in page
|
|
assert "UDP and TCP port 5514" in page and "1,204 lines received" in page
|
|
main = page[page.index("<main"):]
|
|
assert "font-weight:600" not in main and not hex_colours(main), hex_colours(main)
|
|
|
|
|
|
async def test_a_viewer_sees_sources_but_no_controls(app):
|
|
page = await _render(app, "viewer", sources=[_source("SynNAS", "accepted", host=NS(id="h1", name="SynNAS"))],
|
|
offered=[_source("router", "offered")])
|
|
assert 'href="/hosts/h1"' in page and "/plugins/syslog/sources/" not in page
|
|
assert "Sending, not added" in page
|
|
|
|
|
|
async def test_a_port_it_cannot_open_is_said(app):
|
|
page = await _render(app, "admin", listener=_listener(
|
|
listening=False, error="Port 514 couldn't be opened (UDP: Permission denied)."))
|
|
assert "Not listening" in page and "Port 514 couldn't be opened" in page
|
|
assert "below 1024" in page
|
|
assert "No sources yet." in page
|
|
|
|
|
|
async def test_the_edit_page_has_its_trail_and_a_remove(app):
|
|
from quart import session
|
|
source = NS(id="s1", name="SynNAS", hostname_seen="synnas", address="192.168.0.7", host_id=None)
|
|
async with app.test_request_context("/plugins/syslog/sources/s1/edit"):
|
|
session["user_role"] = "admin"
|
|
page = await render_plugin_template(app, "syslog", "syslog/edit.html", source=source,
|
|
hosts=[], label="SynNAS")
|
|
page = " ".join(page.split())
|
|
assert 'href="/plugins/syslog/">Syslog</a>' in page
|
|
assert 'action="/plugins/syslog/sources/s1/delete"' in page and "data-confirm=" in page
|