diff --git a/api/admin_ui/src/components/PluginMarketplace.tsx b/api/admin_ui/src/components/PluginMarketplace.tsx new file mode 100644 index 00000000..76e2f7e6 --- /dev/null +++ b/api/admin_ui/src/components/PluginMarketplace.tsx @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; +import { Box, Typography, TextField, InputAdornment, Grid, Card, CardContent } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; + +/** + * Minimal placeholder for the Admin Console Plugin Marketplace. + * - Strict TypeScript compliant (no unused vars) + * - Not wired into the App shell yet; safe to compile + * - No provider name checks; trait‑gated wiring will come in later PRs + */ +export default function PluginMarketplace(): JSX.Element { + const [query, setQuery] = useState(''); + + return ( + + + Plugin Marketplace + + + setQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{ mb: 3 }} + /> + + + {/* Empty state placeholder; results will be populated in later PRs */} + + + + + Marketplace results will appear here. Use traits to gate provider‑specific UI. + + + + + + + ); +} + diff --git a/api/app/main.py b/api/app/main.py index 2710769f..f027e97a 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -316,6 +316,14 @@ async def v4_config_flush_cache(scope: Optional[str] = None): # Non-fatal if health monitoring deps missing pass +# Admin marketplace (Phase 4; disabled by default) +try: + from .routers import admin_marketplace as _marketplace + app.include_router(_marketplace.router) +except Exception: + # Non-fatal if marketplace router cannot be imported + pass + # Webhook hardening router (DLQ + idempotency) try: from .routers import webhooks_v2 as _webhooks_v2 diff --git a/api/app/routers/admin_marketplace.py b/api/app/routers/admin_marketplace.py new file mode 100644 index 00000000..f4aff289 --- /dev/null +++ b/api/app/routers/admin_marketplace.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import os +from typing import Optional +from fastapi import APIRouter, Depends, Header, HTTPException +from api.app.config import settings +from api.app.auth import verify_db_key + + +def require_admin(x_api_key: Optional[str] = Header(default=None)): + """Minimal admin auth dependency for marketplace endpoints. + - Accepts env bootstrap API key + - Or a DB-backed key with scope keys:manage + """ + if settings.api_key and x_api_key == settings.api_key: + return {"admin": True, "key_id": "env"} + info = verify_db_key(x_api_key) + if not info or ("keys:manage" not in (info.get("scopes") or [])): + raise HTTPException(401, detail="Admin authentication failed") + return info + + +router = APIRouter(prefix="/admin/marketplace", tags=["AdminMarketplace"], dependencies=[Depends(require_admin)]) + + +@router.get("/plugins") +def list_plugins(): + """List available plugins in the marketplace (disabled by default). + + Gate with ADMIN_MARKETPLACE_ENABLED=false by default to avoid exposing in prod + until features are complete. + """ + enabled = os.getenv("ADMIN_MARKETPLACE_ENABLED", "false").lower() in {"1", "true", "yes"} + if not enabled: + # Hide endpoint when disabled to avoid confusing operators + raise HTTPException(404, detail="Not found") + # Placeholder: will be populated via trait‑aware registry in later PRs + return {"plugins": []} +