feat(dashboard): add hide/show toggle for dashboard plugins in sidebar

- New config key: dashboard.hidden_plugins (list of plugin names)
- GET /api/dashboard/plugins now filters out hidden plugins from sidebar
- POST /api/dashboard/plugins/{name}/visibility toggles visibility
- Hub response includes user_hidden boolean per plugin row
- Eye/EyeOff toggle on plugin cards with dashboard manifests
- i18n: 'Show in sidebar' / 'Hide from sidebar' (en/zh)
This commit is contained in:
Austin Pickett 2026-04-30 20:02:15 -04:00
parent a52363231f
commit c73b799de7
6 changed files with 77 additions and 3 deletions

View file

@ -3617,12 +3617,16 @@ def _get_dashboard_plugins(force_rescan: bool = False) -> list:
@app.get("/api/dashboard/plugins")
async def get_dashboard_plugins():
"""Return discovered dashboard plugins."""
"""Return discovered dashboard plugins (excludes user-hidden ones)."""
plugins = _get_dashboard_plugins()
# Strip internal fields before sending to frontend.
# Read user's hidden plugins list from config.
config = load_config()
hidden: list = cfg_get(config, "dashboard", "hidden_plugins", default=[]) or []
# Strip internal fields before sending to frontend and filter out hidden.
return [
{k: v for k, v in p.items() if not k.startswith("_")}
for p in plugins
if p["name"] not in hidden
]
@ -3662,6 +3666,10 @@ def _merged_plugins_hub() -> Dict[str, Any]:
disabled_set = _get_disabled_set()
enabled_set = _get_enabled_set()
# Read user-hidden plugins from config for the user_hidden field.
config = load_config()
hidden_plugins: list = cfg_get(config, "dashboard", "hidden_plugins", default=[]) or []
plugins_root_resolved = (get_hermes_home() / "plugins").resolve()
rows: List[Dict[str, Any]] = []
@ -3718,6 +3726,7 @@ def _merged_plugins_hub() -> Dict[str, Any]:
"can_update_git": can_remove_update and (Path(dir_str) / ".git").exists(),
"auth_required": auth_required,
"auth_command": auth_command,
"user_hidden": name in hidden_plugins,
})
agent_names = {r["name"] for r in rows}
@ -3863,6 +3872,33 @@ async def put_plugin_providers(request: Request, body: _PluginProvidersPutBody):
return {"ok": True}
class _PluginVisibilityBody(BaseModel):
hidden: bool
@app.post("/api/dashboard/plugins/{name}/visibility")
async def post_plugin_visibility(request: Request, name: str, body: _PluginVisibilityBody):
"""Toggle a plugin's sidebar visibility (persists to config.yaml dashboard.hidden_plugins)."""
_require_token(request)
name = _validate_plugin_name(name)
config = load_config()
if "dashboard" not in config or not isinstance(config.get("dashboard"), dict):
config["dashboard"] = {}
hidden_list: list = config["dashboard"].get("hidden_plugins") or []
if not isinstance(hidden_list, list):
hidden_list = []
if body.hidden and name not in hidden_list:
hidden_list.append(name)
elif not body.hidden and name in hidden_list:
hidden_list.remove(name)
config["dashboard"]["hidden_plugins"] = hidden_list
save_config(config)
return {"ok": True, "name": name, "hidden": body.hidden}
@app.get("/dashboard-plugins/{plugin_name}/{file_path:path}")
async def serve_plugin_asset(plugin_name: str, file_path: str):
"""Serve static assets from a dashboard plugin directory.