|
21 | 21 | from fastapi.templating import Jinja2Templates |
22 | 22 | from pydantic import BaseModel |
23 | 23 |
|
24 | | -from app import auth, catalog, compose_generator, database, orchestrator |
| 24 | +from app import auth, catalog, compose_generator, database, exchange_rates, orchestrator |
25 | 25 |
|
26 | 26 | logging.basicConfig( |
27 | 27 | level=logging.INFO, |
@@ -138,7 +138,9 @@ async def lifespan(app: FastAPI): |
138 | 138 | scheduler.add_job(_run_health_check, "interval", minutes=5, id="health_check") |
139 | 139 | scheduler.add_job(_check_stale_workers, "interval", minutes=2, id="stale_workers") |
140 | 140 | scheduler.add_job(_run_data_retention, "interval", hours=24, id="data_retention") |
| 141 | + scheduler.add_job(exchange_rates.refresh, "interval", minutes=15, id="exchange_rates") |
141 | 142 | scheduler.start() |
| 143 | + await exchange_rates.refresh() |
142 | 144 | docker_mode = "direct" if orchestrator.docker_available() else "monitor-only" |
143 | 145 | logger.info("CashPilot started (Docker: %s)", docker_mode) |
144 | 146 |
|
@@ -489,6 +491,7 @@ async def api_services_deployed(request: Request) -> list[dict[str, Any]]: |
489 | 491 | # Get latest earnings per platform for balance display |
490 | 492 | earnings = await database.get_earnings_summary() |
491 | 493 | balance_map = {e["platform"]: e["balance"] for e in earnings} |
| 494 | + currency_map = {e["platform"]: e["currency"] for e in earnings} |
492 | 495 |
|
493 | 496 | # Get health scores |
494 | 497 | health_scores = await database.get_health_scores(7) |
@@ -542,6 +545,7 @@ async def api_services_deployed(request: Request) -> list[dict[str, Any]]: |
542 | 545 | "name": svc["name"] if svc else slug, |
543 | 546 | "container_status": agg["best_status"], |
544 | 547 | "balance": balance_map.get(slug, 0.0), |
| 548 | + "currency": currency_map.get(slug, "USD"), |
545 | 549 | "cpu": f"{agg['total_cpu']:.2f}", |
546 | 550 | "memory": f"{agg['total_mem']:.1f} MB", |
547 | 551 | "image": agg["image"], |
@@ -577,6 +581,7 @@ async def api_services_deployed(request: Request) -> list[dict[str, Any]]: |
577 | 581 | "name": svc["name"] if svc else slug, |
578 | 582 | "container_status": "external", |
579 | 583 | "balance": balance_map.get(slug, 0.0), |
| 584 | + "currency": currency_map.get(slug, "USD"), |
580 | 585 | "cpu": "", |
581 | 586 | "memory": "", |
582 | 587 | "image": "", |
@@ -900,6 +905,14 @@ async def api_earnings_summary(request: Request) -> dict[str, Any]: |
900 | 905 | _require_auth_api(request) |
901 | 906 | summary = await database.get_earnings_dashboard_summary() |
902 | 907 |
|
| 908 | + # Include non-USD balances converted to USD in the total |
| 909 | + all_earnings = await database.get_earnings_summary() |
| 910 | + for e in all_earnings: |
| 911 | + if e["currency"] != "USD": |
| 912 | + usd_val = exchange_rates.to_usd(e["balance"], e["currency"]) |
| 913 | + if usd_val is not None: |
| 914 | + summary["total"] = round(summary["total"] + usd_val, 2) |
| 915 | + |
903 | 916 | # Count active (running) services — use cached data for instant response |
904 | 917 | active = 0 |
905 | 918 | try: |
@@ -991,6 +1004,13 @@ async def api_collector_alerts(request: Request) -> list[dict[str, str]]: |
991 | 1004 | return _collector_alerts |
992 | 1005 |
|
993 | 1006 |
|
| 1007 | +@app.get("/api/exchange-rates") |
| 1008 | +async def api_exchange_rates(request: Request) -> dict[str, Any]: |
| 1009 | + """Return current exchange rates (fiat + crypto) for frontend conversion.""" |
| 1010 | + _require_auth_api(request) |
| 1011 | + return exchange_rates.get_all() |
| 1012 | + |
| 1013 | + |
994 | 1014 | @app.get("/api/services/{slug}/per-node-earnings") |
995 | 1015 | async def api_per_node_earnings(request: Request, slug: str) -> list[dict[str, Any]]: |
996 | 1016 | """Return per-node earnings for services that support it (e.g. MystNodes).""" |
|
0 commit comments