mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 22:01:59 -06:00
Port core API routes for sites and devices
This commit is contained in:
28
Data/Engine/domain/device_views.py
Normal file
28
Data/Engine/domain/device_views.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Domain objects for saved device list views."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List
|
||||
|
||||
__all__ = ["DeviceListView"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DeviceListView:
|
||||
id: int
|
||||
name: str
|
||||
columns: List[str]
|
||||
filters: Dict[str, object]
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
def to_dict(self) -> Dict[str, object]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"columns": self.columns,
|
||||
"filters": self.filters,
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
}
|
||||
291
Data/Engine/domain/devices.py
Normal file
291
Data/Engine/domain/devices.py
Normal file
@@ -0,0 +1,291 @@
|
||||
"""Device domain helpers mirroring the legacy server payloads."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Mapping, Optional, Sequence
|
||||
|
||||
__all__ = [
|
||||
"DEVICE_TABLE_COLUMNS",
|
||||
"DEVICE_TABLE",
|
||||
"DeviceSnapshot",
|
||||
"assemble_device_snapshot",
|
||||
"row_to_device_dict",
|
||||
"serialize_device_json",
|
||||
"clean_device_str",
|
||||
"coerce_int",
|
||||
"ts_to_iso",
|
||||
"device_column_sql",
|
||||
"ts_to_human",
|
||||
]
|
||||
|
||||
|
||||
DEVICE_TABLE = "devices"
|
||||
|
||||
DEVICE_JSON_LIST_FIELDS: Mapping[str, List[Any]] = {
|
||||
"memory": [],
|
||||
"network": [],
|
||||
"software": [],
|
||||
"storage": [],
|
||||
}
|
||||
|
||||
DEVICE_JSON_OBJECT_FIELDS: Mapping[str, Dict[str, Any]] = {
|
||||
"cpu": {},
|
||||
}
|
||||
|
||||
DEVICE_TABLE_COLUMNS: Sequence[str] = (
|
||||
"guid",
|
||||
"hostname",
|
||||
"description",
|
||||
"created_at",
|
||||
"agent_hash",
|
||||
"memory",
|
||||
"network",
|
||||
"software",
|
||||
"storage",
|
||||
"cpu",
|
||||
"device_type",
|
||||
"domain",
|
||||
"external_ip",
|
||||
"internal_ip",
|
||||
"last_reboot",
|
||||
"last_seen",
|
||||
"last_user",
|
||||
"operating_system",
|
||||
"uptime",
|
||||
"agent_id",
|
||||
"ansible_ee_ver",
|
||||
"connection_type",
|
||||
"connection_endpoint",
|
||||
"ssl_key_fingerprint",
|
||||
"token_version",
|
||||
"status",
|
||||
"key_added_at",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DeviceSnapshot:
|
||||
hostname: str
|
||||
description: str
|
||||
created_at: int
|
||||
created_at_iso: str
|
||||
agent_hash: str
|
||||
agent_guid: str
|
||||
guid: str
|
||||
memory: List[Dict[str, Any]]
|
||||
network: List[Dict[str, Any]]
|
||||
software: List[Dict[str, Any]]
|
||||
storage: List[Dict[str, Any]]
|
||||
cpu: Dict[str, Any]
|
||||
device_type: str
|
||||
domain: str
|
||||
external_ip: str
|
||||
internal_ip: str
|
||||
last_reboot: str
|
||||
last_seen: int
|
||||
last_seen_iso: str
|
||||
last_user: str
|
||||
operating_system: str
|
||||
uptime: int
|
||||
agent_id: str
|
||||
connection_type: str
|
||||
connection_endpoint: str
|
||||
details: Dict[str, Any]
|
||||
summary: Dict[str, Any]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"hostname": self.hostname,
|
||||
"description": self.description,
|
||||
"created_at": self.created_at,
|
||||
"created_at_iso": self.created_at_iso,
|
||||
"agent_hash": self.agent_hash,
|
||||
"agent_guid": self.agent_guid,
|
||||
"guid": self.guid,
|
||||
"memory": self.memory,
|
||||
"network": self.network,
|
||||
"software": self.software,
|
||||
"storage": self.storage,
|
||||
"cpu": self.cpu,
|
||||
"device_type": self.device_type,
|
||||
"domain": self.domain,
|
||||
"external_ip": self.external_ip,
|
||||
"internal_ip": self.internal_ip,
|
||||
"last_reboot": self.last_reboot,
|
||||
"last_seen": self.last_seen,
|
||||
"last_seen_iso": self.last_seen_iso,
|
||||
"last_user": self.last_user,
|
||||
"operating_system": self.operating_system,
|
||||
"uptime": self.uptime,
|
||||
"agent_id": self.agent_id,
|
||||
"connection_type": self.connection_type,
|
||||
"connection_endpoint": self.connection_endpoint,
|
||||
"details": self.details,
|
||||
"summary": self.summary,
|
||||
}
|
||||
|
||||
|
||||
def ts_to_iso(ts: Optional[int]) -> str:
|
||||
if not ts:
|
||||
return ""
|
||||
try:
|
||||
return datetime.fromtimestamp(int(ts), timezone.utc).isoformat()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _ts_to_human(ts: Optional[int]) -> str:
|
||||
if not ts:
|
||||
return ""
|
||||
try:
|
||||
return datetime.utcfromtimestamp(int(ts)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _parse_device_json(raw: Optional[str], default: Any) -> Any:
|
||||
if raw is None:
|
||||
return json.loads(json.dumps(default)) if isinstance(default, (list, dict)) else default
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
data = None
|
||||
if isinstance(default, list):
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
return []
|
||||
if isinstance(default, dict):
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
return {}
|
||||
return default
|
||||
|
||||
|
||||
def serialize_device_json(value: Any, default: Any) -> str:
|
||||
candidate = value
|
||||
if candidate is None:
|
||||
candidate = default
|
||||
if not isinstance(candidate, (list, dict)):
|
||||
candidate = default
|
||||
try:
|
||||
return json.dumps(candidate)
|
||||
except Exception:
|
||||
try:
|
||||
return json.dumps(default)
|
||||
except Exception:
|
||||
return "{}" if isinstance(default, dict) else "[]"
|
||||
|
||||
|
||||
def clean_device_str(value: Any) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
||||
text = str(value)
|
||||
elif isinstance(value, str):
|
||||
text = value
|
||||
else:
|
||||
try:
|
||||
text = str(value)
|
||||
except Exception:
|
||||
return None
|
||||
text = text.strip()
|
||||
return text or None
|
||||
|
||||
|
||||
def coerce_int(value: Any) -> Optional[int]:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
if isinstance(value, str) and value.strip() == "":
|
||||
return None
|
||||
return int(float(value))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
def row_to_device_dict(row: Sequence[Any], columns: Sequence[str]) -> Dict[str, Any]:
|
||||
return {columns[idx]: row[idx] for idx in range(min(len(row), len(columns)))}
|
||||
|
||||
|
||||
def assemble_device_snapshot(record: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
summary = {
|
||||
"hostname": record.get("hostname") or "",
|
||||
"description": record.get("description") or "",
|
||||
"device_type": record.get("device_type") or "",
|
||||
"domain": record.get("domain") or "",
|
||||
"external_ip": record.get("external_ip") or "",
|
||||
"internal_ip": record.get("internal_ip") or "",
|
||||
"last_reboot": record.get("last_reboot") or "",
|
||||
"last_seen": record.get("last_seen") or 0,
|
||||
"last_user": record.get("last_user") or "",
|
||||
"operating_system": record.get("operating_system") or "",
|
||||
"uptime": record.get("uptime") or 0,
|
||||
"agent_id": record.get("agent_id") or "",
|
||||
"agent_hash": record.get("agent_hash") or "",
|
||||
"agent_guid": record.get("guid") or record.get("agent_guid") or "",
|
||||
"connection_type": record.get("connection_type") or "",
|
||||
"connection_endpoint": record.get("connection_endpoint") or "",
|
||||
"created_at": record.get("created_at") or 0,
|
||||
}
|
||||
|
||||
created_ts = coerce_int(summary.get("created_at")) or 0
|
||||
last_seen_ts = coerce_int(summary.get("last_seen")) or 0
|
||||
uptime_val = coerce_int(summary.get("uptime")) or 0
|
||||
|
||||
parsed_lists = {
|
||||
key: _parse_device_json(record.get(key), default)
|
||||
for key, default in DEVICE_JSON_LIST_FIELDS.items()
|
||||
}
|
||||
cpu_obj = _parse_device_json(record.get("cpu"), DEVICE_JSON_OBJECT_FIELDS["cpu"])
|
||||
|
||||
details = {
|
||||
"memory": parsed_lists["memory"],
|
||||
"network": parsed_lists["network"],
|
||||
"software": parsed_lists["software"],
|
||||
"storage": parsed_lists["storage"],
|
||||
"cpu": cpu_obj,
|
||||
}
|
||||
|
||||
payload: Dict[str, Any] = {
|
||||
"hostname": summary["hostname"],
|
||||
"description": summary.get("description", ""),
|
||||
"created_at": created_ts,
|
||||
"created_at_iso": ts_to_iso(created_ts),
|
||||
"agent_hash": summary.get("agent_hash", ""),
|
||||
"agent_guid": summary.get("agent_guid", ""),
|
||||
"guid": summary.get("agent_guid", ""),
|
||||
"memory": parsed_lists["memory"],
|
||||
"network": parsed_lists["network"],
|
||||
"software": parsed_lists["software"],
|
||||
"storage": parsed_lists["storage"],
|
||||
"cpu": cpu_obj,
|
||||
"device_type": summary.get("device_type", ""),
|
||||
"domain": summary.get("domain", ""),
|
||||
"external_ip": summary.get("external_ip", ""),
|
||||
"internal_ip": summary.get("internal_ip", ""),
|
||||
"last_reboot": summary.get("last_reboot", ""),
|
||||
"last_seen": last_seen_ts,
|
||||
"last_seen_iso": ts_to_iso(last_seen_ts),
|
||||
"last_user": summary.get("last_user", ""),
|
||||
"operating_system": summary.get("operating_system", ""),
|
||||
"uptime": uptime_val,
|
||||
"agent_id": summary.get("agent_id", ""),
|
||||
"connection_type": summary.get("connection_type", ""),
|
||||
"connection_endpoint": summary.get("connection_endpoint", ""),
|
||||
"details": details,
|
||||
"summary": summary,
|
||||
}
|
||||
return payload
|
||||
|
||||
|
||||
def device_column_sql(alias: Optional[str] = None) -> str:
|
||||
if alias:
|
||||
return ", ".join(f"{alias}.{col}" for col in DEVICE_TABLE_COLUMNS)
|
||||
return ", ".join(DEVICE_TABLE_COLUMNS)
|
||||
|
||||
|
||||
def ts_to_human(ts: Optional[int]) -> str:
|
||||
return _ts_to_human(ts)
|
||||
43
Data/Engine/domain/sites.py
Normal file
43
Data/Engine/domain/sites.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Domain models for operator site management."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional
|
||||
|
||||
__all__ = ["SiteSummary", "SiteDeviceMapping"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SiteSummary:
|
||||
"""Representation of a site record including device counts."""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
description: str
|
||||
created_at: int
|
||||
device_count: int
|
||||
|
||||
def to_dict(self) -> Dict[str, object]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"created_at": self.created_at,
|
||||
"device_count": self.device_count,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SiteDeviceMapping:
|
||||
"""Mapping entry describing which site a device belongs to."""
|
||||
|
||||
hostname: str
|
||||
site_id: Optional[int]
|
||||
site_name: str
|
||||
|
||||
def to_dict(self) -> Dict[str, object]:
|
||||
return {
|
||||
"site_id": self.site_id,
|
||||
"site_name": self.site_name,
|
||||
}
|
||||
Reference in New Issue
Block a user