feat: host display_name в ingest, поиск хостов, UI и docs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-05-28 11:49:10 +10:00
parent 108e1756c4
commit d4bf6e536c
8 changed files with 122 additions and 11 deletions
+3 -2
View File
@@ -34,8 +34,9 @@ def list_hosts(
count_stmt = count_stmt.where(Host.product == product)
if hostname:
like = f"%{hostname}%"
stmt = stmt.where(Host.hostname.ilike(like))
count_stmt = count_stmt.where(Host.hostname.ilike(like))
host_match = Host.hostname.ilike(like) | Host.display_name.ilike(like)
stmt = stmt.where(host_match)
count_stmt = count_stmt.where(host_match)
total = db.scalar(count_stmt) or 0
rows = db.scalars(
+3 -1
View File
@@ -46,7 +46,9 @@ def upsert_host(db: Session, payload: dict) -> Host:
db.add(host)
else:
host.hostname = hostname
host.display_name = host_data.get("display_name") or host.display_name
dn = host_data.get("display_name")
if isinstance(dn, str) and dn.strip():
host.display_name = dn.strip()
host.os_version = host_data.get("os_version") or host.os_version
host.product_version = source.get("product_version") or host.product_version
host.ipv4 = host_data.get("ipv4") or host.ipv4
+11
View File
@@ -0,0 +1,11 @@
"""Extended /health payload for ops monitoring."""
def test_health_includes_stale_and_last_event(client, db_session, auth_headers):
r = client.get("/health")
assert r.status_code == 200
body = r.json()
assert body["database"] == "ok"
assert "hosts_stale" in body
assert "last_event_received_at" in body
assert body["service"] == "security-alert-center"
+26
View File
@@ -0,0 +1,26 @@
"""Hosts list API."""
from datetime import datetime, timezone
from app.models import Host
def test_hosts_search_by_display_name(client, db_session, jwt_headers):
h = Host(
hostname="pc-01",
display_name="UNMS Kalina",
os_family="linux",
product="ssh-monitor",
last_seen_at=datetime.now(timezone.utc),
)
db_session.add(h)
db_session.commit()
r = client.get("/api/v1/hosts?hostname=UNMS", headers=jwt_headers)
assert r.status_code == 200
body = r.json()
assert body["total"] == 1
assert body["items"][0]["display_name"] == "UNMS Kalina"
r2 = client.get("/api/v1/hosts?hostname=nomatch", headers=jwt_headers)
assert r2.json()["total"] == 0
+30
View File
@@ -70,6 +70,36 @@ def test_ingest_rdp_lifecycle_201(client, auth_headers):
assert r.json()["event_id"] == event_id
def test_ingest_host_display_name_updated(client, auth_headers, db_session):
from sqlalchemy import select
from app.models import Host
event_id = str(uuid.uuid4())
payload = {
**VALID_EVENT,
"event_id": event_id,
"host": {
"hostname": "short-name",
"display_name": "UNMS Kalina",
"os_family": "linux",
},
}
assert client.post("/api/v1/events", json=payload, headers=auth_headers).status_code == 201
host = db_session.scalar(select(Host).where(Host.hostname == "short-name"))
assert host is not None
assert host.display_name == "UNMS Kalina"
payload2 = {
**payload,
"event_id": str(uuid.uuid4()),
"host": {"hostname": "short-name", "display_name": "Kalina UNMS", "os_family": "linux"},
}
assert client.post("/api/v1/events", json=payload2, headers=auth_headers).status_code == 201
db_session.refresh(host)
assert host.display_name == "Kalina UNMS"
def test_ingest_invalid_payload_422(client, auth_headers):
r = client.post(
"/api/v1/events",