mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
|
|
"""Mesh-free preview launcher for the MeshAI dashboard API.
|
||
|
|
|
||
|
|
Stands up ONLY the FastAPI dashboard (NO MeshAI instance, NO mesh connector,
|
||
|
|
NO broadcast loop) so the region-routing UI can be developed and tested without
|
||
|
|
a live mesh radio.
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python3 scripts/preview_dashboard.py [config_dir]
|
||
|
|
|
||
|
|
config_dir defaults to /tmp/meshai-region-preview/config.
|
||
|
|
The directory may be empty — an empty/missing config.yaml yields safe defaults.
|
||
|
|
|
||
|
|
The script sets app.state.config and app.state.config_path so all config-backed
|
||
|
|
endpoints work. Endpoints that read other app.state attrs (notification_router,
|
||
|
|
env_store, alert_engine, etc.) guard with getattr(..., None) already, so they
|
||
|
|
return graceful empty/404 responses rather than crashing.
|
||
|
|
|
||
|
|
Run vite separately for the frontend:
|
||
|
|
cd dashboard-frontend && npm run dev
|
||
|
|
|
||
|
|
Then hit the API at http://localhost:8082/api/...
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# Resolve the config directory from the first CLI arg or use the default preview path.
|
||
|
|
cfg_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/tmp/meshai-region-preview/config")
|
||
|
|
cfg_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
# Ensure there is a minimal config.yaml so load_config doesn't error.
|
||
|
|
_cfg_yaml = cfg_dir / "config.yaml"
|
||
|
|
if not _cfg_yaml.exists():
|
||
|
|
_cfg_yaml.write_text("# meshai preview config\n")
|
||
|
|
|
||
|
|
# Import AFTER path is ready so relative imports resolve correctly.
|
||
|
|
from meshai.dashboard.server import create_app
|
||
|
|
from meshai.config_loader import load_config
|
||
|
|
import uvicorn
|
||
|
|
|
||
|
|
app = create_app()
|
||
|
|
cfg = load_config(cfg_dir)
|
||
|
|
app.state.config = cfg
|
||
|
|
app.state.config_path = cfg_dir / "config.yaml"
|
||
|
|
|
||
|
|
# Stub out attrs that endpoints guard with getattr(..., None) to avoid AttributeError
|
||
|
|
# on first access in case FastAPI touches app.state eagerly.
|
||
|
|
for _attr in ("notification_router", "env_store", "alert_engine", "health_engine",
|
||
|
|
"data_store", "connector", "bus", "danger_correlator", "mesh_context",
|
||
|
|
"broadcaster"):
|
||
|
|
if not hasattr(app.state, _attr):
|
||
|
|
setattr(app.state, _attr, None)
|
||
|
|
|
||
|
|
# PREVIEW-ONLY: coverage is a restart-required section, so a normal deploy keeps
|
||
|
|
# the running config stale until restart. In the preview we want create/delete/save
|
||
|
|
# to reflect immediately, so reload the in-memory config from disk after any
|
||
|
|
# /api/config write. (Prod keeps the restart-gated semantics; this hook is local.)
|
||
|
|
@app.middleware("http")
|
||
|
|
async def _reload_config_after_write(request, call_next):
|
||
|
|
response = await call_next(request)
|
||
|
|
try:
|
||
|
|
if (request.method in ("PUT", "POST", "DELETE", "PATCH")
|
||
|
|
and request.url.path.startswith("/api/config")):
|
||
|
|
app.state.config = load_config(cfg_dir)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
return response
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
print(f"[preview] config dir : {cfg_dir}")
|
||
|
|
print(f"[preview] config path: {app.state.config_path}")
|
||
|
|
print(f"[preview] listening : http://0.0.0.0:8082")
|
||
|
|
print(f"[preview] regions API: http://localhost:8082/api/notifications/regions")
|
||
|
|
print(f"[preview] matrix API : http://localhost:8082/api/notifications/region-routing")
|
||
|
|
uvicorn.run(app, host="0.0.0.0", port=8082)
|