""" Linux BenchTools - Devices API """ import json from fastapi import APIRouter, Depends, HTTPException, status, Query, Response from sqlalchemy.orm import Session from typing import List from app.db.session import get_db from app.schemas.device import DeviceListResponse, DeviceDetail, DeviceSummary, DeviceUpdate from app.schemas.benchmark import BenchmarkSummary from app.schemas.hardware import HardwareSnapshotResponse from app.schemas.document import DocumentResponse from app.models.device import Device from app.models.benchmark import Benchmark from app.models.hardware_snapshot import HardwareSnapshot from app.models.document import Document router = APIRouter() @router.get("/devices", response_model=DeviceListResponse) async def get_devices( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), search: str = Query(None), db: Session = Depends(get_db) ): """ Get paginated list of devices with their last benchmark """ query = db.query(Device) # Apply search filter if search: search_filter = f"%{search}%" query = query.filter( (Device.hostname.like(search_filter)) | (Device.description.like(search_filter)) | (Device.tags.like(search_filter)) | (Device.location.like(search_filter)) ) # Get total count total = query.count() # Apply pagination offset = (page - 1) * page_size devices = query.offset(offset).limit(page_size).all() # Build response with last benchmark for each device items = [] for device in devices: # Get last benchmark last_bench = db.query(Benchmark).filter( Benchmark.device_id == device.id ).order_by(Benchmark.run_at.desc()).first() last_bench_summary = None if last_bench: last_bench_summary = BenchmarkSummary( id=last_bench.id, run_at=last_bench.run_at.isoformat(), global_score=last_bench.global_score, cpu_score=last_bench.cpu_score, memory_score=last_bench.memory_score, disk_score=last_bench.disk_score, network_score=last_bench.network_score, gpu_score=last_bench.gpu_score, bench_script_version=last_bench.bench_script_version, notes=last_bench.notes ) items.append(DeviceSummary( id=device.id, hostname=device.hostname, fqdn=device.fqdn, description=device.description, asset_tag=device.asset_tag, location=device.location, owner=device.owner, tags=device.tags, purchase_store=device.purchase_store, purchase_date=device.purchase_date, purchase_price=device.purchase_price, created_at=device.created_at.isoformat(), updated_at=device.updated_at.isoformat(), last_benchmark=last_bench_summary )) return DeviceListResponse( items=items, total=total, page=page, page_size=page_size ) @router.get("/devices/{device_id}", response_model=DeviceDetail) async def get_device( device_id: int, db: Session = Depends(get_db) ): """ Get detailed information about a specific device """ device = db.query(Device).filter(Device.id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Device {device_id} not found" ) # Get last benchmark last_bench = db.query(Benchmark).filter( Benchmark.device_id == device.id ).order_by(Benchmark.run_at.desc()).first() last_bench_summary = None if last_bench: last_bench_summary = BenchmarkSummary( id=last_bench.id, run_at=last_bench.run_at.isoformat(), global_score=last_bench.global_score, cpu_score=last_bench.cpu_score, memory_score=last_bench.memory_score, disk_score=last_bench.disk_score, network_score=last_bench.network_score, gpu_score=last_bench.gpu_score, bench_script_version=last_bench.bench_script_version, notes=last_bench.notes ) # Get last hardware snapshot last_snapshot = db.query(HardwareSnapshot).filter( HardwareSnapshot.device_id == device.id ).order_by(HardwareSnapshot.captured_at.desc()).first() last_snapshot_data = None if last_snapshot: last_snapshot_data = HardwareSnapshotResponse( id=last_snapshot.id, device_id=last_snapshot.device_id, captured_at=last_snapshot.captured_at.isoformat(), cpu_vendor=last_snapshot.cpu_vendor, cpu_model=last_snapshot.cpu_model, cpu_cores=last_snapshot.cpu_cores, cpu_threads=last_snapshot.cpu_threads, cpu_base_freq_ghz=last_snapshot.cpu_base_freq_ghz, cpu_max_freq_ghz=last_snapshot.cpu_max_freq_ghz, ram_total_mb=last_snapshot.ram_total_mb, ram_used_mb=last_snapshot.ram_used_mb, ram_free_mb=last_snapshot.ram_free_mb, ram_shared_mb=last_snapshot.ram_shared_mb, ram_slots_total=last_snapshot.ram_slots_total, ram_slots_used=last_snapshot.ram_slots_used, gpu_summary=last_snapshot.gpu_summary, gpu_model=last_snapshot.gpu_model, storage_summary=last_snapshot.storage_summary, storage_devices_json=last_snapshot.storage_devices_json, partitions_json=last_snapshot.partitions_json, network_interfaces_json=last_snapshot.network_interfaces_json, network_shares_json=last_snapshot.network_shares_json, os_name=last_snapshot.os_name, os_version=last_snapshot.os_version, kernel_version=last_snapshot.kernel_version, architecture=last_snapshot.architecture, virtualization_type=last_snapshot.virtualization_type, screen_resolution=last_snapshot.screen_resolution, display_server=last_snapshot.display_server, session_type=last_snapshot.session_type, last_boot_time=last_snapshot.last_boot_time, uptime_seconds=last_snapshot.uptime_seconds, battery_percentage=last_snapshot.battery_percentage, battery_status=last_snapshot.battery_status, battery_health=last_snapshot.battery_health, hostname=last_snapshot.hostname, desktop_environment=last_snapshot.desktop_environment, motherboard_vendor=last_snapshot.motherboard_vendor, motherboard_model=last_snapshot.motherboard_model, bios_vendor=last_snapshot.bios_vendor, bios_version=last_snapshot.bios_version, bios_date=last_snapshot.bios_date, pci_devices_json=last_snapshot.pci_devices_json, usb_devices_json=last_snapshot.usb_devices_json ) # Get documents for this device documents = db.query(Document).filter( Document.device_id == device.id ).all() documents_list = [ DocumentResponse( id=doc.id, device_id=doc.device_id, doc_type=doc.doc_type, filename=doc.filename, mime_type=doc.mime_type, size_bytes=doc.size_bytes, uploaded_at=doc.uploaded_at.isoformat() ) for doc in documents ] return DeviceDetail( id=device.id, hostname=device.hostname, fqdn=device.fqdn, description=device.description, asset_tag=device.asset_tag, location=device.location, owner=device.owner, tags=device.tags, purchase_store=device.purchase_store, purchase_date=device.purchase_date, purchase_price=device.purchase_price, created_at=device.created_at.isoformat(), updated_at=device.updated_at.isoformat(), last_benchmark=last_bench_summary, last_hardware_snapshot=last_snapshot_data, documents=documents_list ) @router.get("/devices/{device_id}/benchmarks") async def get_device_benchmarks( device_id: int, limit: int = Query(20, ge=1, le=100), offset: int = Query(0, ge=0), db: Session = Depends(get_db) ): """ Get benchmark history for a device """ device = db.query(Device).filter(Device.id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Device {device_id} not found" ) # Get benchmarks benchmarks = db.query(Benchmark).filter( Benchmark.device_id == device_id ).order_by(Benchmark.run_at.desc()).offset(offset).limit(limit).all() total = db.query(Benchmark).filter(Benchmark.device_id == device_id).count() items = [ BenchmarkSummary( id=b.id, run_at=b.run_at.isoformat(), global_score=b.global_score, cpu_score=b.cpu_score, memory_score=b.memory_score, disk_score=b.disk_score, network_score=b.network_score, gpu_score=b.gpu_score, bench_script_version=b.bench_script_version, notes=b.notes ) for b in benchmarks ] return { "items": items, "total": total, "limit": limit, "offset": offset } @router.put("/devices/{device_id}", response_model=DeviceDetail) async def update_device( device_id: int, update_data: DeviceUpdate, db: Session = Depends(get_db) ): """ Update device information """ device = db.query(Device).filter(Device.id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Device {device_id} not found" ) # Update only provided fields update_dict = update_data.dict(exclude_unset=True) for key, value in update_dict.items(): setattr(device, key, value) # Update timestamp from datetime import datetime device.updated_at = datetime.utcnow() db.commit() db.refresh(device) # Return updated device (reuse get_device logic) return await get_device(device_id, db) @router.delete("/devices/{device_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_device( device_id: int, db: Session = Depends(get_db) ): """ Delete a device and all related data """ device = db.query(Device).filter(Device.id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Device {device_id} not found" ) db.delete(device) db.commit() return Response(status_code=status.HTTP_204_NO_CONTENT)