feat: host display_name в ingest, поиск хостов, UI и docs
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user