|
|
|
|
@ -5,21 +5,28 @@ from datetime import datetime, timedelta, timezone
@@ -5,21 +5,28 @@ from datetime import datetime, timedelta, timezone
|
|
|
|
|
from sqlalchemy import Select, exists, func, or_, select |
|
|
|
|
from sqlalchemy.orm import Session |
|
|
|
|
|
|
|
|
|
from app.db.models import Asset, AssetDisk, AssetNetworkAdapter, AssetSnapshot, SoftwareInstallation |
|
|
|
|
from app.db.models import Asset, AssetDisk, AssetNetworkAdapter, AssetSnapshot, AssetUserAssignment, SoftwareInstallation, User |
|
|
|
|
from app.schemas.assets import ( |
|
|
|
|
AssetDetail, |
|
|
|
|
AssetListItem, |
|
|
|
|
AssetOwnerOut, |
|
|
|
|
AssetsByOwnerItem, |
|
|
|
|
CpuOut, |
|
|
|
|
DiskOut, |
|
|
|
|
GpuOut, |
|
|
|
|
MemoryModuleOut, |
|
|
|
|
MemorySummaryOut, |
|
|
|
|
NetworkAdapterOut, |
|
|
|
|
PaginatedAssets, |
|
|
|
|
PaginatedSnapshots, |
|
|
|
|
PaginatedSoftware, |
|
|
|
|
PeripheralOut, |
|
|
|
|
PhysicalDiskOut, |
|
|
|
|
SnapshotListItem, |
|
|
|
|
SnapshotSummary, |
|
|
|
|
SoftwareOut, |
|
|
|
|
StatsSummary, |
|
|
|
|
SystemSnapshotOut, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -151,6 +158,9 @@ def list_assets(
@@ -151,6 +158,9 @@ def list_assets(
|
|
|
|
|
AssetListItem( |
|
|
|
|
id=str(asset.id), |
|
|
|
|
hostname=asset.hostname, |
|
|
|
|
display_name=asset.display_name, |
|
|
|
|
form_factor=_form_factor_from_payload(snap.payload if snap else None) |
|
|
|
|
or (asset.metadata_ or {}).get("form_factor"), |
|
|
|
|
asset_type=asset.asset_type, |
|
|
|
|
status=asset.status, |
|
|
|
|
manufacturer=asset.manufacturer, |
|
|
|
|
@ -240,6 +250,85 @@ def _peripherals_from_payload(payload: dict | None) -> list[PeripheralOut]:
@@ -240,6 +250,85 @@ def _peripherals_from_payload(payload: dict | None) -> list[PeripheralOut]:
|
|
|
|
|
return items |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _form_factor_from_payload(payload: dict | None) -> str | None: |
|
|
|
|
if not payload: |
|
|
|
|
return None |
|
|
|
|
system = payload.get("system") |
|
|
|
|
if isinstance(system, dict): |
|
|
|
|
value = system.get("form_factor") |
|
|
|
|
if value: |
|
|
|
|
return str(value) |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _system_from_payload(payload: dict | None) -> SystemSnapshotOut | None: |
|
|
|
|
if not payload: |
|
|
|
|
return None |
|
|
|
|
system = payload.get("system") |
|
|
|
|
if not isinstance(system, dict): |
|
|
|
|
return None |
|
|
|
|
return SystemSnapshotOut( |
|
|
|
|
form_factor=system.get("form_factor"), |
|
|
|
|
chassis_label=system.get("chassis_label"), |
|
|
|
|
pc_system_label=system.get("pc_system_label"), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _physical_disks_from_payload(payload: dict | None) -> list[PhysicalDiskOut]: |
|
|
|
|
if not payload: |
|
|
|
|
return [] |
|
|
|
|
raw = payload.get("physical_disks") |
|
|
|
|
if not isinstance(raw, list): |
|
|
|
|
return [] |
|
|
|
|
items: list[PhysicalDiskOut] = [] |
|
|
|
|
for entry in raw: |
|
|
|
|
if not isinstance(entry, dict): |
|
|
|
|
continue |
|
|
|
|
items.append( |
|
|
|
|
PhysicalDiskOut( |
|
|
|
|
disk_number=entry.get("disk_number"), |
|
|
|
|
device_id=entry.get("device_id"), |
|
|
|
|
model=entry.get("model"), |
|
|
|
|
friendly_name=entry.get("friendly_name"), |
|
|
|
|
media_type=entry.get("media_type"), |
|
|
|
|
operational_status=entry.get("operational_status"), |
|
|
|
|
health_status=entry.get("health_status"), |
|
|
|
|
total_gb=entry.get("total_gb"), |
|
|
|
|
partition_style=entry.get("partition_style"), |
|
|
|
|
bus_type=entry.get("bus_type"), |
|
|
|
|
serial_number=entry.get("serial_number"), |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
return items |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cpu_from_payload(payload: dict | None) -> CpuOut | None: |
|
|
|
|
if not payload: |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
raw = payload.get("cpu") |
|
|
|
|
if isinstance(raw, dict): |
|
|
|
|
return CpuOut( |
|
|
|
|
model=raw.get("model"), |
|
|
|
|
manufacturer=raw.get("manufacturer"), |
|
|
|
|
cores=raw.get("cores"), |
|
|
|
|
logical_processors=raw.get("logical_processors"), |
|
|
|
|
max_clock_mhz=raw.get("max_clock_mhz"), |
|
|
|
|
current_clock_mhz=raw.get("current_clock_mhz"), |
|
|
|
|
socket=raw.get("socket"), |
|
|
|
|
socket_count=raw.get("socket_count"), |
|
|
|
|
l2_cache_kb=raw.get("l2_cache_kb"), |
|
|
|
|
l3_cache_kb=raw.get("l3_cache_kb"), |
|
|
|
|
architecture=raw.get("architecture"), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
system = payload.get("system") |
|
|
|
|
if isinstance(system, dict) and system.get("cpu_model"): |
|
|
|
|
return CpuOut(model=str(system["cpu_model"])) |
|
|
|
|
|
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _gpus_from_payload(payload: dict | None) -> list[GpuOut]: |
|
|
|
|
if not payload: |
|
|
|
|
return [] |
|
|
|
|
@ -263,21 +352,77 @@ def _gpus_from_payload(payload: dict | None) -> list[GpuOut]:
@@ -263,21 +352,77 @@ def _gpus_from_payload(payload: dict | None) -> list[GpuOut]:
|
|
|
|
|
GpuOut( |
|
|
|
|
name=str(name), |
|
|
|
|
dedicated_memory_mb=int(dedicated) if dedicated is not None else None, |
|
|
|
|
adapter_compatibility=entry.get("adapter_compatibility"), |
|
|
|
|
video_processor=entry.get("video_processor"), |
|
|
|
|
driver_version=entry.get("driver_version"), |
|
|
|
|
driver_date=str(entry.get("driver_date")) if entry.get("driver_date") else None, |
|
|
|
|
pnp_device_id=entry.get("pnp_device_id"), |
|
|
|
|
status=entry.get("status"), |
|
|
|
|
current_resolution=entry.get("current_resolution"), |
|
|
|
|
video_mode_description=entry.get("video_mode_description"), |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
return items |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _memory_from_payload(payload: dict | None) -> tuple[list[MemoryModuleOut], MemorySummaryOut | None]: |
|
|
|
|
if not payload: |
|
|
|
|
return [], None |
|
|
|
|
|
|
|
|
|
modules: list[MemoryModuleOut] = [] |
|
|
|
|
raw_modules = payload.get("memory_modules") |
|
|
|
|
if isinstance(raw_modules, list): |
|
|
|
|
for entry in raw_modules: |
|
|
|
|
if not isinstance(entry, dict): |
|
|
|
|
continue |
|
|
|
|
modules.append( |
|
|
|
|
MemoryModuleOut( |
|
|
|
|
slot=entry.get("slot"), |
|
|
|
|
bank_label=entry.get("bank_label"), |
|
|
|
|
capacity_gb=entry.get("capacity_gb"), |
|
|
|
|
speed_mhz=entry.get("speed_mhz"), |
|
|
|
|
configured_speed_mhz=entry.get("configured_speed_mhz"), |
|
|
|
|
manufacturer=entry.get("manufacturer"), |
|
|
|
|
part_number=entry.get("part_number"), |
|
|
|
|
serial_number=entry.get("serial_number"), |
|
|
|
|
form_factor=entry.get("form_factor"), |
|
|
|
|
memory_type_label=entry.get("memory_type_label"), |
|
|
|
|
rank=entry.get("rank"), |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
summary = None |
|
|
|
|
raw_summary = payload.get("memory_summary") |
|
|
|
|
if isinstance(raw_summary, dict): |
|
|
|
|
summary = MemorySummaryOut( |
|
|
|
|
total_gb=raw_summary.get("total_gb"), |
|
|
|
|
module_count=raw_summary.get("module_count"), |
|
|
|
|
slot_count=raw_summary.get("slot_count"), |
|
|
|
|
empty_slots=raw_summary.get("empty_slots"), |
|
|
|
|
layout=raw_summary.get("layout"), |
|
|
|
|
configuration=raw_summary.get("configuration"), |
|
|
|
|
upgradeable=raw_summary.get("upgradeable"), |
|
|
|
|
ddr_generation=raw_summary.get("ddr_generation"), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
return modules, summary |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _snapshot_to_summary(db: Session, snapshot: AssetSnapshot) -> SnapshotSummary: |
|
|
|
|
disks, network = _load_snapshot_children(db, snapshot) |
|
|
|
|
peripherals = _peripherals_from_payload(snapshot.payload) |
|
|
|
|
gpus = _gpus_from_payload(snapshot.payload) |
|
|
|
|
cpu = _cpu_from_payload(snapshot.payload) |
|
|
|
|
memory_modules, memory_summary = _memory_from_payload(snapshot.payload) |
|
|
|
|
physical_disks = _physical_disks_from_payload(snapshot.payload) |
|
|
|
|
system = _system_from_payload(snapshot.payload) |
|
|
|
|
return SnapshotSummary( |
|
|
|
|
id=str(snapshot.id), |
|
|
|
|
agent_version=snapshot.agent_version, |
|
|
|
|
os_name=snapshot.os_name, |
|
|
|
|
os_version=snapshot.os_version, |
|
|
|
|
cpu_model=snapshot.cpu_model, |
|
|
|
|
cpu_model=snapshot.cpu_model or (cpu.model if cpu else None), |
|
|
|
|
cpu=cpu, |
|
|
|
|
ram_gb=float(snapshot.ram_gb) if snapshot.ram_gb is not None else None, |
|
|
|
|
gpu_model=snapshot.gpu_model, |
|
|
|
|
bios_version=snapshot.bios_version, |
|
|
|
|
@ -287,7 +432,11 @@ def _snapshot_to_summary(db: Session, snapshot: AssetSnapshot) -> SnapshotSummar
@@ -287,7 +432,11 @@ def _snapshot_to_summary(db: Session, snapshot: AssetSnapshot) -> SnapshotSummar
|
|
|
|
|
collected_at=snapshot.collected_at, |
|
|
|
|
created_at=snapshot.created_at, |
|
|
|
|
gpus=gpus, |
|
|
|
|
memory_modules=memory_modules, |
|
|
|
|
memory_summary=memory_summary, |
|
|
|
|
disks=disks, |
|
|
|
|
physical_disks=physical_disks, |
|
|
|
|
system=system, |
|
|
|
|
network=network, |
|
|
|
|
peripherals=peripherals, |
|
|
|
|
) |
|
|
|
|
@ -417,3 +566,89 @@ def list_software(
@@ -417,3 +566,89 @@ def list_software(
|
|
|
|
|
snapshot_id=str(resolved_snapshot_id) if resolved_snapshot_id else None, |
|
|
|
|
**meta, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_asset_owner(db: Session, asset_id: uuid.UUID, ldap_username: str) -> AssetOwnerOut | None: |
|
|
|
|
asset = db.scalar(select(Asset).where(Asset.id == asset_id, Asset.deleted_at.is_(None))) |
|
|
|
|
if not asset: |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
user = db.scalar(select(User).where(User.username == ldap_username)) |
|
|
|
|
if not user: |
|
|
|
|
user = User(username=ldap_username) |
|
|
|
|
db.add(user) |
|
|
|
|
db.flush() |
|
|
|
|
|
|
|
|
|
existing = db.scalar( |
|
|
|
|
select(AssetUserAssignment).where( |
|
|
|
|
AssetUserAssignment.asset_id == asset_id, |
|
|
|
|
AssetUserAssignment.is_primary == True, # noqa: E712 |
|
|
|
|
AssetUserAssignment.ended_at.is_(None), |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
if existing: |
|
|
|
|
existing.ended_at = _utcnow() |
|
|
|
|
db.flush() |
|
|
|
|
|
|
|
|
|
assignment = AssetUserAssignment( |
|
|
|
|
asset_id=asset_id, |
|
|
|
|
user_id=user.id, |
|
|
|
|
assigned_by="manual", |
|
|
|
|
is_primary=True, |
|
|
|
|
) |
|
|
|
|
db.add(assignment) |
|
|
|
|
db.commit() |
|
|
|
|
db.refresh(assignment) |
|
|
|
|
|
|
|
|
|
return AssetOwnerOut( |
|
|
|
|
asset_id=str(asset_id), |
|
|
|
|
ldap_username=user.username, |
|
|
|
|
assigned_at=assignment.assigned_at, |
|
|
|
|
assigned_by=assignment.assigned_by, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_assets_by_ldap_username(db: Session, ldap_username: str) -> list[AssetsByOwnerItem]: |
|
|
|
|
user = db.scalar(select(User).where(User.username == ldap_username)) |
|
|
|
|
if not user: |
|
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
rows = db.execute( |
|
|
|
|
select(Asset, AssetUserAssignment) |
|
|
|
|
.join(AssetUserAssignment, AssetUserAssignment.asset_id == Asset.id) |
|
|
|
|
.where( |
|
|
|
|
AssetUserAssignment.user_id == user.id, |
|
|
|
|
AssetUserAssignment.is_primary == True, # noqa: E712 |
|
|
|
|
AssetUserAssignment.ended_at.is_(None), |
|
|
|
|
Asset.deleted_at.is_(None), |
|
|
|
|
) |
|
|
|
|
.order_by(Asset.last_seen_at.desc().nullslast()) |
|
|
|
|
).all() |
|
|
|
|
|
|
|
|
|
latest = _latest_snapshot_subquery() |
|
|
|
|
items: list[AssetsByOwnerItem] = [] |
|
|
|
|
for asset, _ in rows: |
|
|
|
|
snap = db.scalar( |
|
|
|
|
select(AssetSnapshot) |
|
|
|
|
.join( |
|
|
|
|
latest, |
|
|
|
|
(AssetSnapshot.asset_id == latest.c.asset_id) |
|
|
|
|
& (AssetSnapshot.collected_at == latest.c.max_collected_at), |
|
|
|
|
) |
|
|
|
|
.where(AssetSnapshot.asset_id == asset.id) |
|
|
|
|
) |
|
|
|
|
items.append( |
|
|
|
|
AssetsByOwnerItem( |
|
|
|
|
id=str(asset.id), |
|
|
|
|
hostname=asset.hostname, |
|
|
|
|
display_name=asset.display_name, |
|
|
|
|
asset_type=asset.asset_type, |
|
|
|
|
status=asset.status, |
|
|
|
|
manufacturer=asset.manufacturer, |
|
|
|
|
model=asset.model, |
|
|
|
|
serial_number=asset.serial_number, |
|
|
|
|
last_seen_at=asset.last_seen_at, |
|
|
|
|
latest_snapshot_at=snap.collected_at if snap else None, |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
return items |
|
|
|
|
|