aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-page.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-page.js57
1 files changed, 56 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
index 1528213..29f3602 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
@@ -76,6 +76,10 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// every registered app when a node predates the setting (or hasn't answered
// yet), so nothing disappears for an existing group.
const [enabledApps, setEnabledApps] = useState(null);
+ // Reconcile interval / debounce currently in effect on the node — shown
+ // to the operator in Settings, not enforced from here (indexer.py owns
+ // that). Null until the handshake ack arrives.
+ const [scanSettings, setScanSettings] = useState(null);
// Paired ≠ operator account. `is_node_admin` says the hub account owning this
// node is the one connecting; this says the node pinned *this browser's* key
// as an operator key. Only the second one lets you sign an invite, and only
@@ -123,6 +127,25 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
cacheGroupIndex(groupId, group ? group.name : groupId, fresh);
}, [groupId, group]);
+ // additions/deletions only (daemon.py _broadcast_index_change, once there
+ // is a previous snapshot to diff against) — applied on top of whatever
+ // applyIndex last put in `entries`, instead of replacing the whole table
+ // for one changed file.
+ const applyIndexDelta = useCallback((deltaMsg) => {
+ setEntries((prev) => {
+ const deletions = new Set(deltaMsg.deletions || []);
+ const kept = prev.filter((e) => !deletions.has(e.id));
+ // The index is keyed by content hash: an addition whose id is already
+ // present is the same duplicate-content case indexer.py's own
+ // reconcile sweep leaves alone, not a second row for one file.
+ const keptIds = new Set(kept.map((e) => e.id));
+ const additions = (deltaMsg.additions || []).filter((e) => !keptIds.has(e.id));
+ const fresh = kept.concat(additions);
+ cacheGroupIndex(groupId, group ? group.name : groupId, fresh);
+ return fresh;
+ });
+ }, [groupId, group]);
+
useEffect(() => {
let cancelled = false;
@@ -172,11 +195,24 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
setIsNodeAdmin(!!ack.is_node_admin);
setMemberUpload(ack.member_upload !== false);
setEnabledApps(ack.enabled_apps || null);
+ setScanSettings(ack.scan_settings || null);
// Changed while we are connected, by an operator who may be someone
// else entirely. Without this the button stays until a reconnection,
// and a button that is still there is a button people press.
transport.onUploadPolicy = (allowed) => setMemberUpload(allowed);
transport.onAppsEnabled = (apps) => setEnabledApps(apps);
+ // The node's own scan (a root added while we were already connected,
+ // or reconcile catching one back up) — never the entries, just
+ // enough to animate the sidebar dot. Guaranteed a final push at the
+ // False transition (daemon.py _progress_pusher), so this always
+ // settles back to 'online' rather than getting stuck.
+ transport.onIndexProgress = (status) => {
+ if (cancelled || !onPresence) return;
+ const pct = status.total_bytes
+ ? Math.min(100, Math.round(100 * status.scanned_bytes / status.total_bytes))
+ : 0;
+ onPresence(groupId, status.scanning ? 'indexing' : 'online', pct);
+ };
setOperatorPaired(transport.memberRole === 'operator');
// A first join to this node generated an identity for it; leave it with
@@ -203,6 +239,10 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
if (cancelled) return;
applyIndex(msg);
};
+ transport.onIndexDelta = (msg) => {
+ if (cancelled) return;
+ applyIndexDelta(msg);
+ };
// We are in: an invitation to this group has served its purpose.
if (onJoined) onJoined(groupId);
@@ -214,7 +254,20 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
touchActivity();
// First-hand evidence, and the strongest available: this browser spoke
// to the node. It outranks whatever the hub said in the group list.
- if (onPresence) onPresence(groupId, 'online');
+ // A scan already under way at the moment of connecting (ack.indexing,
+ // webrtc_server.py _complete_handshake) shows as indexing right away
+ // rather than waiting for the next periodic push.
+ if (onPresence) {
+ const idx = ack.indexing;
+ if (idx && idx.scanning) {
+ const pct = idx.total_bytes
+ ? Math.min(100, Math.round(100 * idx.scanned_bytes / idx.total_bytes))
+ : 0;
+ onPresence(groupId, 'indexing', pct);
+ } else {
+ onPresence(groupId, 'online');
+ }
+ }
} catch (err) {
if (cancelled) return;
@@ -448,6 +501,8 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
onMemberUpload=${(allowed) => setMemberUpload(allowed)}
enabledApps=${enabledApps}
onEnabledApps=${(keys) => setEnabledApps(keys)}
+ scanSettings=${scanSettings}
+ onScanSettings=${(s) => setScanSettings(s)}
onLeft=${onLeft}
onPaired=${() => setOperatorPaired(true)} />
`}