LAIM: Lab Asset Inventory Manager
A complete rewrite of a legacy PHP inventory system I built as a college capstone project. LAIM transforms manual spreadsheet-based asset tracking into an automated, API-driven platform with multi-source device discovery, role-based access control, and modern deployment patterns.
The original system was functional but showed its age -table-based layouts, popup windows, hardcoded dropdown values, and no API layer. LAIM addresses these limitations while adding capabilities that weren't feasible in the original architecture: automated device sync from Netdisco and LibreNMS, backup/restore functionality, and a proper separation of concerns.
This project is open source! Check out the code, fork it, or give it a star if you find it useful.
- PHP + MySQL + Nginx stack
- Table-based layouts, inline CSS
- window.open() popup forms
- Full page refresh for search
- Hardcoded categories (37 locations!)
- Manual registration via email
- Basic session auth, no RBAC
- No API, server-rendered only
- FastAPI + PostgreSQL + async
- Tailwind CSS, responsive design
- Modal dialogs, SPA-like UX
- Real-time client-side filtering
- Configurable via settings API
- Self-service registration with roles
- JWT auth with RBAC enforcement
- RESTful API with OpenAPI docs
Modern async Python stack with external integrations for automated device discovery:
Modern Python framework with automatic OpenAPI docs, async/await support, and dependency injection
Relational database with asyncpg driver for high-performance async queries
Async ORM with Alembic migrations for schema versioning and type-safe queries
Multi-source network discovery with intelligent merge logic for automated asset population
01 Async Database Operations
Session Management
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import create_async_engine engine = create_async_engine( DATABASE_URL, pool_size=10, max_overflow=20 ) async def get_db() -> AsyncGenerator: async with AsyncSession(engine) as session: yield session Route with Dependency Injection
@app.get("/api/items") async def list_items( db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), search: str = None, item_type: ItemType = None ): query = select(InventoryItem) .where(InventoryItem.is_active == True) if search: query = query.where( InventoryItem.hostname.ilike(f"%{search}%") ) return await db.execute(query) 02 Multi-Source Device Sync
Deduplication Strategy
# Priority: serial_number > mac_address > hostname+ip def merge_devices(netdisco: list, librenms: list): merged = {} for device in netdisco + librenms: key = ( device.serial_number or device.mac_address or f"{device.hostname}:{device.ip}" ) if key in merged: # LibreNMS has higher data quality merged[key].update(device) else: merged[key] = device return list(merged.values()) Scheduled Sync
from apscheduler.schedulers.asyncio import AsyncIOScheduler scheduler = AsyncIOScheduler() @scheduler.scheduled_job( 'interval', hours=SYNC_INTERVAL_HOURS ) async def scheduled_sync(): async with AsyncSession(engine) as db: await sync_service.sync_all(db) Device sync fetches from both Netdisco (L2/L3 topology) and LibreNMS (SNMP data), then merges by serial number, MAC address, or hostname+IP to prevent duplicates while preserving the highest-quality data from each source.
03 JWT Authentication & RBAC
Token Generation
from jose import jwt from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"]) def create_access_token(user_id: int) -> str: expire = datetime.utcnow() + timedelta(hours=8) return jwt.encode( {"sub": str(user_id), "exp": expire}, SECRET_KEY, algorithm="HS256" ) Role Enforcement
class UserRole(str, Enum): SUPERUSER = "superuser" ADMIN = "admin" def require_admin(user = Depends(get_current_user)): if user.role not in [Role.ADMIN, Role.SUPERUSER]: raise HTTPException(403, "Admin required") return user @app.post("/api/items") async def create_item( data: ItemCreate, user: User = Depends(require_admin) ): ... - Async/await patterns
- Dependency injection
- Pydantic validation
- Background tasks
- SQLAlchemy ORM
- Alembic migrations
- Indexing strategy
- Soft deletes
- httpx async client
- Retry logic
- Rate limiting
- Data merging
- Jinja2 templating
- Tailwind CSS
- Dark/light mode
- Real-time search
- Docker Compose
- Health checks
- Environment config
- Non-root containers
Single-command deployment with PostgreSQL and FastAPI services, health checks, and named volumes for data persistence.
Non-root container user, HttpOnly JWT cookies, bcrypt password hashing, and environment-based secrets management.
Environment variables for all settings: database URL, API tokens, sync intervals, room locations, and excluded IP ranges.
Alembic-managed schema with versioned migrations. Current schema at version 006 with full upgrade/downgrade paths.
- ✓ Core CRUD operations functional
- ✓ JWT authentication with RBAC
- ✓ Netdisco + LibreNMS sync integration
- ✓ Backup/restore system operational
- ✓ Docker deployment configured
- ✓ Dark/light mode with real-time search