diff --git a/server/api.py b/server/api.py index 5303329..ff7e987 100644 --- a/server/api.py +++ b/server/api.py @@ -557,8 +557,13 @@ async def api_market_upload(file: UploadFile = File(...), name: str = "", catego return {"error": "无效的仪表盘文件:缺少 manifest.json"} if 'index.html' not in names: return {"error": "无效的仪表盘文件:缺少 index.html"} - manifest = _json.loads(zf.read('manifest.json').decode('utf-8')) + + if not manifest.get('id'): + return {"error": "manifest.json 缺少 id"} + if not manifest.get('name'): + return {"error": "manifest.json 缺少 name"} + actual_name = name or manifest.get('name', file.filename.replace('.tsd', '')) actual_category = category or manifest.get('category', 'community') actual_author = author or manifest.get('author', 'Unknown') @@ -604,3 +609,84 @@ async def api_market_install(id: str = "", filename: str = ""): return {"ok": ok} except Exception as e: return {"ok": False, "error": str(e)} + + +# ---- Market Scenes ---- +@router.get("/market/scenes") +async def api_market_scenes(): + 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/scenes/records?sort=-created&perPage=50") + return resp.json() + except Exception: + return {"items": []} + + +@router.post("/market/scenes/upload") +async def api_market_upload_scene(file: UploadFile = File(...), token: str = ""): + import io, zipfile, json as _json + if not file.filename or not file.filename.endswith('.tss'): + return {"error": "仅支持 .tss 文件"} + try: + zip_data = await file.read() + with zipfile.ZipFile(io.BytesIO(zip_data), 'r') as zf: + if 'scene.json' not in zf.namelist(): + return {"error": "缺少 scene.json"} + scene = _json.loads(zf.read('scene.json').decode('utf-8')) + if not scene.get('name'): + return {"error": "scene.json 缺少 name"} + deps = set() + for canvas in scene.get('canvases', []): + for p in canvas.get('placements', []): + if p.get('dashboard_id'): + deps.add(p['dashboard_id']) + except zipfile.BadZipFile: + return {"error": "无效的 zip 文件"} + except Exception as e: + return {"error": str(e)} + + import httpx as _httpx + cfg = get_config() + url = cfg.get("market_url", "http://127.0.0.1:5301") + try: + async with _httpx.AsyncClient(timeout=30) as client: + resp = await client.post( + f"{url}/api/collections/scenes/records", + headers={"Authorization": f"Bearer {token}"}, + data={"name": scene.get('name'), "description": scene.get('description',''), "game_id": scene.get('game_id',''), "author": scene.get('author','') or "Unknown", "dashb_pending": True}, + files={"file": (file.filename, zip_data, file.content_type)}, + ) + result = resp.json() + result["dependencies"] = list(deps) + return result + except Exception as e: + return {"error": str(e)} + + +@router.get("/market/scenes/install") +async def api_market_install_scene(id: str = "", filename: str = ""): + import httpx, io, zipfile, json as _json + 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/scenes/{id}/{filename}") + zip_data = resp.content + missing = [] + with zipfile.ZipFile(io.BytesIO(zip_data), 'r') as zf: + if 'scene.json' in zf.namelist(): + scene = _json.loads(zf.read('scene.json').decode('utf-8')) + from models.dashboard import dashboard_manager + for canvas in scene.get('canvases', []): + for p in canvas.get('placements', []): + did = p.get('dashboard_id', '') + if did and not dashboard_manager.get(did): + missing.append(did) + from models.scene import scene_manager + ok = scene_manager.import_scene_zip(zip_data) + return {"ok": ok, "missing_dashboards": missing} + except Exception as e: + return {"ok": False, "error": str(e)} diff --git a/static/js/pages/market.js b/static/js/pages/market.js index 7be221a..4eb85b2 100644 --- a/static/js/pages/market.js +++ b/static/js/pages/market.js @@ -1,6 +1,7 @@ const PageMarket = { _el: null, _marketToken: null, + _tab: 'dashboards', init() { this._el = document.getElementById('page-market'); @@ -18,24 +19,29 @@ const PageMarket = { const grid = document.getElementById('market-grid'); if (!grid) return; grid.innerHTML = '
浏览并安装社区分享的仪表盘 未登录
+浏览并安装社区分享的仪表盘和场景 未登录