release: v3.3.0 — platform hardening & admin unification (Track F.1–F.8)
Plugin visibility control, UI/UX fixes, unified admin panel, database-backed settings, and security hardening. Highlights: - ENABLED_PLUGINS env var for plugin visibility (F.1) - Admin designation via ADMIN_EMAILS (F.4a) - Master key scope control (F.4b) - Panel unification + settings from UI (F.4c) - exec() removal, shell injection fix, bcrypt migration (F.8) - 5 new test suites Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,7 +97,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Config Snippets Section -->
|
||||
<!-- Config Snippets + Connection Guide (unified) -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ t.config_snippets }}</h3>
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<select id="config-site" onchange="updateConfig()"
|
||||
class="w-full bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-4 py-2.5 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500">
|
||||
{% for site in sites %}
|
||||
<option value="{{ site.alias }}">{{ site.alias }} ({{ site.plugin_type }})</option>
|
||||
<option value="{{ site.alias }}" data-plugin="{{ site.plugin_type }}">{{ site.alias }} ({{ site.plugin_type }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
@@ -128,8 +128,8 @@
|
||||
<button onclick="copyConfig()" class="absolute top-2 {{ 'left-2' if lang == 'fa' else 'right-2' }} text-xs bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 px-3 py-1 rounded transition-colors" id="copy-config-btn">{{ t['copy'] }}</button>
|
||||
</div>
|
||||
|
||||
<!-- Transport type info -->
|
||||
<div class="mt-4 bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-500/30 rounded-lg p-4">
|
||||
<!-- Transport type info (hidden for web-based clients via JS) -->
|
||||
<div id="transport-note" class="mt-4 bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-500/30 rounded-lg p-4">
|
||||
<p class="text-sm text-blue-700 dark:text-blue-400">
|
||||
{% if lang == 'fa' %}
|
||||
<strong>نکته:</strong> تنظیمات بالا شامل نوع اتصال (<code>streamableHttp</code> برای Claude Desktop و <code>http</code> برای VS Code/Claude Code) میباشد. از <code>sse</code> استفاده نکنید — باعث خطای <code>400 Bad Request</code> میشود.
|
||||
@@ -139,76 +139,50 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- WordPress plugin requirement -->
|
||||
<div class="mt-3 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 rounded-lg p-4">
|
||||
<!-- WordPress SEO plugin note (shown/hidden dynamically by JS based on selected site) -->
|
||||
<div id="wp-seo-note" style="display:none" class="mt-3 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 rounded-lg p-4">
|
||||
<p class="text-sm text-amber-700 dark:text-amber-400">
|
||||
{% if lang == 'fa' %}
|
||||
<strong>وردپرس:</strong> برای استفاده از ابزارهای SEO، افزونه
|
||||
<a href="https://wordpress.org/plugins/airano-mcp-seo-bridge/" target="_blank" class="underline">Airano MCP SEO Bridge</a>
|
||||
را روی سایت وردپرسی خود نصب کنید. همچنین یک <strong>Application Password</strong> در وردپرس (Users → Profile) ایجاد و هنگام افزودن سایت وارد کنید.
|
||||
را روی سایت وردپرسی خود نصب کنید.
|
||||
{% else %}
|
||||
<strong>WordPress:</strong> For SEO tools, install the
|
||||
<a href="https://wordpress.org/plugins/airano-mcp-seo-bridge/" target="_blank" class="underline">Airano MCP SEO Bridge</a>
|
||||
plugin on your WordPress site. Also create an <strong>Application Password</strong> in WordPress (Users → Profile) and enter it when adding your site.
|
||||
plugin on your WordPress site.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Connection tip (shown only for web-based clients via JS) -->
|
||||
<div id="connection-tip" style="display:none" class="mt-3 bg-green-50 dark:bg-green-500/10 border border-green-200 dark:border-green-500/30 rounded-lg p-4">
|
||||
<p class="text-sm text-green-700 dark:text-green-400">
|
||||
{% if lang == 'fa' %}
|
||||
<strong>نکته:</strong> فقط آدرس URL کافی است. هنگام اتصال میتوانید با <strong>API Key</strong> یا <strong>GitHub/Google</strong> احراز هویت کنید.
|
||||
ساخت <a href="/dashboard/connect/oauth-clients{% if lang and lang != 'en' %}?lang={{ lang }}{% endif %}" class="underline">OAuth Client</a> اختیاری است.
|
||||
{% else %}
|
||||
<strong>Tip:</strong> You only need the URL above. When connecting, you can authenticate with an <strong>API Key</strong> or <strong>GitHub/Google</strong>.
|
||||
Creating an <a href="/dashboard/connect/oauth-clients{% if lang and lang != 'en' %}?lang={{ lang }}{% endif %}" class="underline">OAuth Client</a> is optional.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- API Key hint for desktop/CLI clients (hidden for web clients via JS) -->
|
||||
<div id="bearer-hint" class="mt-3 bg-gray-50 dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||
{% if lang == 'fa' %}
|
||||
<strong>API Key:</strong> از بخش API Keys بالا بسازید و مقدار آن را در تنظیمات وارد کنید:
|
||||
{% else %}
|
||||
<strong>API Key:</strong> Create one above and use it in your config:
|
||||
{% endif %}
|
||||
</p>
|
||||
<code class="block bg-gray-100 dark:bg-gray-900 px-3 py-1.5 rounded text-xs font-mono text-gray-800 dark:text-gray-200">"Authorization": "Bearer mhu_YOUR_API_KEY_HERE"</code>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm">{{ t.no_sites }}. <a href="/dashboard/sites/add{% if lang and lang != 'en' %}?lang={{ lang }}{% endif %}" class="text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300">{{ t.add_site }}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Claude.ai Connection Guide -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{% if lang == 'fa' %}اتصال به Claude.ai{% else %}Connect to Claude.ai{% endif %}
|
||||
</h3>
|
||||
<ol class="space-y-3 text-sm text-gray-700 dark:text-gray-300 {% if lang == 'fa' %}list-decimal list-inside{% else %}list-decimal list-inside{% endif %}" {% if lang == 'fa' %}dir="rtl"{% endif %}>
|
||||
<li>
|
||||
{% if lang == 'fa' %}
|
||||
در صفحه <strong>OAuth Clients</strong> یک کلاینت جدید بسازید
|
||||
{% else %}
|
||||
Create an OAuth Client on the <strong>OAuth Clients</strong> page
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{% if lang == 'fa' %}
|
||||
در Claude.ai → Settings → Connectors → Add → Custom
|
||||
{% else %}
|
||||
Go to Claude.ai → Settings → Connectors → Add → Custom
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{% if lang == 'fa' %}
|
||||
آدرس MCP Endpoint خود را وارد کنید:
|
||||
{% else %}
|
||||
Enter your MCP Endpoint URL:
|
||||
{% endif %}
|
||||
{% if sites %}
|
||||
<code class="block mt-1 bg-gray-100 dark:bg-gray-900 px-3 py-1.5 rounded text-xs font-mono text-gray-800 dark:text-gray-200">
|
||||
{{ public_url }}/u/{{ session.user_id }}/{{ sites[0].alias }}/mcp
|
||||
</code>
|
||||
{% else %}
|
||||
<code class="block mt-1 bg-gray-100 dark:bg-gray-900 px-3 py-1.5 rounded text-xs font-mono text-gray-800 dark:text-gray-200">
|
||||
{{ public_url }}/u/{{ session.user_id }}/YOUR-ALIAS/mcp
|
||||
</code>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{% if lang == 'fa' %}Client ID و Client Secret را از OAuth Clients وارد کنید{% else %}Enter Client ID and Client Secret from your OAuth Client{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{% if lang == 'fa' %}با GitHub یا Google وارد شوید{% else %}Log in with GitHub or Google when prompted{% endif %}
|
||||
</li>
|
||||
</ol>
|
||||
<a href="/dashboard/connect/oauth-clients{% if lang and lang != 'en' %}?lang={{ lang }}{% endif %}"
|
||||
class="mt-4 inline-flex items-center gap-2 px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg text-sm font-medium transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
||||
</svg>
|
||||
{% if lang == 'fa' %}مدیریت OAuth Clients{% else %}Manage OAuth Clients{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -254,11 +228,35 @@ async function deleteKey(keyId) {
|
||||
}
|
||||
}
|
||||
|
||||
const WEB_CLIENTS = ['claude_connectors', 'chatgpt'];
|
||||
const WP_TYPES = ['wordpress', 'woocommerce'];
|
||||
|
||||
async function updateConfig() {
|
||||
const alias = document.getElementById('config-site')?.value;
|
||||
const siteSelect = document.getElementById('config-site');
|
||||
const alias = siteSelect?.value;
|
||||
const client = document.getElementById('config-client')?.value;
|
||||
if (!alias || !client) return;
|
||||
|
||||
const isWebClient = WEB_CLIENTS.includes(client);
|
||||
|
||||
// Show/hide transport note
|
||||
const transportNote = document.getElementById('transport-note');
|
||||
if (transportNote) transportNote.style.display = isWebClient ? 'none' : '';
|
||||
|
||||
// Show/hide connection tip (only for web clients)
|
||||
const connTip = document.getElementById('connection-tip');
|
||||
if (connTip) connTip.style.display = isWebClient ? '' : 'none';
|
||||
|
||||
// Show/hide bearer hint (only for desktop/CLI clients)
|
||||
const bearerHint = document.getElementById('bearer-hint');
|
||||
if (bearerHint) bearerHint.style.display = isWebClient ? 'none' : '';
|
||||
|
||||
// Show/hide WordPress SEO note based on selected site's plugin type
|
||||
const selectedOption = siteSelect.options[siteSelect.selectedIndex];
|
||||
const pluginType = selectedOption?.dataset?.plugin || '';
|
||||
const wpNote = document.getElementById('wp-seo-note');
|
||||
if (wpNote) wpNote.style.display = WP_TYPES.includes(pluginType) ? '' : 'none';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/config/' + alias + '?client=' + client);
|
||||
const data = await resp.json();
|
||||
|
||||
Reference in New Issue
Block a user