diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-settings.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-settings.js | 61 |
1 files changed, 60 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js index a666c29..8ed8eec 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -46,6 +46,9 @@ async function readLocalNode(node, groupId, attempts = NODE_CHECK_ATTEMPTS, // ── Shared Directories Table ──────────────────────────────────────────── +// How often the table asks its node what it could not read. +const TROUBLE_POLL_MS = 10000; + /** * A group's root directories, and the operator's controls over them. * @@ -98,6 +101,10 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, // reason nothing happened. const [msg, setMsg] = useState(null); const [indexProgress, setIndexProgress] = useState(null); + // What the node could not read and the folders it paused by itself, as its + // loopback API says them; `troubleTick` asks again after a change here. + const [trouble, setTrouble] = useState(null); + const [troubleTick, setTroubleTick] = useState(0); const say = useCallback((text) => setMsg(text ? { text, error: false } : null), []); const refuse = useCallback((text) => setMsg({ text, error: true }), []); @@ -161,6 +168,26 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, const canEdit = isLocal || overMnp || overLoopback; const canWiden = isLocal || onNodeMachine; + // Paths, so only where the loopback API answers: on the node's own machine. + // An operator elsewhere sees the folder paused, as any member does. + useEffect(() => { + if (!onNodeMachine) { setTrouble(null); return undefined; } + let stopped = false; + const read = async () => { + try { + const s = await platform.node.op('groupIndexStatus', { groupId }); + const next = { unread: s.unread || [], total: s.unread_total || 0, + stalled: s.stalled || [] }; + if (!stopped) { + setTrouble((prev) => JSON.stringify(prev) === JSON.stringify(next) ? prev : next); + } + } catch { /* busy, or mid-reload: the next poll asks again */ } + }; + read(); + const id = setInterval(read, TROUBLE_POLL_MS); + return () => { stopped = true; clearInterval(id); }; + }, [onNodeMachine, groupId, troubleTick]); + // Deliberately no index refresh after a root change. // // Adding a root makes the node reload, which rescans — minutes on a real @@ -182,7 +209,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, } catch (err) { refuse(platform.bridgeMessage(err)); return false; - } finally { setBusy(false); } + } finally { setBusy(false); setTroubleTick((n) => n + 1); } }, [onRootsChange, refuse]); const doUpdateRoot = useCallback(async (rootName, updates) => { @@ -296,6 +323,34 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, <p class="settings-hint" style="margin-top:8px"> ${t('settings_node.roots_local_only_hint')}</p>`; + // A folder the node paused because its disk stopped answering, and the + // files it gave up on. Under the table, with the paths: this is the list an + // operator used to need the node's log for. + const stalledAt = {}; + for (const s of (trouble && trouble.stalled) || []) { + if (displayRoots.some((r) => r.name === s.root && r.paused)) stalledAt[s.root] = s.path; + } + const troubleNote = (isLocal || !trouble) ? '' : html` + ${Object.entries(stalledAt).map(([name, file]) => html` + <p class="error-msg" role="status" style="margin-top:10px" key=${name}> + ${t('node.root_stalled_hint', { name, file })}</p>`)} + ${trouble.total > 0 && html` + <details class="unread-files"> + <summary>${t('node.unread_summary', { count: trouble.total })}</summary> + <ul> + ${trouble.unread.map((u) => html` + <li key=${u.path}> + <span class="unread-path">${u.path}</span> + <span class="unread-reason">${u.reason === 'no_answer' + ? t('node.unread_no_answer') : u.reason}</span> + </li>`)} + </ul> + ${trouble.total > trouble.unread.length && html` + <p class="settings-hint">${t('node.unread_more', + { count: trouble.total - trouble.unread.length })}</p>`} + </details>`} + `; + if (!displayRoots.length) { return html` <div class="shared-directories-table"> @@ -336,6 +391,9 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, <span class="node-root-badge node-root-badge-warn">${t('node.unavailable')}</span>`} ${!isLocal && r.paused && html` <span class="node-root-badge">${t('node.root_paused')}</span>`} + ${!isLocal && stalledAt[r.name] && html` + <span class="node-root-badge node-root-badge-warn" + title=${stalledAt[r.name]}>${t('node.root_stalled')}</span>`} </td> ${hasPaths && html` <td class="sdt-col-path" title=${r.path || ''}>${r.path || ''}</td>`} @@ -390,6 +448,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, `; })} </tbody> </table> + ${troubleNote} ${localOnlyHint} ${addControls} ${message} |