aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
diff options
context:
space:
mode:
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.js61
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}