fix: SUG-6 API key modal, SUG-4 project activity filter, WooCommerce health auth

- SUG-6: Replace auto-closing inline banner with persistent modal on Connect
  page — key stays visible until user clicks "Done"
- SUG-4: Filter health_metric_recorded/health_check from project detail activity
- WooCommerce: Accept consumer_key/consumer_secret credentials (preferred) with
  username/app_password fallback; fix health check to use WC endpoint and creds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-24 07:16:05 +03:30
parent 983d93c2a7
commit 1736779d69
5 changed files with 86 additions and 28 deletions

View File

@@ -918,12 +918,15 @@ async def get_project_detail(project_id: str) -> dict | None:
# Get recent activity for this project
audit_logger = get_audit_logger()
recent_entries = audit_logger.get_recent_entries(limit=20)
recent_entries = audit_logger.get_recent_entries(limit=60)
project_activity = [
e
for e in recent_entries
if e.get("metadata", {}).get("project_id") == project_id
or e.get("metadata", {}).get("site") == site_id
if (
e.get("metadata", {}).get("project_id") == project_id
or e.get("metadata", {}).get("site") == site_id
)
and e.get("event_type", "") not in ("health_metric_recorded", "health_check")
][:5]
# Get cached health status

View File

@@ -563,13 +563,26 @@ class HealthMonitor:
try:
import aiohttp
auth = aiohttp.BasicAuth(
config.username or "",
config.app_password or "",
)
if plugin_type == "woocommerce":
# WooCommerce uses consumer_key/consumer_secret
ck = getattr(config, "consumer_key", None) or ""
cs = getattr(config, "consumer_secret", None) or ""
# consumer_key/consumer_secret may be in model_extra
if not ck and hasattr(config, "model_extra"):
ck = (config.model_extra or {}).get("consumer_key", "")
cs = (config.model_extra or {}).get("consumer_secret", "")
auth = aiohttp.BasicAuth(ck, cs)
auth_check_url = f"{config.url}/wp-json/wc/v3/system_status"
else:
auth = aiohttp.BasicAuth(
config.username or "",
config.app_password or "",
)
auth_check_url = f"{config.url}/wp-json/wp/v2/users/me"
async with aiohttp.ClientSession(auth=auth) as session:
async with session.get(
f"{config.url}/wp-json/wp/v2/users/me",
auth_check_url,
timeout=aiohttp.ClientTimeout(total=10),
ssl=False,
) as resp:

View File

@@ -28,14 +28,35 @@
</div>
{% endif %}
<!-- New key display (JS-created) -->
<div id="new-key-display" class="hidden bg-yellow-50 dark:bg-yellow-500/20 border border-yellow-200 dark:border-yellow-500/50 text-yellow-800 dark:text-yellow-300 px-4 py-3 rounded-lg mb-4">
<p class="font-medium mb-1">{{ t.your_api_key }}</p>
<div class="flex items-center gap-2">
<code id="new-key-value" class="bg-gray-100 dark:bg-gray-900 px-3 py-1 rounded text-sm flex-1 overflow-x-auto text-gray-800 dark:text-gray-200"></code>
<button onclick="copyNewKey()" class="text-sm text-yellow-700 dark:text-yellow-300 hover:text-yellow-600 dark:hover:text-yellow-200">{{ t['copy'] }}</button>
<!-- New Key Modal (stays open until user clicks Done) -->
<div id="new-key-display" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 w-full max-w-lg mx-4">
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<svg class="w-5 h-5 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
{{ t.your_api_key }}
</h3>
</div>
<div class="p-6 space-y-4">
<div class="bg-yellow-50 dark:bg-yellow-500/10 border border-yellow-200 dark:border-yellow-500/30 rounded-lg p-4">
<p class="text-sm text-yellow-700 dark:text-yellow-400">{{ t.key_shown_once }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">API Key</label>
<div class="flex items-center gap-2">
<code id="new-key-value" class="flex-1 bg-gray-100 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg px-4 py-2 text-sm font-mono text-gray-800 dark:text-gray-200 overflow-x-auto"></code>
<button onclick="copyNewKey()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white text-sm transition-colors">{{ t['copy'] }}</button>
</div>
</div>
</div>
<div class="p-6 border-t border-gray-200 dark:border-gray-700 flex justify-end">
<button onclick="closeNewKeyModal()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white text-sm transition-colors">
{% if lang == 'fa' %}تأیید{% else %}Done{% endif %}
</button>
</div>
</div>
<p class="text-xs text-yellow-600 dark:text-yellow-400 mt-1">{{ t.key_shown_once }}</p>
</div>
{% if api_keys %}
@@ -132,8 +153,8 @@ async function createKey() {
if (resp.ok && data.key) {
document.getElementById('new-key-value').textContent = data.key.key;
document.getElementById('new-key-display').classList.remove('hidden');
// Reload to show in table
setTimeout(() => location.reload(), 500);
document.getElementById('new-key-display').classList.add('flex');
// Modal stays open — user clicks "Done" to dismiss
} else {
alert(data.error || 'Failed to create key');
}
@@ -180,6 +201,12 @@ function copyNewKey() {
navigator.clipboard.writeText(key);
}
function closeNewKeyModal() {
document.getElementById('new-key-display').classList.add('hidden');
document.getElementById('new-key-display').classList.remove('flex');
location.reload();
}
function copyConfig() {
const text = document.getElementById('config-output').textContent;
navigator.clipboard.writeText(text);