fix(ci): fix 3 pre-existing CI failures + doc updates
- i18n: add 115 missing EN translation keys (parity with FA) - csrf: test /dashboard-legacy/keys (SPA owns /dashboard/*) - oauth: add tier scopes to test fixture allowed_scopes - DOCKER_README: v3.13.0 — remove Appwrite/Directus, clarify 8 plugins - getting-started: remove Appwrite/Directus, add Coolify endpoint row - Overview.tsx: fix Lang type for AdminStatsPanel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
108
DOCKER_README.md
108
DOCKER_README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
**The AI-native management hub for WordPress, WooCommerce, and self-hosted services.**
|
||||
|
||||
633 tools across 10 plugins (incl. Coolify). Connect your sites, stores, repos, and databases — manage them all through Claude, ChatGPT, Cursor, or any MCP client.
|
||||
8 plugins, hundreds of tools. Connect your sites, stores, repos, and databases — manage them all through Claude, ChatGPT, Cursor, or any MCP client.
|
||||
|
||||
> **Don't want to self-host?** Try the hosted instance at **[mcp.palebluedot.live](https://mcp.palebluedot.live)** — log in with GitHub or Google, add your sites, and connect your AI client in minutes.
|
||||
|
||||
@@ -37,6 +37,8 @@ curl http://localhost:8000/health
|
||||
# http://localhost:8000/dashboard
|
||||
```
|
||||
|
||||
Log in with your `MASTER_API_KEY`, then add sites via **My Sites → Add Service**.
|
||||
|
||||
### 4. Connect your AI client
|
||||
|
||||
**Claude Desktop** (`claude_desktop_config.json`):
|
||||
@@ -55,7 +57,7 @@ curl http://localhost:8000/health
|
||||
}
|
||||
```
|
||||
|
||||
**VS Code** (`.vscode/mcp.json`):
|
||||
**VS Code / Claude Code** (`.vscode/mcp.json` or `.mcp.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -71,7 +73,7 @@ curl http://localhost:8000/health
|
||||
}
|
||||
```
|
||||
|
||||
> Use a plugin-specific endpoint (e.g., `/wordpress/mcp`) instead of `/mcp` to reduce tool count and save tokens. See [Endpoints](#endpoints) below.
|
||||
> Use a plugin-specific endpoint (e.g., `/wordpress/mcp`) instead of `/mcp` to keep the tool list focused and save tokens. See [Endpoints](#endpoints) below.
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -81,20 +83,18 @@ MCP Hub uses **Bearer token** authentication:
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
> `X-API-Key` header and query parameter auth are **not** supported.
|
||||
|
||||
## Endpoints
|
||||
|
||||
Use the most specific endpoint for your use case:
|
||||
|
||||
| Endpoint | Tools | `site` param? | Best for |
|
||||
|----------|------:|:-------------:|----------|
|
||||
| `/u/{user_id}/{alias}/mcp` | 22-100 | No (pre-scoped) | Hosted/OAuth users |
|
||||
| `/project/{alias}/mcp` | 22-100 | No (pre-scoped) | Single-site workflow |
|
||||
| `/{plugin}/mcp` | 23-101 | Yes | Multi-site management |
|
||||
| `/mcp` | 633 | Yes | Admin & discovery only |
|
||||
| Endpoint | Use case |
|
||||
|----------|----------|
|
||||
| `/u/{user_id}/{alias}/mcp` | Hosted/OAuth users — single pre-scoped service |
|
||||
| `/project/{alias}/mcp` | Single-site workflow (recommended) |
|
||||
| `/{plugin}/mcp` | Multi-site management for one service type |
|
||||
| `/mcp` | Admin & discovery — all enabled tools |
|
||||
|
||||
Available plugin endpoints: `/wordpress/mcp`, `/woocommerce/mcp`, `/wordpress-specialist/mcp`, `/gitea/mcp`, `/n8n/mcp`, `/supabase/mcp`, `/openpanel/mcp`, `/appwrite/mcp`, `/directus/mcp`, `/system/mcp`
|
||||
Available plugin endpoints: `/wordpress/mcp`, `/woocommerce/mcp`, `/wordpress-specialist/mcp`, `/gitea/mcp`, `/n8n/mcp`, `/supabase/mcp`, `/openpanel/mcp`, `/coolify/mcp`, `/system/mcp`
|
||||
|
||||
## Using Docker Compose
|
||||
|
||||
@@ -118,61 +118,71 @@ volumes:
|
||||
mcphub-logs:
|
||||
```
|
||||
|
||||
> **WP-CLI tools** (cache flush, database export, plugin updates via CLI) require Docker socket access.
|
||||
> Add `WORDPRESS_SITE1_CONTAINER=your-wp-container-name` to your `.env` and uncomment the Docker socket volume above.
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
> **WP-CLI tools** (cache flush, database export, plugin management via CLI) require Docker socket access. Uncomment the socket volume and set the `container` field when adding the WordPress site in the dashboard.
|
||||
|
||||
## After Starting
|
||||
|
||||
| URL | Description |
|
||||
|-----|-------------|
|
||||
| `http://localhost:8000/health` | Health check & status |
|
||||
| `http://localhost:8000/dashboard` | Web dashboard (manage sites, API keys, OAuth clients, health monitoring). Login via MASTER_API_KEY or GitHub/Google OAuth |
|
||||
| `http://localhost:8000/mcp` | MCP endpoint (connect AI clients here) |
|
||||
| `http://localhost:8000/dashboard` | Web dashboard — manage sites, API keys, OAuth clients, health |
|
||||
| `http://localhost:8000/mcp` | MCP endpoint (connect your AI client here) |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `MASTER_API_KEY` | Recommended | API key for authentication. If omitted, a temporary key is auto-generated and printed to logs |
|
||||
| `WORDPRESS_SITE1_URL` | For WP | WordPress site URL |
|
||||
| `WORDPRESS_SITE1_USERNAME` | For WP | WordPress admin username |
|
||||
| `WORDPRESS_SITE1_APP_PASSWORD` | For WP | WordPress Application Password |
|
||||
| `WORDPRESS_SITE1_ALIAS` | Recommended | Friendly name (e.g., `myblog`) |
|
||||
| `WORDPRESS_SITE1_CONTAINER` | For WP-CLI | Docker container name of your WordPress site (enables cache/db/system tools) |
|
||||
| `OAUTH_JWT_SECRET_KEY` | For OAuth | JWT secret for ChatGPT auto-registration (not needed for Claude/Cursor) |
|
||||
| `OAUTH_BASE_URL` | For OAuth | Public URL of your server (not needed for Claude/Cursor) |
|
||||
| `GITHUB_CLIENT_ID` | For Social Login | GitHub OAuth App client ID |
|
||||
| `GITHUB_CLIENT_SECRET` | For Social Login | GitHub OAuth App client secret |
|
||||
| `GOOGLE_CLIENT_ID` | For Social Login | Google OAuth client ID |
|
||||
| `GOOGLE_CLIENT_SECRET` | For Social Login | Google OAuth client secret |
|
||||
| `PUBLIC_URL` | For Social Login | Public URL for OAuth callbacks (e.g., `https://mcp.example.com`) |
|
||||
| `ENCRYPTION_KEY` | For Multi-user | AES-256 key for encrypting user site credentials. Generate: `python -c "import os, base64; print(base64.b64encode(os.urandom(32)).decode())"` |
|
||||
### Required
|
||||
|
||||
> **CONTAINER**: Required for WP-CLI tools (cache flush, database export, system info) and **all** WordPress Advanced tools. Find your container name: `docker ps --filter name=wordpress`. Also requires Docker socket mount.
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `MASTER_API_KEY` | API key for admin access. If omitted, a temporary key is auto-generated and printed to logs. |
|
||||
|
||||
> **OAuth**: Only needed for ChatGPT Remote MCP auto-registration. For Claude Desktop, Claude Code, Cursor, and VS Code — just use `MASTER_API_KEY` with Bearer token auth.
|
||||
### Optional — OAuth & Social Login
|
||||
|
||||
> **Social Login**: Enable GitHub/Google login for multi-user mode. Users can add sites via the dashboard and get personal MCP endpoints (`/u/{user_id}/{alias}/mcp`).
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `OAUTH_JWT_SECRET_KEY` | JWT secret for ChatGPT Remote MCP auto-registration. Not needed for Claude/Cursor/VS Code. |
|
||||
| `OAUTH_BASE_URL` | Public URL of your server. Required for OAuth flows. |
|
||||
| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | Enable GitHub social login for multi-user mode. |
|
||||
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Enable Google social login for multi-user mode. |
|
||||
| `PUBLIC_URL` | Public URL for OAuth callbacks (e.g., `https://mcp.example.com`). |
|
||||
|
||||
Add more sites with `SITE2`, `SITE3`, etc. See [full configuration guide](https://github.com/airano-ir/mcphub/blob/main/docs/getting-started.md).
|
||||
### Optional — Multi-user & Encryption
|
||||
|
||||
## Supported Plugins
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ENCRYPTION_KEY` | — | AES-256-GCM key for encrypting user site credentials. Generate: `python -c "import os, base64; print(base64.b64encode(os.urandom(32)).decode())"` |
|
||||
| `MAX_SITES_PER_USER` | `10` | Maximum sites per user account (also configurable via the dashboard Settings page). |
|
||||
| `USER_RATE_LIMIT_PER_MIN` | `30` | Per-user rate limit (requests/minute). Configurable via dashboard. |
|
||||
| `USER_RATE_LIMIT_PER_HR` | `500` | Per-user rate limit (requests/hour). Configurable via dashboard. |
|
||||
| `ENABLED_PLUGINS` | `wordpress,woocommerce,supabase,openpanel,gitea` | Comma-separated list of plugins visible to public/OAuth users. Admin always sees all plugins. |
|
||||
|
||||
| Plugin | Tools | Env Prefix |
|
||||
|--------|-------|------------|
|
||||
| WordPress | 67 | `WORDPRESS_` |
|
||||
| WooCommerce | 28 | `WOOCOMMERCE_` |
|
||||
| WordPress Specialist | 51 | (DB-managed via dashboard) |
|
||||
| Gitea | 56 | `GITEA_` |
|
||||
| n8n | 56 | `N8N_` |
|
||||
| Supabase | 70 | `SUPABASE_` |
|
||||
| OpenPanel | 73 | `OPENPANEL_` |
|
||||
| Appwrite | 100 | `APPWRITE_` |
|
||||
| Directus | 100 | `DIRECTUS_` |
|
||||
### Optional — Logging
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `LOG_LEVEL` | `INFO` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
||||
|
||||
> **Site configuration** (URLs, credentials) is managed via the web dashboard — not environment variables. Log in and go to **My Sites → Add Service**.
|
||||
|
||||
## 8 Plugins, Hundreds of Tools
|
||||
|
||||
| Plugin | Approx. Tools | What you can do |
|
||||
|--------|------:|-----------------|
|
||||
| WordPress | ~70 | Posts, pages, media (incl. AI image gen), users, menus, taxonomies, SEO |
|
||||
| WooCommerce | ~30 | Products, orders, customers, coupons, reports, shipping |
|
||||
| WordPress Specialist | ~50 | Plugins, themes, options, cron, page editing, DB inspection, bulk fan-out |
|
||||
| Gitea | ~65 | Repos, issues, PRs, releases, webhooks, orgs, batch file ops |
|
||||
| n8n | ~55 | Workflows, executions, credentials, variables, audit |
|
||||
| Supabase | ~70 | Database, auth, storage, edge functions, realtime |
|
||||
| OpenPanel | ~40 | Events, export, insights, profiles, projects |
|
||||
| Coolify | ~65 | Apps, deployments, servers, projects, databases, services |
|
||||
| System | ~25 | Health monitoring, API keys, OAuth management, audit logs |
|
||||
|
||||
> **WordPress Specialist** requires [Airano MCP Bridge](https://wordpress.org/plugins/airano-mcp-bridge/) (v2.18.0+) installed on your WordPress site.
|
||||
|
||||
## Links
|
||||
|
||||
|
||||
@@ -775,6 +775,152 @@ DASHBOARD_TRANSLATIONS = {
|
||||
"uploads to /wp/v2/media; Consumer Key/Secret do not work for that. Optional."
|
||||
),
|
||||
"api_keys.user_intro": "Use these to authenticate MCP clients to your hub.",
|
||||
"api_keys.admin_intro": "Personal and machine keys for MCP clients. Each key has separate access and an independent log.",
|
||||
"api_keys.admin_empty_cta": "Create one to authenticate MCP clients.",
|
||||
"api_keys.admin_warning": (
|
||||
"Admin access grants full system control including destructive operations "
|
||||
"(delete, write env, system tools). Anyone with this key can act on all your "
|
||||
"sites. Unless the client truly needs it, choose a narrower scope."
|
||||
),
|
||||
"api_keys.user_empty_cta": "Create one to connect a client.",
|
||||
"api_keys.description": "Description",
|
||||
"api_keys.description_placeholder": "What is this key for?",
|
||||
"api_keys.expiry_label": "Expiry (days, optional)",
|
||||
"api_keys.expiry_placeholder": "Leave blank for no expiry",
|
||||
"api_keys.sensitive_warning": (
|
||||
"Reads backup files and environment variables that often contain sensitive data. "
|
||||
"Treat this key like a credential and do not share it over unencrypted channels."
|
||||
),
|
||||
"api_keys.confirm_delete": 'Permanently delete "{name}"?\nThis cannot be undone.',
|
||||
"api_keys.confirm_delete_user": 'Delete "{name}"?\nThis key will stop working immediately.',
|
||||
"api_keys.confirm_revoke": 'Revoke "{name}"?\nThis key will stop working immediately.',
|
||||
"api_keys.toast_deleted": "Key deleted",
|
||||
"api_keys.toast_revoked": "Key revoked",
|
||||
# Audit log
|
||||
"audit.intro": (
|
||||
"Every authentication, tool call, and settings change. GDPR-compliant. "
|
||||
"Filters are applied server-side."
|
||||
),
|
||||
"audit.col.time": "Time",
|
||||
"audit.col.actor": "User",
|
||||
"audit.col.event": "Event",
|
||||
"audit.col.level": "Level",
|
||||
"audit.col.message": "Message",
|
||||
"audit.col.result": "Result",
|
||||
"audit.col.target": "Target",
|
||||
"audit.col.duration": "Duration",
|
||||
"audit.search_placeholder": "Search user / event / target / message…",
|
||||
"audit.event_type_placeholder": "Event type (e.g. tool_call)",
|
||||
"audit.date_filter_title": "Filter by a day (YYYY-MM-DD)",
|
||||
"audit.level.info": "Info",
|
||||
"audit.level.warn": "Warning",
|
||||
"audit.no_entries": "No entries found.",
|
||||
"audit.zero_entries": "No entries",
|
||||
"audit.clear_filters": "Clear filters",
|
||||
"audit.page_label": "Page",
|
||||
"audit.page_size": "Page size",
|
||||
"audit.per_page": "{n} per page",
|
||||
"audit.range_of": "{from}–{to} of {total}",
|
||||
# Badges
|
||||
"badge.admin": "Admin",
|
||||
"badge.elevated": "Elevated",
|
||||
"badge.sensitive": "Sensitive",
|
||||
# Connect page
|
||||
"connect.connect_x": "Connect {name}",
|
||||
"connect.client.claude-code.desc": "CLI · Developer",
|
||||
"connect.client.vscode.desc": "Extension · Preview",
|
||||
"connect.client.cursor.desc": "JSON config",
|
||||
"connect.client.chatgpt.desc": "OAuth · Apps SDK",
|
||||
"connect.client.gemini.desc": "CLI · Token",
|
||||
"connect.client.custom.name": "Custom client",
|
||||
"connect.client.custom.desc": "Any MCP client",
|
||||
"connect.json.paste_into": "Paste this into your {name} MCP config",
|
||||
"connect.json.location_hint": "Settings → MCP Servers · File path differs by client",
|
||||
"connect.json.compatible": "Compatible:",
|
||||
"connect.json.custom_mcp": "Custom MCP",
|
||||
"connect.json.token_once": "Token shown only once",
|
||||
"connect.json.token_once_body": "Save it somewhere safe. You can rotate it anytime from API Keys.",
|
||||
"connect.claude.step1": "Use this connector URL",
|
||||
"connect.claude.step1_body": "Enter this URL in Claude.ai Connectors in your browser when it asks for the MCP endpoint.",
|
||||
"connect.claude.step2": "Confirm in Claude.ai",
|
||||
"connect.claude.step2_body_prefix": "Claude will show:",
|
||||
"connect.claude.step2_body_suffix": "Confirm to continue.",
|
||||
"connect.claude.step3": "You're connected",
|
||||
"connect.claude.step3_body": "You'll see the new client in your overview. Ask Claude to list your sites.",
|
||||
"connect.claude.prompt_text": "MCP Hub wants access to N tools",
|
||||
"connect.claude.open_desktop": "Open Claude Desktop",
|
||||
"connect.claude.link_lifetime": "Link valid for 10 minutes · One-time use",
|
||||
"connect.cli.step1": "Run this in your terminal",
|
||||
"connect.cli.step2": "Confirm",
|
||||
"connect.oauth.register_btn": "Register OAuth client",
|
||||
"connect.oauth.step1": "Register the OAuth app (once)",
|
||||
"connect.oauth.step1_body": "The hub creates an OAuth client and gives you a Redirect URL to paste into your AI tool manifest.",
|
||||
"connect.oauth.step2": "Sign in via the AI client",
|
||||
"connect.oauth.step2_body": 'Users see a "Sign in with MCP Hub" button. You confirm access once; the token refreshes automatically.',
|
||||
"connect.tier.admin_warning": (
|
||||
"Admin exposes destructive operations (delete posts, set options, install plugins, "
|
||||
"write env) on this site. Anyone with this site's token can act with full access. "
|
||||
"Unless the agent needs everything, choose a narrower scope."
|
||||
),
|
||||
"connect.tier.install_warning": (
|
||||
"Installer is an elevated scope — installs run code from the WordPress/theme "
|
||||
"repository on your site. Only use when the client needs this access."
|
||||
),
|
||||
"connect.tier.sensitive_warning": (
|
||||
"Sensitive reads is an elevated scope — includes backups and environment variables. "
|
||||
"Only use when the client needs this access."
|
||||
),
|
||||
"connect.toast.scope_updated": "Tool access updated to {scope}",
|
||||
"connect.toast.scope_failed": "Update failed: {error}",
|
||||
# OAuth clients
|
||||
"oauth.empty": "You have no OAuth clients yet.",
|
||||
"oauth.register_first": "Register your first client",
|
||||
"oauth.none": "— None —",
|
||||
"oauth.allowed_scope": "Allowed scope",
|
||||
"oauth.confirm_delete": 'Delete OAuth client "{name}"?\nUsers signed in via this client will be disconnected.',
|
||||
"oauth.admin_warning": (
|
||||
"Admin scope on a third-party OAuth client allows that app to act on all your "
|
||||
"sites on behalf of users. Only use for trusted applications you control."
|
||||
),
|
||||
"oauth.sensitive_warning": "Sensitive reads exposes backups and environment variables to the OAuth client.",
|
||||
"oauth.invalid_uris": "{n} URI is not a valid http(s) address:",
|
||||
"oauth.valid_uris": "{n} valid URI.",
|
||||
"oauth.toast_created": "Client created",
|
||||
"oauth.toast_deleted": "Client deleted",
|
||||
"oauth.toast_delete_failed": "Delete failed: {error}",
|
||||
"oauth.toast_create_failed": "Create failed: {error}",
|
||||
# Sites
|
||||
"sites.dialog_add_title": "Add site",
|
||||
"sites.dialog_add_submit": "Add site",
|
||||
"sites.dialog_edit_title": "Edit site",
|
||||
"sites.dialog_edit_submit": "Save changes",
|
||||
"sites.field_plugin_type": "Plugin",
|
||||
"sites.field_url": "URL",
|
||||
"sites.field_alias": "Alias",
|
||||
"sites.alias_placeholder": "short-site-id",
|
||||
"sites.alias_hint": "Short ID the AI sees as `site=…`. Lowercase letters, digits and hyphens only.",
|
||||
"sites.credentials": "Credentials",
|
||||
"sites.cred_unchanged": "Leave blank to keep the current value",
|
||||
"sites.show_advanced": "Show advanced fields",
|
||||
"sites.hide_advanced": "Hide advanced fields",
|
||||
"sites.manage_tools": "Manage tools",
|
||||
"sites.toast_created": "Site created",
|
||||
"sites.toast_updated": "Site updated",
|
||||
# Tier hints (used on the Connect page scope selector)
|
||||
"tier.read.hint": "List and inspect resources — read-only.",
|
||||
"tier.read_sensitive.hint": "Includes backups, environment variables, and other sensitive reads.",
|
||||
"tier.write.hint": "Create / update / delete resources and configuration.",
|
||||
"tier.editor.hint": "Pages, posts, content editing (wordpress_specialist F.19.5).",
|
||||
"tier.settings.hint": "Options, databases, identity, cron (wordpress_specialist F.19.6).",
|
||||
"tier.install.hint": "Install plugins/themes from the repository. Treat as elevated access.",
|
||||
"tier.deploy.hint": "Run deployments and lifecycle, without editing.",
|
||||
"tier.admin.hint": "Full system control including destructive operations.",
|
||||
"tier.custom.hint": "After selecting this, enable/disable tools manually.",
|
||||
# Pagination
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
# Status labels
|
||||
"status.revoked": "Revoked",
|
||||
# Audit / generic
|
||||
"event_type": "Event type",
|
||||
"level": "Level",
|
||||
|
||||
@@ -310,21 +310,6 @@ OPENPANEL_SITE1_CLIENT_SECRET=your-client-secret # Required
|
||||
OPENPANEL_SITE1_ALIAS=my-analytics # Recommended
|
||||
```
|
||||
|
||||
#### Appwrite
|
||||
```env
|
||||
APPWRITE_SITE1_URL=https://appwrite.example.com # Required
|
||||
APPWRITE_SITE1_API_KEY=your-api-key # Required
|
||||
APPWRITE_SITE1_PROJECT_ID=your-project-id # Required
|
||||
APPWRITE_SITE1_ALIAS=my-appwrite # Recommended
|
||||
```
|
||||
|
||||
#### Directus
|
||||
```env
|
||||
DIRECTUS_SITE1_URL=https://directus.example.com # Required
|
||||
DIRECTUS_SITE1_TOKEN=your-admin-token # Required
|
||||
DIRECTUS_SITE1_ALIAS=my-directus # Recommended
|
||||
```
|
||||
|
||||
> **Note:** Use `APP_PASSWORD` (WordPress Application Password), not `PASSWORD`.
|
||||
|
||||
### Configuration Tips
|
||||
@@ -534,8 +519,7 @@ Use the most specific endpoint for your use case to minimize token usage:
|
||||
| `/n8n/mcp` | n8n | 56 |
|
||||
| `/supabase/mcp` | Supabase | 70 |
|
||||
| `/openpanel/mcp` | OpenPanel | 42 |
|
||||
| `/appwrite/mcp` | Appwrite | 100 |
|
||||
| `/directus/mcp` | Directus | 100 |
|
||||
| `/coolify/mcp` | Coolify | ~65 |
|
||||
| `/system/mcp` | System Management | 24 |
|
||||
|
||||
**Plugin endpoint vs Project endpoint:**
|
||||
|
||||
@@ -135,7 +135,7 @@ def user_client(monkeypatch, user_row, patched_db):
|
||||
|
||||
class TestRenderedCsrfMeta:
|
||||
def test_keys_page_has_non_empty_csrf_meta(self, user_client):
|
||||
r = user_client.get("/dashboard/keys")
|
||||
r = user_client.get("/dashboard-legacy/keys")
|
||||
assert r.status_code == 200
|
||||
# The meta tag must be present *and* populated with a real token.
|
||||
assert '<meta name="csrf-token"' in r.text
|
||||
|
||||
@@ -65,7 +65,7 @@ def test_client(oauth_components):
|
||||
client_name="Test Client",
|
||||
redirect_uris=["http://localhost:3000/callback"],
|
||||
grant_types=["authorization_code", "refresh_token", "client_credentials"],
|
||||
allowed_scopes=["read", "write", "admin"],
|
||||
allowed_scopes=["read", "editor", "settings", "install", "write", "admin"],
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -4,8 +4,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from core.settings import get_cached_max_sites
|
||||
from core.site_api import (
|
||||
MAX_SITES_PER_USER,
|
||||
create_user_site,
|
||||
delete_user_site,
|
||||
get_credential_fields,
|
||||
@@ -225,7 +225,7 @@ class TestSiteCreation:
|
||||
@pytest.mark.unit
|
||||
async def test_create_site_max_limit(self, mock_db, mock_encryption):
|
||||
"""Exceeding MAX_SITES_PER_USER should raise ValueError."""
|
||||
mock_db.count_sites_by_user.return_value = MAX_SITES_PER_USER
|
||||
mock_db.count_sites_by_user.return_value = get_cached_max_sites()
|
||||
with pytest.raises(ValueError, match="Site limit reached"):
|
||||
await create_user_site(
|
||||
user_id="user-uuid-001",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useDashboardStats, useSession, useSites, useUserKeys } from "../lib/que
|
||||
import type { DashboardStats } from "../lib/types";
|
||||
import { useT } from "../lib/i18n";
|
||||
import { useUiStore } from "../lib/store";
|
||||
import type { Lang } from "../lib/store";
|
||||
import { fmtNumber, fmtInt, fmtDateTime, normalizeSiteStatus } from "../lib/format";
|
||||
|
||||
export function OverviewPage() {
|
||||
@@ -263,7 +264,7 @@ function AdminStatsPanel({
|
||||
t,
|
||||
}: {
|
||||
stats: DashboardStats | undefined;
|
||||
lang: string;
|
||||
lang: Lang;
|
||||
t: (key: string, fallback: string) => string;
|
||||
}) {
|
||||
if (!stats) return null;
|
||||
|
||||
Reference in New Issue
Block a user