diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-12 00:17:32 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-12 00:17:32 +0200 |
| commit | 14911aae21ec4975f7a805b91e3c4867cb41974b (patch) | |
| tree | 8c00230a88053905fd77938f3a9539efeb4c3094 /packages/meshbay-hub/src/meshbay_hub/static/index-dock.js | |
| parent | f6f107e02c7a4aa7326c9a82310a239327d81ed6 (diff) | |
| download | meshbay-14911aae21ec4975f7a805b91e3c4867cb41974b.tar.gz | |
feat: let the operator pause and resume a root's indexing (MNP 6.2)
A whole-root walk now checks its root before every file: removed or
ejected, it stops instead of reading the drive to the end; paused, it
finishes from the hash cache alone. A paused root is not watched, not
swept by reconcile and survives a restart (roster), its indexed files
stay served. Resume runs the reconcile sweep over the root, so entries
and their enrichment are kept.
Signed root_pause / root_resume over MNP, loopback routes, CLI
`root pause|resume`, a pause button on the indexing dock and
pause/resume in the roots table of Group settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/index-dock.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/index-dock.js | 62 |
1 files changed, 53 insertions, 9 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js b/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js index 6c89eb6..47a5818 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js @@ -6,7 +6,7 @@ import { navigate, session } from './hub-client.js'; import { useStickyBand } from './sticky.js'; import { Icon } from './icon.js'; import { - fromLoopback, fromPush, mergeActivity, dockRows, hideRow, percent, queuedCount, + fromLoopback, fromPush, mergeActivity, dockRows, hideRow, pauseRow, percent, queuedCount, } from './index-dock-model.js'; // ── Indexing dock ─────────────────────────────────────────────────────────── @@ -29,6 +29,9 @@ const ABSENT_POLL_MS = 30000; // rather than a prop through App: the page and the dock are far apart, and a // push every two seconds must not re-render the whole application. const pushed = new Map(); +// How to pause a root of that group over the page's connection, signed by its +// operator — the dock holds no transport of its own. +const pushedPause = new Map(); const listeners = new Set(); /** @@ -36,11 +39,17 @@ const listeners = new Set(); * member keeps the sidebar dot. `status` is the push (or the handshake ack's * `indexing`); `roots` the roots table the page opened, which names `root_pos`. * `null` when the page lets go of the group: nothing will update the entry any - * more, and a stale "scanning" would stay on screen for ever. + * more, and a stale "scanning" would stay on screen for ever. `pause(rootName)` + * is what the row's pause button calls when no node answers on this machine. */ -export function reportIndexPush(groupId, status, roots) { - if (status) pushed.set(groupId, fromPush(groupId, status, roots)); - else pushed.delete(groupId); +export function reportIndexPush(groupId, status, roots, pause) { + if (status) { + pushed.set(groupId, fromPush(groupId, status, roots)); + if (pause) pushedPause.set(groupId, pause); + } else { + pushed.delete(groupId); + pushedPause.delete(groupId); + } for (const fn of listeners) fn(); } @@ -96,7 +105,7 @@ function usePushedActivity() { return snapshot; } -function DockRow({ row, onOpen, onHide }) { +function DockRow({ row, error, onOpen, onPause, onHide }) { const done = row.state === 'done'; const waiting = !done && !row.scanning; const listing = !done && row.scanning && !row.totalBytes; @@ -109,7 +118,9 @@ function DockRow({ row, onOpen, onHide }) { const where = row.root ? `${row.groupName} › ${row.root}` : row.groupName; let meta = ''; - if (listing) { + if (error) { + meta = error; + } else if (listing) { meta = t('indexdock.listing'); } else if (!done && !waiting) { meta = [ @@ -133,7 +144,8 @@ function DockRow({ row, onOpen, onHide }) { onClick=${onOpen}> <span class="index-dock-head"> <span class="index-dock-title"><strong>${status}</strong> ${where}</span> - ${meta && html`<span class="index-dock-meta">${meta}</span>`} + ${meta && html`<span class="index-dock-meta ${error ? 'index-dock-error' : ''}" + role=${error ? 'alert' : null}>${meta}</span>`} </span> <span class="index-progress-bar index-dock-bar"> <span class="index-progress-fill ${moving ? 'index-dock-indeterminate' : ''}" @@ -141,6 +153,12 @@ function DockRow({ row, onOpen, onHide }) { </span> ${next && html`<span class="index-dock-next">${next}</span>`} </button> + ${!done && onPause && html` + <button type="button" class="index-dock-hide" title=${t('indexdock.pause')} + aria-label=${t('indexdock.pause')} onClick=${onPause}> + <${Icon} name="pause" /> + </button> + `} ${!done && html` <button type="button" class="index-dock-hide" title=${t('indexdock.hide')} aria-label=${t('indexdock.hide')} onClick=${onHide}> @@ -156,6 +174,7 @@ export function IndexingDock({ groups }) { const remote = usePushedActivity(); const memory = useRef({}); const [, setTick] = useState(0); + const [errors, setErrors] = useState({}); // Publishes `--index-dock-h` for the sidebar, as the music bar publishes its // own; withdrawn when the dock renders nothing. const band = useStickyBand('--index-dock-h'); @@ -173,11 +192,36 @@ export function IndexingDock({ groups }) { return () => clearInterval(id); }, [pending]); + // A whole-root walk names its root; a burst of file events does not, and + // there is nothing to pause. The local node first, as for the figures. + const pauser = (row) => { + if (row.state !== 'running' || !row.scanning || !row.root) return null; + if (local[row.groupId]) { + return (rootName) => platform.node.op('pauseRoot', { groupId: row.groupId, rootName }); + } + return pushedPause.get(row.groupId) || null; + }; + + const pause = async (row, via) => { + const before = memory.current; + memory.current = pauseRow(memory.current, row.groupId); + setErrors((e) => ({ ...e, [row.groupId]: '' })); + setTick((n) => n + 1); + try { + await via(row.root); + } catch (err) { + // Still running: put the row back, and say why. + memory.current = { ...memory.current, [row.groupId]: before[row.groupId] }; + setErrors((e) => ({ ...e, [row.groupId]: platform.bridgeMessage(err) })); + } + }; + if (!rows.length) return null; return html` <div class="index-dock" ref=${band}> ${rows.map((row) => html` - <${DockRow} key=${row.groupId} row=${row} + <${DockRow} key=${row.groupId} row=${row} error=${errors[row.groupId] || ''} + onPause=${pauser(row) && (() => pause(row, pauser(row)))} onOpen=${() => { session.openGroupTab = { groupId: row.groupId, tab: 'settings' }; navigate('/group/' + row.groupId); |