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:
2026-07-26 19:52:00 +08:00
parent 06042a93a2
commit 683e573e9a
4 changed files with 217 additions and 45 deletions
+85
View File
@@ -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)}