← back to Ventura Claw
Wizard: Test button — saves + validates against connector health, auto-advances on ✓
437cf54637a3ad7023c21b96fe1d32c8ba534b72 · 2026-05-06 16:47:20 -0700 · SteveStudio2
- New 'Test' button next to 'Save & Next →' chains save → /api/connectors/:id/health
- Inline result panel shows: ok (green, auto-advances 1.2s), 'no test handler' for
catalog-only (yellow, stays put), error reason (red, stays put for fixing)
- 'Save & Next →' still works as fast-path skip-validation
- Test-result resets each step
Files touched
M server/public/admin-wizard.html
Diff
commit 437cf54637a3ad7023c21b96fe1d32c8ba534b72
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 16:47:20 2026 -0700
Wizard: Test button — saves + validates against connector health, auto-advances on ✓
- New 'Test' button next to 'Save & Next →' chains save → /api/connectors/:id/health
- Inline result panel shows: ok (green, auto-advances 1.2s), 'no test handler' for
catalog-only (yellow, stays put), error reason (red, stays put for fixing)
- 'Save & Next →' still works as fast-path skip-validation
- Test-result resets each step
---
server/public/admin-wizard.html | 87 +++++++++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 12 deletions(-)
diff --git a/server/public/admin-wizard.html b/server/public/admin-wizard.html
index 40aae97..309f779 100644
--- a/server/public/admin-wizard.html
+++ b/server/public/admin-wizard.html
@@ -66,6 +66,19 @@
.toast.ok { background:rgba(143,184,154,0.18); border:1px solid var(--good); color:var(--good); }
.toast.bad { background:rgba(200,122,110,0.18); border:1px solid var(--bad); color:var(--bad); }
+ /* Test-result inline panel (between fields and actions) */
+ .test-result {
+ margin: 4px 0 14px 0; padding: 10px 14px; border-radius: 3px;
+ font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em;
+ display: none; line-height: 1.5; word-break: break-word;
+ }
+ .test-result.ok { background: rgba(143,184,154,0.10); border: 1px solid var(--good); color: var(--good); }
+ .test-result.skip { background: rgba(212,160,74,0.06); border: 1px solid var(--ink-faint); color: var(--ink-mute); }
+ .test-result.bad { background: rgba(200,122,110,0.12); border: 1px solid var(--bad); color: var(--bad); }
+ .test-result.testing { background: var(--bg-3); border: 1px solid var(--rule); color: var(--ink-mute); }
+ .test-result strong { font-weight: 600; }
+ .btn[disabled] { opacity: 0.5; cursor: not-allowed; }
+
.done-card { text-align:center; padding:48px 32px; }
.done-card .checkmark { font-size:64px; color:var(--good); margin-bottom:16px; }
.done-card .stat { font-family:var(--serif); font-size:36px; color:var(--gold); margin:18px 0 8px 0; }
@@ -91,9 +104,11 @@
<div class="question" id="question"></div>
<div id="fields"></div>
<div class="help-link" id="help-link"></div>
+ <div class="test-result" id="test-result"></div>
<div class="actions">
<button class="btn btn-skip" id="btn-skip">Skip</button>
<button class="btn btn-vault" id="btn-vault" title="Try to find this key in the secrets-manager vault">🔍 Search Vault</button>
+ <button class="btn" id="btn-test" title="Save then validate against the provider's API">Test</button>
<button class="btn btn-primary" id="btn-save">Save & Next →</button>
</div>
</div>
@@ -133,6 +148,8 @@ async function load() {
function render() {
const c = allConnectors[cursor];
if (!c) return showDone(totalCatalog - allConnectors.length + cursor, totalCatalog);
+ // Reset test-result panel each step
+ const tr = $('#test-result'); if (tr) { tr.style.display = 'none'; tr.className = 'test-result'; tr.innerHTML = ''; }
$('#step-tag').textContent = 'STEP ' + (cursor + 1) + ' OF ' + allConnectors.length;
$('#conn-name').innerHTML = esc(c.name).replace(/\s+(\w+)$/, ' <em>$1</em>');
$('#conn-category').textContent = c.category || '—';
@@ -186,29 +203,75 @@ $('#btn-skip').addEventListener('click', () => {
render();
});
-$('#btn-save').addEventListener('click', async () => {
- const c = allConnectors[cursor];
+function collectFields(c) {
const body = {};
let any = false;
for (const f of c.fields || []) {
const el = $('#f-' + f.key);
if (el && el.value.trim()) { body[f.key] = el.value.trim(); any = true; }
}
- if (!any) {
- toast('Paste a key or hit Skip', true);
- return;
- }
+ return { body, any };
+}
+
+function setTestResult(state, html) {
+ const el = $('#test-result');
+ el.className = 'test-result ' + state;
+ el.innerHTML = html;
+ el.style.display = 'block';
+}
+
+async function saveCurrent() {
+ const c = allConnectors[cursor];
+ const { body, any } = collectFields(c);
+ if (!any) { toast('Paste a key or hit Skip', true); return null; }
const r = await fetch('/api/me/connections/' + encodeURIComponent(c.id), {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const j = await r.json();
- if (j.ok) {
- toast('✓ ' + c.name + ' connected');
- cursor++;
- render();
- } else {
- toast('✗ ' + (j.error || 'failed'), true);
+ if (!j.ok) {
+ toast('✗ ' + (j.error || 'save failed'), true);
+ return null;
+ }
+ return c;
+}
+
+$('#btn-save').addEventListener('click', async () => {
+ const saved = await saveCurrent();
+ if (!saved) return;
+ toast('✓ ' + saved.name + ' saved');
+ cursor++;
+ render();
+});
+
+$('#btn-test').addEventListener('click', async () => {
+ const c = allConnectors[cursor];
+ $('#btn-test').disabled = true; $('#btn-save').disabled = true;
+ setTestResult('testing', 'Saving + testing against ' + esc(c.name) + '…');
+ const saved = await saveCurrent();
+ if (!saved) {
+ setTestResult('bad', '<strong>Save failed.</strong> Fix above and try again.');
+ $('#btn-test').disabled = false; $('#btn-save').disabled = false;
+ return;
+ }
+ // Now hit the health endpoint
+ try {
+ const r = await fetch('/api/connectors/' + encodeURIComponent(c.id) + '/health');
+ const h = await r.json();
+ if (h.ok) {
+ setTestResult('ok', '<strong>✓ ' + esc(c.name) + ' authenticates cleanly.</strong> ' + esc(h.label || h.account || h.email || ''));
+ // Auto-advance after 1.2s on success
+ setTimeout(() => { cursor++; render(); $('#btn-test').disabled = false; $('#btn-save').disabled = false; }, 1200);
+ } else if (h.reason === 'not_implemented') {
+ setTestResult('skip', '<strong>Saved.</strong> No test handler for ' + esc(c.name) + ' yet — credentials stored, will be used once a handler is wired.');
+ $('#btn-test').disabled = false; $('#btn-save').disabled = false;
+ } else {
+ setTestResult('bad', '<strong>✗ ' + esc(c.name) + ' rejected.</strong> ' + esc(h.reason || h.error || 'unknown') + (h.hint ? ' · ' + esc(h.hint) : ''));
+ $('#btn-test').disabled = false; $('#btn-save').disabled = false;
+ }
+ } catch (e) {
+ setTestResult('bad', '<strong>✗ Test crashed.</strong> ' + esc(e.message));
+ $('#btn-test').disabled = false; $('#btn-save').disabled = false;
}
});
← 7334fa7 Connector wizard: one-question-at-a-time onboarding at /admi
·
back to Ventura Claw
·
Wizard: bulk health strip — see all saved connectors auth st 1ad784f →