feat: marketplace integration via backend proxy
- market_url config (server-side only, never exposed to frontend) - /api/market/* proxy endpoints to PocketBase - Dashboard page: 'Market' tab in sub-sidebar - Market list: browse/install community dashboards - Login/register modal, upload to market - PocketBase API fully proxied, zero credentials in frontend
This commit is contained in:
@@ -487,3 +487,88 @@ async def api_delete_recording(filename: str):
|
||||
path = RECORDINGS_DIR / filename
|
||||
if path.exists(): path.unlink()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ---- Market Proxy ----
|
||||
@router.get("/market/dashboards")
|
||||
async def api_market_list():
|
||||
import httpx
|
||||
cfg = get_config()
|
||||
url = cfg.get("market_url", "http://127.0.0.1:5301")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.get(f"{url}/api/collections/dashboards/records?sort=-created&perPage=50")
|
||||
return resp.json()
|
||||
except Exception:
|
||||
return {"items": []}
|
||||
|
||||
|
||||
@router.post("/market/auth")
|
||||
async def api_market_auth(data: dict):
|
||||
import httpx
|
||||
cfg = get_config()
|
||||
url = cfg.get("market_url", "http://127.0.0.1:5301")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.post(
|
||||
f"{url}/api/collections/users/auth-with-password",
|
||||
json={"identity": data.get("email"), "password": data.get("password")}
|
||||
)
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.post("/market/register")
|
||||
async def api_market_register(data: dict):
|
||||
import httpx
|
||||
cfg = get_config()
|
||||
url = cfg.get("market_url", "http://127.0.0.1:5301")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.post(
|
||||
f"{url}/api/collections/users/records",
|
||||
json={
|
||||
"email": data.get("email"),
|
||||
"password": data.get("password"),
|
||||
"passwordConfirm": data.get("passwordConfirm"),
|
||||
}
|
||||
)
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.post("/market/upload")
|
||||
async def api_market_upload(file: UploadFile = File(...), name: str = "", category: str = "", author: str = "", token: str = ""):
|
||||
import httpx
|
||||
cfg = get_config()
|
||||
url = cfg.get("market_url", "http://127.0.0.1:5301")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
files_httpx = {"file": (file.filename, await file.read(), file.content_type)}
|
||||
resp = await client.post(
|
||||
f"{url}/api/collections/dashboards/records",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
data={"name": name, "category": category, "author": author, "version": "1.0.0"},
|
||||
files=files_httpx,
|
||||
)
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.get("/market/install")
|
||||
async def api_market_install(id: str = "", filename: str = ""):
|
||||
import httpx
|
||||
cfg = get_config()
|
||||
url = cfg.get("market_url", "http://127.0.0.1:5301")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client:
|
||||
resp = await client.get(f"{url}/api/files/dashboards/{id}/{filename}")
|
||||
zip_data = resp.content
|
||||
from models.dashboard import dashboard_manager
|
||||
ok = dashboard_manager.import_theme_zip(zip_data)
|
||||
return {"ok": ok}
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
Reference in New Issue
Block a user