implement admin db testing
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -94,3 +95,4 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -245,6 +245,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -427,3 +428,4 @@
|
|||||||
<script src="/js/blog-admin.js"></script>
|
<script src="/js/blog-admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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 active" href="/admin/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -307,3 +308,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -80,3 +81,4 @@
|
|||||||
<script src="/admin.js"></script>
|
<script src="/admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost active" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost active" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -535,3 +536,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -145,3 +146,4 @@
|
|||||||
<script src="/admin.js"></script>
|
<script src="/admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
|
|||||||
@@ -224,9 +224,10 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost active" href="/admin/resources">Resources</a>
|
<a class="ghost active" href="/admin/resources">Resources</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</a>
|
<a class="ghost" href="/admin/system-tests">System Tests</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/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||||
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
<a class="ghost" href="/admin/login">Login</a>
|
<a class="ghost" href="/admin/login">Login</a>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -962,3 +963,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
79
chat/public/admin-system-tests.html
Normal file
79
chat/public/admin-system-tests.html
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Admin Panel - System Tests</title>
|
||||||
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
|
|
||||||
|
<!-- PostHog Analytics -->
|
||||||
|
<script src="/posthog.js"></script>
|
||||||
|
</head>
|
||||||
|
<body data-page="system-tests">
|
||||||
|
<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/system-tests">System Tests</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/feature-requests">Feature Requests</a>
|
||||||
|
<a class="ghost" href="/admin/blogs">Blog Management</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;">System Tests</div>
|
||||||
|
<div class="crumb">Run end-to-end checks for database, accounts, and payments.</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>Full system self-test</h3>
|
||||||
|
<div class="pill">Diagnostics</div>
|
||||||
|
</header>
|
||||||
|
<p class="muted" style="margin-top:0;">
|
||||||
|
Runs database/encryption checks, creates a temporary test account, and verifies payment configuration.
|
||||||
|
If Dodo is configured, it will create a test checkout session (no charge is completed).
|
||||||
|
</p>
|
||||||
|
<div class="admin-actions">
|
||||||
|
<button id="system-tests-run" class="primary">Run full self-test</button>
|
||||||
|
<div class="status-line" id="system-tests-status"></div>
|
||||||
|
</div>
|
||||||
|
<div id="system-tests-output" class="admin-list" style="margin-top: 12px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<script src="/admin.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -184,6 +184,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -990,3 +991,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -93,3 +94,4 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||||
<a class="ghost" href="/admin/resources">Resources</a>
|
<a class="ghost" href="/admin/resources">Resources</a>
|
||||||
|
<a class="ghost" href="/admin/system-tests">System Tests</a>
|
||||||
<a class="ghost" href="/admin/external-testing">External Testing</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/contact-messages">Contact Messages</a>
|
||||||
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
<a class="ghost" href="/admin/feature-requests">Feature Requests</a>
|
||||||
@@ -322,3 +323,4 @@
|
|||||||
<script src="/admin.js"></script>
|
<script src="/admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,9 @@
|
|||||||
ollamaTestRun: document.getElementById('ollama-test-run'),
|
ollamaTestRun: document.getElementById('ollama-test-run'),
|
||||||
ollamaTestStatus: document.getElementById('ollama-test-status'),
|
ollamaTestStatus: document.getElementById('ollama-test-status'),
|
||||||
ollamaTestOutput: document.getElementById('ollama-test-output'),
|
ollamaTestOutput: document.getElementById('ollama-test-output'),
|
||||||
|
systemTestsRun: document.getElementById('system-tests-run'),
|
||||||
|
systemTestsStatus: document.getElementById('system-tests-status'),
|
||||||
|
systemTestsOutput: document.getElementById('system-tests-output'),
|
||||||
};
|
};
|
||||||
|
|
||||||
function ensureAvailableDatalist() {
|
function ensureAvailableDatalist() {
|
||||||
@@ -221,6 +224,12 @@
|
|||||||
el.externalTestingStatus.style.color = isError ? 'var(--danger)' : 'inherit';
|
el.externalTestingStatus.style.color = isError ? 'var(--danger)' : 'inherit';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSystemTestsStatus(msg, isError = false) {
|
||||||
|
if (!el.systemTestsStatus) return;
|
||||||
|
el.systemTestsStatus.textContent = msg || '';
|
||||||
|
el.systemTestsStatus.style.color = isError ? 'var(--danger)' : 'inherit';
|
||||||
|
}
|
||||||
|
|
||||||
function renderExternalTestingConfig(config) {
|
function renderExternalTestingConfig(config) {
|
||||||
if (!el.externalTestingConfig) return;
|
if (!el.externalTestingConfig) return;
|
||||||
el.externalTestingConfig.innerHTML = '';
|
el.externalTestingConfig.innerHTML = '';
|
||||||
@@ -337,6 +346,66 @@
|
|||||||
renderExternalTestingConfig(data.config || {});
|
renderExternalTestingConfig(data.config || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSystemTestsOutput(data) {
|
||||||
|
if (!el.systemTestsOutput) return;
|
||||||
|
el.systemTestsOutput.innerHTML = '';
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const summary = data.summary || { total: 0, passed: 0, failed: 0, skipped: 0 };
|
||||||
|
const summaryRow = document.createElement('div');
|
||||||
|
summaryRow.className = 'admin-row';
|
||||||
|
const summaryLabel = document.createElement('div');
|
||||||
|
summaryLabel.style.minWidth = '180px';
|
||||||
|
summaryLabel.style.color = 'var(--muted)';
|
||||||
|
summaryLabel.textContent = 'Summary';
|
||||||
|
const summaryValue = document.createElement('div');
|
||||||
|
summaryValue.textContent = `${summary.passed} passed, ${summary.failed} failed, ${summary.skipped} skipped (${data.durationMs || 0}ms)`;
|
||||||
|
summaryRow.appendChild(summaryLabel);
|
||||||
|
summaryRow.appendChild(summaryValue);
|
||||||
|
el.systemTestsOutput.appendChild(summaryRow);
|
||||||
|
|
||||||
|
const results = Array.isArray(data.results) ? data.results : [];
|
||||||
|
results.forEach((result) => {
|
||||||
|
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 = result.name || 'Check';
|
||||||
|
labelWrap.appendChild(strong);
|
||||||
|
|
||||||
|
const valueWrap = document.createElement('div');
|
||||||
|
const status = document.createElement('span');
|
||||||
|
const statusText = (result.status || 'unknown').toUpperCase();
|
||||||
|
status.textContent = statusText;
|
||||||
|
if (result.status === 'passed') status.style.color = 'var(--shopify-green)';
|
||||||
|
else if (result.status === 'failed') status.style.color = 'var(--danger)';
|
||||||
|
else status.style.color = 'var(--muted)';
|
||||||
|
|
||||||
|
valueWrap.appendChild(status);
|
||||||
|
if (result.details) {
|
||||||
|
const sep = document.createElement('span');
|
||||||
|
sep.textContent = ' - ';
|
||||||
|
valueWrap.appendChild(sep);
|
||||||
|
const details = document.createElement('span');
|
||||||
|
details.textContent = result.details;
|
||||||
|
valueWrap.appendChild(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof result.durationMs === 'number') {
|
||||||
|
const timing = document.createElement('span');
|
||||||
|
timing.textContent = ` (${result.durationMs}ms)`;
|
||||||
|
timing.style.color = 'var(--muted)';
|
||||||
|
timing.style.marginLeft = '4px';
|
||||||
|
valueWrap.appendChild(timing);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.appendChild(labelWrap);
|
||||||
|
row.appendChild(valueWrap);
|
||||||
|
el.systemTestsOutput.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// --- Ollama Test UI ---
|
// --- Ollama Test UI ---
|
||||||
function setOllamaTestStatus(msg, isError = false) {
|
function setOllamaTestStatus(msg, isError = false) {
|
||||||
if (!el.ollamaTestStatus) return;
|
if (!el.ollamaTestStatus) return;
|
||||||
@@ -2822,6 +2891,23 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (el.systemTestsRun) {
|
||||||
|
el.systemTestsRun.addEventListener('click', async () => {
|
||||||
|
el.systemTestsRun.disabled = true;
|
||||||
|
setSystemTestsStatus('Running system tests...');
|
||||||
|
if (el.systemTestsOutput) el.systemTestsOutput.innerHTML = '';
|
||||||
|
try {
|
||||||
|
const data = await api('/api/admin/system-tests', { method: 'POST' });
|
||||||
|
renderSystemTestsOutput(data || null);
|
||||||
|
setSystemTestsStatus(data && data.ok ? 'System tests passed.' : 'System tests completed with failures.', !data || !data.ok);
|
||||||
|
} catch (err) {
|
||||||
|
setSystemTestsStatus(err.message || 'System tests failed.', true);
|
||||||
|
} finally {
|
||||||
|
el.systemTestsRun.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Ollama Test button handler
|
// Ollama Test button handler
|
||||||
if (el.ollamaTestRun) {
|
if (el.ollamaTestRun) {
|
||||||
el.ollamaTestRun.addEventListener('click', async () => {
|
el.ollamaTestRun.addEventListener('click', async () => {
|
||||||
|
|||||||
261
chat/server.js
261
chat/server.js
@@ -20,7 +20,7 @@ const blogSystem = require('./blog-system');
|
|||||||
const versionManager = require('./src/utils/versionManager');
|
const versionManager = require('./src/utils/versionManager');
|
||||||
const { DATA_ROOT, STATE_DIR, DB_PATH, KEY_FILE } = require('./src/database/config');
|
const { DATA_ROOT, STATE_DIR, DB_PATH, KEY_FILE } = require('./src/database/config');
|
||||||
const { initDatabase, getDatabase, closeDatabase } = require('./src/database/connection');
|
const { initDatabase, getDatabase, closeDatabase } = require('./src/database/connection');
|
||||||
const { initEncryption, encrypt } = require('./src/utils/encryption');
|
const { initEncryption, encrypt, decrypt, isEncryptionInitialized } = require('./src/utils/encryption');
|
||||||
|
|
||||||
let sharp = null;
|
let sharp = null;
|
||||||
try {
|
try {
|
||||||
@@ -17279,6 +17279,259 @@ async function handleAdminOllamaTest(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAdminSystemTests(req, res) {
|
||||||
|
const adminSession = requireAdminAuth(req, res);
|
||||||
|
if (!adminSession) return;
|
||||||
|
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
const pushResult = (name, status, details, meta, durationMs) => {
|
||||||
|
results.push({
|
||||||
|
name,
|
||||||
|
status,
|
||||||
|
details: details || '',
|
||||||
|
meta: meta || null,
|
||||||
|
durationMs: durationMs || 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const runCheck = async (name, fn) => {
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const outcome = await fn();
|
||||||
|
const status = outcome?.status || 'passed';
|
||||||
|
pushResult(name, status, outcome?.details || '', outcome?.meta || null, Date.now() - start);
|
||||||
|
} catch (error) {
|
||||||
|
pushResult(
|
||||||
|
name,
|
||||||
|
'failed',
|
||||||
|
error?.message || String(error),
|
||||||
|
{ error: String(error) },
|
||||||
|
Date.now() - start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await runCheck('database.enabled', async () => {
|
||||||
|
const db = getDatabase();
|
||||||
|
if (!databaseEnabled || !db || !db.open) {
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
details: `Database disabled (${databaseFallbackReason || 'unknown'})`,
|
||||||
|
meta: {
|
||||||
|
dbPath: DB_PATH,
|
||||||
|
enabled: databaseEnabled,
|
||||||
|
fallbackReason: databaseFallbackReason || null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 'passed',
|
||||||
|
details: `Database enabled (${DB_PATH})`,
|
||||||
|
meta: {
|
||||||
|
dbPath: DB_PATH,
|
||||||
|
sqlcipher: DATABASE_USE_SQLCIPHER,
|
||||||
|
walMode: DATABASE_WAL_MODE,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await runCheck('database.schema', async () => {
|
||||||
|
const db = getDatabase();
|
||||||
|
if (!databaseEnabled || !db || !db.open) {
|
||||||
|
return { status: 'skipped', details: 'Database not enabled' };
|
||||||
|
}
|
||||||
|
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
|
||||||
|
const tableNames = new Set(tables.map((t) => t.name));
|
||||||
|
const missing = [];
|
||||||
|
|
||||||
|
if (!tableNames.has('users')) missing.push('users');
|
||||||
|
if (!tableNames.has('affiliate_accounts')) missing.push('affiliate_accounts');
|
||||||
|
if (!tableNames.has('sessions')) missing.push('sessions');
|
||||||
|
|
||||||
|
const userColumns = db.prepare("PRAGMA table_info('users')").all();
|
||||||
|
const userColumnNames = new Set(userColumns.map((c) => c.name));
|
||||||
|
if (!userColumnNames.has('data')) missing.push('users.data');
|
||||||
|
|
||||||
|
if (missing.length) {
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
details: `Missing schema: ${missing.join(', ')}`,
|
||||||
|
meta: { tables: Array.from(tableNames).sort().slice(0, 40) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { status: 'passed', details: 'Schema looks healthy' };
|
||||||
|
});
|
||||||
|
|
||||||
|
await runCheck('encryption.roundtrip', async () => {
|
||||||
|
if (!databaseEnabled) {
|
||||||
|
return { status: 'skipped', details: 'Database disabled; encryption not initialized' };
|
||||||
|
}
|
||||||
|
if (!isEncryptionInitialized()) {
|
||||||
|
return { status: 'failed', details: 'Encryption not initialized' };
|
||||||
|
}
|
||||||
|
const sample = `self-test-${randomUUID()}`;
|
||||||
|
const encrypted = encrypt(sample);
|
||||||
|
const decrypted = decrypt(encrypted);
|
||||||
|
if (decrypted !== sample) {
|
||||||
|
return { status: 'failed', details: 'Encryption roundtrip mismatch' };
|
||||||
|
}
|
||||||
|
return { status: 'passed', details: 'Encryption roundtrip ok' };
|
||||||
|
});
|
||||||
|
|
||||||
|
await runCheck('account.create', async () => {
|
||||||
|
const testEmail = `admin-selftest-${Date.now()}@example.com`;
|
||||||
|
const testPassword = `SelfTest1!XyZ${String(Date.now()).slice(-4)}`;
|
||||||
|
const passwordValidation = validatePassword(testPassword);
|
||||||
|
if (!passwordValidation.valid) {
|
||||||
|
return { status: 'failed', details: `Generated password failed validation: ${passwordValidation.errors.join(', ')}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
let testUser = null;
|
||||||
|
try {
|
||||||
|
testUser = await createUser(testEmail, testPassword);
|
||||||
|
const found = findUserByEmail(testEmail);
|
||||||
|
if (!found || found.id !== testUser.id) {
|
||||||
|
return { status: 'failed', details: 'User not found after creation' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginUser = await verifyUserPassword(testEmail, testPassword);
|
||||||
|
if (!loginUser || loginUser.id !== testUser.id) {
|
||||||
|
return { status: 'failed', details: 'Password verification failed' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseEnabled) {
|
||||||
|
const db = getDatabase();
|
||||||
|
if (!db) return { status: 'failed', details: 'Database not initialized during account test' };
|
||||||
|
const row = db.prepare('SELECT email, email_encrypted, password_hash, data FROM users WHERE id = ?').get(testUser.id);
|
||||||
|
if (!row) {
|
||||||
|
return { status: 'failed', details: 'User row missing from database' };
|
||||||
|
}
|
||||||
|
if (row.email !== testEmail) {
|
||||||
|
return { status: 'failed', details: 'Database email mismatch after creation' };
|
||||||
|
}
|
||||||
|
if (!row.password_hash) {
|
||||||
|
return { status: 'failed', details: 'Database password hash missing' };
|
||||||
|
}
|
||||||
|
if (!row.email_encrypted) {
|
||||||
|
return { status: 'failed', details: 'Encrypted email missing in database' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'passed', details: 'Account created and verified' };
|
||||||
|
} finally {
|
||||||
|
if (testUser) {
|
||||||
|
const index = usersDb.findIndex((u) => u && u.id === testUser.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
usersDb.splice(index, 1);
|
||||||
|
await persistUsersDb();
|
||||||
|
}
|
||||||
|
if (databaseEnabled) {
|
||||||
|
const db = getDatabase();
|
||||||
|
if (db) {
|
||||||
|
try {
|
||||||
|
db.prepare('DELETE FROM users WHERE id = ?').run(testUser.id);
|
||||||
|
} catch (_) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await runCheck('payments.topups.config', async () => {
|
||||||
|
if (!DODO_ENABLED) {
|
||||||
|
return { status: 'skipped', details: 'Dodo Payments disabled' };
|
||||||
|
}
|
||||||
|
const pack = resolveTopupPack('topup_1', 'usd');
|
||||||
|
const baseAmount = getTopupPrice(pack.tier, pack.currency);
|
||||||
|
if (!pack.productId || !pack.tokens || !baseAmount) {
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
details: 'Top-up configuration missing (productId, tokens, or price)',
|
||||||
|
meta: { pack, baseAmount },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 'passed',
|
||||||
|
details: `Top-up configured (${pack.tokens} tokens for ${pack.currency.toUpperCase()} ${baseAmount})`,
|
||||||
|
meta: { productId: pack.productId },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await runCheck('payments.topups.checkout', async () => {
|
||||||
|
if (!DODO_ENABLED) {
|
||||||
|
return { status: 'skipped', details: 'Dodo Payments disabled' };
|
||||||
|
}
|
||||||
|
const { pack, baseAmount } = await fetchTopupProduct('topup_1', 'usd');
|
||||||
|
const unitAmount = applyTopupDiscount(baseAmount, 0);
|
||||||
|
const orderId = `admin_self_test_${randomUUID()}`;
|
||||||
|
const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(email || ''));
|
||||||
|
const adminEmail = isValidEmail(ADMIN_USER) ? ADMIN_USER : 'admin@example.com';
|
||||||
|
const checkoutBody = {
|
||||||
|
product_cart: [{
|
||||||
|
product_id: pack.productId,
|
||||||
|
quantity: 1,
|
||||||
|
amount: unitAmount,
|
||||||
|
}],
|
||||||
|
customer: {
|
||||||
|
email: adminEmail,
|
||||||
|
name: ADMIN_USER || 'Admin',
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
type: 'admin_self_test',
|
||||||
|
orderId,
|
||||||
|
admin: 'true',
|
||||||
|
tokens: String(pack.tokens),
|
||||||
|
tier: String(pack.tier),
|
||||||
|
currency: String(pack.currency),
|
||||||
|
amount: String(unitAmount),
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
redirect_immediately: false,
|
||||||
|
},
|
||||||
|
return_url: `${resolveBaseUrl(req)}/test-checkout`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkoutSession = await dodoRequest('/checkouts', {
|
||||||
|
method: 'POST',
|
||||||
|
body: checkoutBody,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionId = checkoutSession?.session_id || checkoutSession?.id || '';
|
||||||
|
if (!sessionId || !checkoutSession?.checkout_url) {
|
||||||
|
return { status: 'failed', details: 'Dodo checkout did not return a checkout URL' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 'passed',
|
||||||
|
details: 'Dodo checkout created successfully',
|
||||||
|
meta: { sessionId },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const durationMs = Date.now() - startedAt;
|
||||||
|
const summary = results.reduce(
|
||||||
|
(acc, r) => {
|
||||||
|
acc.total += 1;
|
||||||
|
if (r.status === 'passed') acc.passed += 1;
|
||||||
|
else if (r.status === 'failed') acc.failed += 1;
|
||||||
|
else acc.skipped += 1;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ total: 0, passed: 0, failed: 0, skipped: 0 }
|
||||||
|
);
|
||||||
|
const ok = summary.failed === 0;
|
||||||
|
|
||||||
|
sendJson(res, 200, {
|
||||||
|
ok,
|
||||||
|
startedAt: new Date(startedAt).toISOString(),
|
||||||
|
finishedAt: new Date().toISOString(),
|
||||||
|
durationMs,
|
||||||
|
summary,
|
||||||
|
results,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get detailed resource usage breakdown by session for admin panel
|
// Get detailed resource usage breakdown by session for admin panel
|
||||||
async function handleAdminResources(req, res) {
|
async function handleAdminResources(req, res) {
|
||||||
const adminSession = requireAdminAuth(req, res);
|
const adminSession = requireAdminAuth(req, res);
|
||||||
@@ -19551,6 +19804,7 @@ async function routeInternal(req, res, url, pathname) {
|
|||||||
if (req.method === 'GET' && pathname === '/api/admin/resources') return handleAdminResources(req, res);
|
if (req.method === 'GET' && pathname === '/api/admin/resources') return handleAdminResources(req, res);
|
||||||
if (req.method === 'GET' && pathname === '/api/admin/external-testing-status') return handleAdminExternalTestingStatus(req, res);
|
if (req.method === 'GET' && pathname === '/api/admin/external-testing-status') return handleAdminExternalTestingStatus(req, res);
|
||||||
if (req.method === 'POST' && pathname === '/api/admin/external-testing-self-test') return handleAdminExternalTestingSelfTest(req, res);
|
if (req.method === 'POST' && pathname === '/api/admin/external-testing-self-test') return handleAdminExternalTestingSelfTest(req, res);
|
||||||
|
if (req.method === 'POST' && pathname === '/api/admin/system-tests') return handleAdminSystemTests(req, res);
|
||||||
if (req.method === 'POST' && pathname === '/api/upgrade-popup-tracking') return handleUpgradePopupTracking(req, res);
|
if (req.method === 'POST' && pathname === '/api/upgrade-popup-tracking') return handleUpgradePopupTracking(req, res);
|
||||||
if (req.method === 'POST' && pathname === '/api/admin/cancel-messages') return handleAdminCancelMessages(req, res);
|
if (req.method === 'POST' && pathname === '/api/admin/cancel-messages') return handleAdminCancelMessages(req, res);
|
||||||
if (req.method === 'POST' && pathname === '/api/admin/ollama-test') return handleAdminOllamaTest(req, res);
|
if (req.method === 'POST' && pathname === '/api/admin/ollama-test') return handleAdminOllamaTest(req, res);
|
||||||
@@ -19748,6 +20002,11 @@ async function routeInternal(req, res, url, pathname) {
|
|||||||
if (!session) return serveFile(res, safeStaticPath('admin-login.html'), 'text/html');
|
if (!session) return serveFile(res, safeStaticPath('admin-login.html'), 'text/html');
|
||||||
return serveFile(res, safeStaticPath('admin-resources.html'), 'text/html');
|
return serveFile(res, safeStaticPath('admin-resources.html'), 'text/html');
|
||||||
}
|
}
|
||||||
|
if (pathname === '/admin/system-tests') {
|
||||||
|
const session = getAdminSession(req);
|
||||||
|
if (!session) return serveFile(res, safeStaticPath('admin-login.html'), 'text/html');
|
||||||
|
return serveFile(res, safeStaticPath('admin-system-tests.html'), 'text/html');
|
||||||
|
}
|
||||||
if (pathname === '/admin/external-testing') {
|
if (pathname === '/admin/external-testing') {
|
||||||
const session = getAdminSession(req);
|
const session = getAdminSession(req);
|
||||||
if (!session) return serveFile(res, safeStaticPath('admin-login.html'), 'text/html');
|
if (!session) return serveFile(res, safeStaticPath('admin-login.html'), 'text/html');
|
||||||
|
|||||||
Reference in New Issue
Block a user