Files
ruvnet--RuView/v2/crates/wifi-densepose-desktop/ui/src/hooks/useNodes.ts
T
rUv f49c722764 chore(repo): rename rust-port/wifi-densepose-rs → v2/ (flatten to one level) (#427)
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/)
without any sibling under rust-port/ that warranted the extra level.
Move the whole workspace up to v2/ to match v1/ (Python) at the same
depth and shorten every cd / build command across the repo.

git mv preserves history for all tracked files. 60 files updated for
path references (CI workflows, ADRs, docs, scripts, READMEs, internal
.claude-flow state). Two manual fixes for relative-cd paths in
CLAUDE.md and ADR-043 that became wrong after the depth change
(cd ../.. → cd ..).

Validated:
- cargo check --workspace --no-default-features → clean (after target/
  nuke; the gitignored target/ was carried by the OS rename and had
  hard-coded old paths in build scripts)
- cargo test --workspace --no-default-features → 1,539 passed, 0 failed,
  8 ignored (same totals as pre-rename)
- ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm)

After-merge follow-up: contributors should `rm -rf v2/target` once and
let cargo regenerate from the new path.
2026-04-25 21:28:13 -04:00

92 lines
2.3 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from "react";
import { invoke } from "@tauri-apps/api/core";
import type { Node } from "../types";
interface UseNodesOptions {
/** Auto-poll interval in milliseconds. Set to 0 to disable. Default: 10000 */
pollInterval?: number;
/** Whether to start scanning on mount. Default: false */
autoScan?: boolean;
}
interface UseNodesReturn {
nodes: Node[];
isScanning: boolean;
error: string | null;
scan: () => Promise<void>;
/** Total nodes discovered */
total: number;
/** Nodes currently online */
onlineCount: number;
/** Nodes currently offline */
offlineCount: number;
}
export function useNodes(options: UseNodesOptions = {}): UseNodesReturn {
const { pollInterval = 10_000, autoScan = false } = options;
const [nodes, setNodes] = useState<Node[]>([]);
const [isScanning, setIsScanning] = useState(false);
const [error, setError] = useState<string | null>(null);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const scan = useCallback(async () => {
if (isScanning) return;
setIsScanning(true);
setError(null);
try {
const discovered = await invoke<Node[]>("discover_nodes", {
timeoutMs: 5000,
});
setNodes(discovered);
} catch (err) {
const message =
err instanceof Error ? err.message : String(err);
setError(message);
} finally {
setIsScanning(false);
}
}, [isScanning]);
// Auto-scan on mount if requested
useEffect(() => {
if (autoScan) {
scan();
}
}, [autoScan]); // eslint-disable-line react-hooks/exhaustive-deps
// Polling interval
useEffect(() => {
if (pollInterval <= 0) return;
intervalRef.current = setInterval(() => {
scan();
}, pollInterval);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [pollInterval]); // eslint-disable-line react-hooks/exhaustive-deps
const onlineCount = nodes.filter(
(n) => n.health === "online"
).length;
const offlineCount = nodes.filter(
(n) => n.health === "offline"
).length;
return {
nodes,
isScanning,
error,
scan,
total: nodes.length,
onlineCount,
offlineCount,
};
}