diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 69 |
1 files changed, 68 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 50a972b..4aab150 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -1130,7 +1130,31 @@ function CreateGroupWizard({ token, username, onCreated, allowPublicGroups = tru } } - // Step 1: Group details + directories + // Node detected but not fully ready — provision config and/or link key, + // then wait for 'running' before showing the group form. + const provisionAttempted = useRef(false); + useEffect(() => { + if (step === 1 && nodeStatus && !nodeStarting + && !provisionAttempted.current + && (nodeStatus.status === 'waiting_for_account' + || nodeStatus.status === 'waiting_for_node_key' + || nodeStatus.status === 'starting')) { + provisionAttempted.current = true; + startNode(); + } + }, [step, nodeStatus, nodeStarting, startNode]); + + if (step === 1 && nodeStatus + && nodeStatus.status !== 'running' && nodeStatus.status !== undefined) { + return html`<div class="page-content"> + <h2>${t('wizard.title')}</h2> + <p class="page-message">${error + ? t('wizard.wrong_account') + : t('wizard.detecting')}</p> + ${error && html`<button class="btn btn-secondary" onClick=${detectNode}> + ${t('wizard.retry')}</button>`} + </div>`; + } if (step === 1) { const canProceed = name.trim() && roots.length > 0 && enabledApps.length > 0; return html`<div class="page-content"> @@ -2352,6 +2376,10 @@ function NodePage({ token, username, userId, groups }) { const [attachGroup_, setAttachGroup_] = useState(''); const [attachDir, setAttachDir] = useState(''); const [attachUpload, setAttachUpload] = useState(''); + const [operatorPaired, setOperatorPaired] = useState(false); + const [pairCode, setPairCode] = useState(''); + const [pairStatus, setPairStatus] = useState(''); + const [pairing, setPairing] = useState(false); const transportRef = useRef(null); const connectAndFetch = useCallback(async () => { @@ -2405,6 +2433,7 @@ function NodePage({ token, username, userId, groups }) { console.log('[NodePage] got status:', result.groups?.length, 'groups'); transportRef.current = transport; setNodeGroups(result.groups || []); + setOperatorPaired(!!result.operator_paired); setStatus('connected'); return; } catch (err) { @@ -2437,6 +2466,7 @@ function NodePage({ token, username, userId, groups }) { try { const result = await transport.fetchNodeStatus(); setNodeGroups(result.groups || []); + setOperatorPaired(!!result.operator_paired); } catch {} }, []); @@ -2606,6 +2636,27 @@ function NodePage({ token, username, userId, groups }) { } }, [attachGroup_, attachDir, attachUpload, signFn]); + const doPairOperator = useCallback(async (e) => { + e.preventDefault(); + const code = pairCode.trim(); + if (!code) return; + setPairing(true); + setPairStatus(''); + try { + const transport = transportRef.current; + if (!transport || !transport.connected) throw new Error('Not connected'); + await transport.pairOperator(userId, code); + setPairCode(''); + setPairStatus('paired'); + setOperatorPaired(true); + await refresh(); + } catch (err) { + setPairStatus(err.message); + } finally { + setPairing(false); + } + }, [pairCode, userId, refresh]); + const reloadConfig = useCallback(async () => { const transport = transportRef.current; if (!transport || !transport.connected) return; @@ -2656,6 +2707,22 @@ function NodePage({ token, username, userId, groups }) { </div> <${NodeServicePanel} onChanged=${connectAndFetch} /> ${actionMsg && html`<div class="node-message">${actionMsg}</div>`} + ${!operatorPaired && html` + <div class="node-pair-banner"> + <p>${t('node.pair_needed')}</p> + <form onSubmit=${doPairOperator} class="node-pair-form"> + <input type="text" placeholder=${t('node.pair_code_placeholder')} + value=${pairCode} onInput=${e => setPairCode(e.target.value)} + disabled=${pairing} /> + <button class="btn btn-primary btn-small" type="submit" + disabled=${pairing || !pairCode.trim()}> + ${t('node.pair_button')}</button> + </form> + ${pairStatus === 'paired' + ? html`<p class="success-msg">${t('node.pair_success')}</p>` + : pairStatus ? html`<p class="error-msg">${pairStatus}</p>` : null} + </div> + `} ${(() => { const hubIds = new Set((groups || []).map(g => g.id)); return nodeGroups.map(g => { |