implement wp testing
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -136,8 +136,9 @@
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost active" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
81
chat/public/admin-external-testing.html
Normal file
81
chat/public/admin-external-testing.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Admin Panel - External Testing</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body data-page="external-testing">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">External WP Testing</div>
|
||||
<div class="crumb">Run a CLI-only self check without AI calls.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>External testing self-check</h3>
|
||||
<div class="pill">WP-CLI</div>
|
||||
</header>
|
||||
<p class="muted" style="margin-top:0;">Uploads a temporary plugin, activates it, verifies the activation flag, and cleans up. No AI API calls are made.</p>
|
||||
<div class="admin-actions">
|
||||
<button id="external-testing-run" class="primary">Run self-test</button>
|
||||
<div class="status-line" id="external-testing-status"></div>
|
||||
</div>
|
||||
<div id="external-testing-output" class="admin-list" style="margin-top: 12px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top: 16px;">
|
||||
<header>
|
||||
<h3>Current external testing configuration</h3>
|
||||
<div class="pill">Config</div>
|
||||
</header>
|
||||
<div id="external-testing-config" class="admin-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,6 +31,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -224,7 +224,8 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost active" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -184,6 +184,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
opencodeBackupForm: document.getElementById('opencode-backup-form'),
|
||||
opencodeBackup: document.getElementById('opencode-backup'),
|
||||
opencodeBackupStatus: document.getElementById('opencode-backup-status'),
|
||||
externalTestingRun: document.getElementById('external-testing-run'),
|
||||
externalTestingStatus: document.getElementById('external-testing-status'),
|
||||
externalTestingOutput: document.getElementById('external-testing-output'),
|
||||
externalTestingConfig: document.getElementById('external-testing-config'),
|
||||
};
|
||||
console.log('Element check - opencodeBackupForm:', el.opencodeBackupForm);
|
||||
console.log('Element check - opencodeBackup:', el.opencodeBackup);
|
||||
@@ -178,6 +182,128 @@
|
||||
el.opencodeBackupStatus.style.color = isError ? 'var(--danger)' : 'inherit';
|
||||
}
|
||||
|
||||
function setExternalTestingStatus(msg, isError = false) {
|
||||
if (!el.externalTestingStatus) return;
|
||||
el.externalTestingStatus.textContent = msg || '';
|
||||
el.externalTestingStatus.style.color = isError ? 'var(--danger)' : 'inherit';
|
||||
}
|
||||
|
||||
function renderExternalTestingConfig(config) {
|
||||
if (!el.externalTestingConfig) return;
|
||||
el.externalTestingConfig.innerHTML = '';
|
||||
if (!config) return;
|
||||
const rows = [
|
||||
['WP host', config.wpHost || '—'],
|
||||
['WP path', config.wpPath || '—'],
|
||||
['Base URL', config.wpBaseUrl || '—'],
|
||||
['Multisite enabled', config.enableMultisite ? 'Yes' : 'No'],
|
||||
['Subsite mode', config.subsiteMode || '—'],
|
||||
['Subsite domain', config.subsiteDomain || '—'],
|
||||
['Max concurrent tests', String(config.maxConcurrentTests ?? '—')],
|
||||
['Auto cleanup', config.autoCleanup ? 'Yes' : 'No'],
|
||||
['Cleanup delay (ms)', String(config.cleanupDelayMs ?? '—')],
|
||||
['SSH key configured', config.sshKeyConfigured ? 'Yes' : 'No'],
|
||||
];
|
||||
|
||||
rows.forEach(([label, value]) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'admin-row';
|
||||
const labelWrap = document.createElement('div');
|
||||
labelWrap.style.minWidth = '180px';
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = label;
|
||||
labelWrap.appendChild(strong);
|
||||
const valueWrap = document.createElement('div');
|
||||
valueWrap.textContent = value;
|
||||
row.appendChild(labelWrap);
|
||||
row.appendChild(valueWrap);
|
||||
el.externalTestingConfig.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function renderExternalTestingOutput(result) {
|
||||
if (!el.externalTestingOutput) return;
|
||||
el.externalTestingOutput.innerHTML = '';
|
||||
if (!result) return;
|
||||
|
||||
const summary = document.createElement('div');
|
||||
summary.className = 'admin-row';
|
||||
const summaryLabel = document.createElement('div');
|
||||
summaryLabel.style.minWidth = '180px';
|
||||
const summaryStrong = document.createElement('strong');
|
||||
summaryStrong.textContent = 'Overall result';
|
||||
summaryLabel.appendChild(summaryStrong);
|
||||
const summaryValue = document.createElement('div');
|
||||
summaryValue.textContent = result.ok ? 'Passed' : 'Failed';
|
||||
summary.appendChild(summaryLabel);
|
||||
summary.appendChild(summaryValue);
|
||||
el.externalTestingOutput.appendChild(summary);
|
||||
|
||||
const detailRows = [
|
||||
['Subsite URL', result.subsite_url || '—'],
|
||||
['Duration', typeof result.duration === 'number' ? `${(result.duration / 1000).toFixed(1)}s` : '—'],
|
||||
];
|
||||
detailRows.forEach(([label, value]) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'admin-row';
|
||||
const labelWrap = document.createElement('div');
|
||||
labelWrap.style.minWidth = '180px';
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = label;
|
||||
labelWrap.appendChild(strong);
|
||||
const valueWrap = document.createElement('div');
|
||||
valueWrap.textContent = value;
|
||||
row.appendChild(labelWrap);
|
||||
row.appendChild(valueWrap);
|
||||
el.externalTestingOutput.appendChild(row);
|
||||
});
|
||||
|
||||
const scenarioResults = result?.test_results?.cli_tests?.results || [];
|
||||
if (scenarioResults.length) {
|
||||
scenarioResults.forEach((scenario) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'admin-row';
|
||||
const labelWrap = document.createElement('div');
|
||||
labelWrap.style.minWidth = '180px';
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = scenario.name || 'Scenario';
|
||||
labelWrap.appendChild(strong);
|
||||
const valueWrap = document.createElement('div');
|
||||
valueWrap.textContent = scenario.status === 'passed' ? 'Passed' : 'Failed';
|
||||
if (scenario.status !== 'passed') {
|
||||
valueWrap.style.color = 'var(--danger)';
|
||||
}
|
||||
row.appendChild(labelWrap);
|
||||
row.appendChild(valueWrap);
|
||||
el.externalTestingOutput.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
const errors = Array.isArray(result.errors) ? result.errors : [];
|
||||
if (errors.length) {
|
||||
errors.forEach((err) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'admin-row';
|
||||
const labelWrap = document.createElement('div');
|
||||
labelWrap.style.minWidth = '180px';
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = 'Error';
|
||||
labelWrap.appendChild(strong);
|
||||
const valueWrap = document.createElement('div');
|
||||
valueWrap.textContent = err;
|
||||
valueWrap.style.color = 'var(--danger)';
|
||||
row.appendChild(labelWrap);
|
||||
row.appendChild(valueWrap);
|
||||
el.externalTestingOutput.appendChild(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadExternalTestingStatus() {
|
||||
const data = await api('/api/admin/external-testing-status');
|
||||
renderExternalTestingConfig(data.config || {});
|
||||
}
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const res = await fetch(path, {
|
||||
credentials: 'same-origin',
|
||||
@@ -1844,6 +1970,7 @@
|
||||
() => (el.planTokensTable ? loadPlanTokens() : null),
|
||||
() => ((el.tokenRateUsd || el.tokenRateGbp || el.tokenRateEur) ? loadTokenRates() : null),
|
||||
() => ((el.providerUsage || el.providerLimitForm) ? loadProviderLimits() : null),
|
||||
() => (el.externalTestingConfig ? loadExternalTestingStatus() : null),
|
||||
];
|
||||
await Promise.all(loaders.map((fn) => fn()).filter(Boolean));
|
||||
// Always try to load provider limits if not already loaded (needed for backup dropdown)
|
||||
@@ -2139,6 +2266,22 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (el.externalTestingRun) {
|
||||
el.externalTestingRun.addEventListener('click', async () => {
|
||||
el.externalTestingRun.disabled = true;
|
||||
setExternalTestingStatus('Running self-test...');
|
||||
try {
|
||||
const data = await api('/api/admin/external-testing-self-test', { method: 'POST' });
|
||||
renderExternalTestingOutput(data.result || null);
|
||||
setExternalTestingStatus(data.result && data.result.ok ? 'Self-test passed.' : 'Self-test failed.', !data.result || !data.result.ok);
|
||||
} catch (err) {
|
||||
setExternalTestingStatus(err.message || 'Self-test failed.', true);
|
||||
} finally {
|
||||
el.externalTestingRun.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (el.logout) {
|
||||
el.logout.addEventListener('click', async () => {
|
||||
await api('/api/admin/logout', { method: 'POST' }).catch(() => { });
|
||||
|
||||
@@ -1118,13 +1118,21 @@
|
||||
</div>
|
||||
|
||||
<button class="primary action-link" id="export-zip-btn" title="Download as ZIP">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:8px;">
|
||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"></path>
|
||||
<polyline points="7,10 12,15 17,10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Download ZIP
|
||||
</button>
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<label style="display:inline-flex; align-items:center; gap:8px; font-weight:600; font-size:13px;">
|
||||
<input type="checkbox" id="external-testing-toggle" style="width:18px; height:18px;" />
|
||||
<span id="external-testing-label" style="font-weight:600; font-size:13px;">External WP Tests</span>
|
||||
</label>
|
||||
<button id="external-testing-info" class="action-link" title="Run the plugin through CLI tests on an external WP site" style="padding:6px 8px; border-radius:8px;">i</button>
|
||||
<div id="external-testing-usage" style="font-size:12px; color:var(--muted); min-width:140px; text-align:right;">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1437,6 +1445,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Confirm Build Modal -->
|
||||
|
||||
<!-- External Testing Limit Modal -->
|
||||
<div id="external-testing-limit-modal" class="modal" style="display:none;">
|
||||
<div class="modal-card" style="max-width:520px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<strong style="color:var(--ink); font-size:20px;">External Testing Limit Reached</strong>
|
||||
<button id="external-testing-limit-close" style="background:none; border:none; font-size:22px;">×</button>
|
||||
</div>
|
||||
<p style="color:var(--muted); margin-top:12px;">Your current plan limits the number of external WP CLI tests you can run per month. To continue using external testing, upgrade your plan or purchase additional test credits.</p>
|
||||
<div class="admin-actions" style="margin-top:18px; justify-content:flex-end; gap:8px;">
|
||||
<button id="external-testing-limit-upgrade" class="primary">Upgrade Plan</button>
|
||||
<button id="external-testing-limit-cancel" class="action-link">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirm-build-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
|
||||
|
||||
@@ -32,7 +32,8 @@ const builderState = savedState || {
|
||||
lastUserRequest: '',
|
||||
lastPlanText: '',
|
||||
pluginPrompt: '',
|
||||
subsequentPrompt: ''
|
||||
subsequentPrompt: '',
|
||||
externalTestingEnabled: false
|
||||
};
|
||||
|
||||
// Auto-save builderState changes to localStorage
|
||||
@@ -117,6 +118,21 @@ async function proceedWithBuild(planContent) {
|
||||
|
||||
async function executeBuild(planContent) {
|
||||
console.log('executeBuild called with planContent:', planContent ? planContent.substring(0, 100) + '...' : 'null');
|
||||
// Ensure external testing is still allowed if enabled
|
||||
if (builderState.externalTestingEnabled) {
|
||||
await loadUsageSummary();
|
||||
const summary = state.usageSummary?.externalTesting || null;
|
||||
const limit = summary ? summary.limit : null;
|
||||
const used = summary ? summary.used : 0;
|
||||
if (Number.isFinite(limit) && used >= limit) {
|
||||
// show modal and abort
|
||||
const modal = document.getElementById('external-testing-limit-modal');
|
||||
if (modal) modal.style.display = 'flex';
|
||||
setStatus('External testing limit reached. Disable external testing or upgrade your plan.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
builderState.mode = 'build';
|
||||
builderState.planApproved = true;
|
||||
updateBuildModeUI();
|
||||
@@ -160,7 +176,8 @@ async function executeBuild(planContent) {
|
||||
model: selectedModel,
|
||||
cli: 'opencode',
|
||||
isProceedWithBuild: true,
|
||||
planContent: planContent
|
||||
planContent: planContent,
|
||||
externalTestingEnabled: !!builderState.externalTestingEnabled
|
||||
};
|
||||
// Preserve opencodeSessionId for session continuity
|
||||
if (session && session.opencodeSessionId) {
|
||||
@@ -188,7 +205,16 @@ async function executeBuild(planContent) {
|
||||
console.log('Build process initiated successfully');
|
||||
} catch (e) {
|
||||
console.error('Failed to start build:', e);
|
||||
alert('Failed to start build: ' + (e.message || 'Unknown error'));
|
||||
const msg = (e && e.message) ? e.message : 'Unknown error';
|
||||
if (msg && msg.toLowerCase().includes('external wp cli testing')) {
|
||||
// Show upgrade modal
|
||||
const modal = document.getElementById('external-testing-limit-modal');
|
||||
if (modal) modal.style.display = 'flex';
|
||||
setStatus(msg, true);
|
||||
} else {
|
||||
alert('Failed to start build: ' + msg);
|
||||
}
|
||||
|
||||
builderState.mode = 'plan'; // Revert
|
||||
updateBuildModeUI();
|
||||
hideLoadingIndicator();
|
||||
@@ -603,6 +629,9 @@ async function loadUsageSummary() {
|
||||
usageMeterTrack: !!el.usageMeterTrack
|
||||
});
|
||||
updateUsageProgressBar(state.usageSummary);
|
||||
if (typeof window.updateExternalTestingUI === 'function') {
|
||||
try { updateExternalTestingUI(); } catch (e) { console.warn('external testing UI update failed', e); }
|
||||
}
|
||||
if (typeof window.checkTokenLimitAndShowModal === 'function') {
|
||||
setTimeout(() => window.checkTokenLimitAndShowModal(), 500);
|
||||
}
|
||||
@@ -618,6 +647,92 @@ async function loadUsageSummary() {
|
||||
// Expose for builder.html
|
||||
window.loadUsageSummary = loadUsageSummary;
|
||||
|
||||
// --- External testing UI helpers ---
|
||||
function updateExternalTestingUI() {
|
||||
const elToggle = document.getElementById('external-testing-toggle');
|
||||
const elUsage = document.getElementById('external-testing-usage');
|
||||
const infoBtn = document.getElementById('external-testing-info');
|
||||
if (!elToggle || !elUsage) return;
|
||||
|
||||
const et = state.usageSummary?.externalTesting || null;
|
||||
if (!et) {
|
||||
elUsage.textContent = 'Not configured';
|
||||
elToggle.disabled = true;
|
||||
elToggle.checked = false;
|
||||
builderState.externalTestingEnabled = false;
|
||||
saveBuilderState(builderState);
|
||||
return;
|
||||
}
|
||||
|
||||
const used = Number(et.used || 0);
|
||||
const limit = Number.isFinite(Number(et.limit)) ? et.limit : 'unlimited';
|
||||
elUsage.textContent = Number.isFinite(Number(et.limit)) ? `${used} / ${limit}` : `${used} / ∞`;
|
||||
|
||||
if (typeof builderState.externalTestingEnabled === 'boolean') {
|
||||
elToggle.checked = !!builderState.externalTestingEnabled;
|
||||
} else {
|
||||
elToggle.checked = false;
|
||||
builderState.externalTestingEnabled = false;
|
||||
saveBuilderState(builderState);
|
||||
}
|
||||
|
||||
if (infoBtn) {
|
||||
infoBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
alert('External WP tests run a series of WP-CLI checks on an external WordPress site. Tests are counted against your monthly allowance.');
|
||||
};
|
||||
}
|
||||
|
||||
elToggle.addEventListener('change', async (e) => {
|
||||
const wantOn = e.target.checked === true;
|
||||
if (!wantOn) {
|
||||
builderState.externalTestingEnabled = false;
|
||||
saveBuilderState(builderState);
|
||||
elToggle.checked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-check usage before enabling
|
||||
await loadUsageSummary();
|
||||
const summary = state.usageSummary?.externalTesting || null;
|
||||
const limit = summary ? summary.limit : null;
|
||||
const used = summary ? summary.used : 0;
|
||||
if (Number.isFinite(limit) && used >= limit) {
|
||||
// show modal suggesting upgrade
|
||||
const modal = document.getElementById('external-testing-limit-modal');
|
||||
if (modal) modal.style.display = 'flex';
|
||||
elToggle.checked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
builderState.externalTestingEnabled = true;
|
||||
saveBuilderState(builderState);
|
||||
elToggle.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
(function wireExternalTestingModal() {
|
||||
const modal = document.getElementById('external-testing-limit-modal');
|
||||
if (!modal) return;
|
||||
const closeBtn = document.getElementById('external-testing-limit-close');
|
||||
const cancelBtn = document.getElementById('external-testing-limit-cancel');
|
||||
const upgradeBtn = document.getElementById('external-testing-limit-upgrade');
|
||||
const upgradeHeaderBtn = document.getElementById('upgrade-header-btn');
|
||||
const closeModal = () => { modal.style.display = 'none'; };
|
||||
if (closeBtn) closeBtn.addEventListener('click', closeModal);
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', closeModal);
|
||||
if (upgradeBtn) {
|
||||
upgradeBtn.addEventListener('click', () => {
|
||||
closeModal();
|
||||
if (upgradeHeaderBtn) upgradeHeaderBtn.click(); else window.location.href = '/topup';
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Expose to global scope
|
||||
window.updateExternalTestingUI = updateExternalTestingUI;
|
||||
|
||||
|
||||
function checkTokenLimitAndShowModal() {
|
||||
const remaining = state.usageSummary?.remaining || 0;
|
||||
if (remaining <= 5000) {
|
||||
@@ -1273,6 +1388,8 @@ async function hydrateUserIdFromServerSession() {
|
||||
// Load usage summary on page load
|
||||
loadUsageSummary().catch(err => {
|
||||
console.warn('[USAGE] Initial loadUsageSummary failed:', err.message);
|
||||
}).then(() => {
|
||||
try { if (typeof window.updateExternalTestingUI === 'function') window.updateExternalTestingUI(); } catch (e) { console.warn('updateExternalTestingUI failed on init', e); }
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -2065,7 +2182,9 @@ window.renderMessages = renderMessages;
|
||||
function renderContentWithTodos(text) {
|
||||
const wrapper = document.createElement('div');
|
||||
if (!text) return document.createTextNode('');
|
||||
const processedText = String(text).replace(/([.:])\s+(?=[A-Z])/g, '$1\n\n');
|
||||
const processedText = String(text)
|
||||
.replace(/:\s*(?=[A-Z])/g, ':\n\n')
|
||||
.replace(/([.])\s+(?=[A-Z])/g, '$1\n\n');
|
||||
const lines = processedText.split(/\r?\n/);
|
||||
let currentList = null;
|
||||
let inCodeBlock = false;
|
||||
|
||||
Reference in New Issue
Block a user