feat: host inventory (agent.inventory), host detail page, UI columns
Store hardware/software snapshots on hosts; warn on hardware changes. Host card from Hosts list. Add agent version column to Events/Overview; rename Hosts table columns and sort by status. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -333,6 +333,50 @@ def format_smb_admin_share_html(event: Event) -> str:
|
||||
return msg.rstrip()
|
||||
|
||||
|
||||
def _format_inventory_change_line(change: dict[str, Any]) -> str:
|
||||
field = html_escape(str(change.get("field") or "?"))
|
||||
old = change.get("old")
|
||||
new = change.get("new")
|
||||
return f"• <b>{field}</b>: {html_escape(old)} → {html_escape(new)}"
|
||||
|
||||
|
||||
def format_agent_inventory_html(event: Event) -> str:
|
||||
details = _details_dict(event)
|
||||
changes = details.get("hardware_changes")
|
||||
if isinstance(changes, list) and changes:
|
||||
msg = "<b>⚠️ Изменилось железо хоста</b>\n"
|
||||
msg += _line("🏢", "Хост", host_label(event.host))
|
||||
msg += _line("🕐", "Время", format_time(event.occurred_at))
|
||||
msg += "\n"
|
||||
for item in changes:
|
||||
if isinstance(item, dict):
|
||||
msg += _format_inventory_change_line(item) + "\n"
|
||||
if event.summary:
|
||||
msg += f"\n{html_escape(event.summary)}"
|
||||
return msg.rstrip()
|
||||
|
||||
inv = details.get("inventory")
|
||||
if not isinstance(inv, dict):
|
||||
inv = {}
|
||||
msg = "<b>🖥️ Инвентаризация хоста</b>\n"
|
||||
msg += _line("🏢", "Хост", host_label(event.host))
|
||||
windows = inv.get("windows") if isinstance(inv.get("windows"), dict) else {}
|
||||
os_name = windows.get("product_name") or event.host.os_version if event.host else None
|
||||
if os_name:
|
||||
msg += _line("💿", "ОС", html_escape(os_name))
|
||||
mem = inv.get("memory_gb")
|
||||
if mem is not None:
|
||||
msg += _line("🧠", "RAM", f"{html_escape(mem)} GB")
|
||||
procs = inv.get("processor")
|
||||
if isinstance(procs, list) and procs:
|
||||
names = [str(p.get("name") or "").strip() for p in procs if isinstance(p, dict)]
|
||||
names = [n for n in names if n]
|
||||
if names:
|
||||
msg += _line("⚙️", "CPU", html_escape("; ".join(names)))
|
||||
msg += _line("🕐", "Время", format_time(event.occurred_at))
|
||||
return msg.rstrip()
|
||||
|
||||
|
||||
def format_generic_event_html(event: Event) -> str:
|
||||
sev = event.severity.upper()
|
||||
msg = f"<b>🚨 SAC: {html_escape(event.title)}</b>\n"
|
||||
@@ -453,6 +497,8 @@ def _format_event_body_html(event: Event) -> str:
|
||||
return format_winrm_session_html(event)
|
||||
if event.type.startswith("smb."):
|
||||
return format_smb_admin_share_html(event)
|
||||
if event.type == "agent.inventory":
|
||||
return format_agent_inventory_html(event)
|
||||
return format_generic_event_html(event)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user