aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js69
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 => {