# Vela Platform 🏔️ A lightweight self-hosted P&L tracking app for a C2C server/storage business. ## Stack - Python 3.11+ with FastAPI + uvicorn - SQLite via SQLAlchemy (single file, zero setup) - Jinja2 templates + Bootstrap 5 dark frontend - Chart.js for trend charts - JWT auth (cookie + Bearer header) - stdio MCP server for Hermes integration ## Data Model ### User - id, username, password_hash, display_name, role (admin|viewer) ### Service (Offer) - id, name (Atlas/Atlas+/Rif/Rif+), description - sell_price (computed from components), cost_price (computed from components) - active ### ServiceComponent - id, service_id (FK), name (RAM/HDD/SSD/etc.), unit_cost, unit_sell, quantity, notes - `recompute_prices()` method sums cost/sell from components ### Transaction - id, service_id (FK), quantity, revenue (calculated: qty * service.sell_price), cost (calculated: qty * service.cost_price), month (YYYY-MM), notes, created_by (FK user), created_at ### Monthly P&L (computed) - month, total_revenue, total_cost, gross_profit, gross_margin%, opex (800 MAD), net_profit, net_margin% ## Preseed Data - Users: admin (password: admin123), viewer (password: viewer123) - Services with components (see COMPONENT_TEMPLATES in main.py) - Opex: 800 MAD/month hardcoded ## Key Files | Path | What | |------|------| | `backend/main.py` | FastAPI app (API + frontend routes) | | `backend/models.py` | SQLAlchemy models | | `backend/auth.py` | JWT auth, cookie support | | `backend/database.py` | SQLite setup | | `backend/templates/` | Jinja2 templates | | `mcp/server.py` | MCP stdio server | | `CLAUDE.md` | This file | ## API Endpoints ### Auth - POST /api/auth/login -> {token, user} + set cookie - GET /api/auth/me -> current user ### Services - GET /api/services -> list with components - GET /api/services/{id} -> single with components - POST /api/services -> create (admin) - PUT /api/services/{id} -> update (admin) - DELETE /api/services/{id} -> deactivate (admin) ### Components - GET /api/services/{id}/components -> list - POST /api/services/{id}/components -> create (admin, JSON body) - PUT /api/components/{id} -> update (admin, JSON body) — auto-recomputes prices - DELETE /api/components/{id} -> delete (admin) — auto-recomputes prices ### Transactions - GET /api/transactions?month=YYYY-MM -> list - POST /api/transactions -> create {service_id, quantity, month, notes} - DELETE /api/transactions/{id} -> (admin) ### P&L - GET /api/pnl?month=YYYY-MM -> computed P&L - GET /api/dashboard -> current + 12-month trend ## MCP Server (stdio) Located at `mcp/server.py`. Run as a stdio MCP server via `python3 mcp/server.py`. Tools: get_pnl, get_services, get_service, add_component, update_component, delete_component, add_transaction, get_dashboard Resources: vela://pnl/current, vela://services, vela://services/{id} ## URLs - Web app: http://192.168.1.30:8788 - MCP: registered as `vela-platform` in Hermes config ## Component Pricing Templates Each offer has its own set of components. The total sell/cost is auto-computed: ### Atlas (1TB Storage) HDD(1TB) 600/1000, RAM(16GB) 300/500, CPU alloc 200/400, Casing 200/300, Bandwidth 200/500, Setup 300/600, Support 200/700 → Total: cost 2000, sell 4000 ### Atlas+ (1TB + Backup) HDD(1TB) 600/1000, SSD(256GB) 400/700, RAM(32GB) 600/1000, CPU alloc 300/600, Casing 200/300, Bandwidth 200/500, Sauvegarde 300/500, Setup 200/400, Support 200/700 → Total: cost 2800, sell 5000 ### Rif (500GB Server) SSD(500GB) 300/600, RAM(16GB) 300/500, CPU alloc 200/400, Casing 200/300, Bandwidth 200/500, Setup 300/600, Support 200/700 → Total: cost 1700, sell 3500 ### Rif+ (500GB + Backup) SSD(500GB) 300/600, HDD(1TB) 600/1000, RAM(32GB) 600/1000, CPU alloc 300/600, Casing 200/300, Bandwidth 200/500, Sauvegarde 300/500, Setup 200/400, Support 200/700 → Total: cost 2200, sell 4500 ## Deployment ```bash cd ~/bestof-manager/backend && uvicorn main:app --host 0.0.0.0 --port 8788 # Or via systemd: systemctl --user restart bestof-manager.service ``` ## Auth Flow - Page routes use cookie auth (httpOnly 'token' cookie set on login) - API routes use Bearer token from Authorization header - Login sets both cookie AND returns JSON token - Token expires after 24h - Pages without valid cookie redirect to /login