diff --git a/backend/browse.py b/backend/browse.py index a4b999b..80c8cba 100644 --- a/backend/browse.py +++ b/backend/browse.py @@ -26,7 +26,21 @@ log = logging.getLogger("browse") _nodes: dict[str, dict] = {} # hash_hex -> node info _own_hash: str | None = None -_own_name: str = os.environ.get("NOMADNET_NODE_NAME", "Micronomicon") +def _own_name() -> str: + """Read node name from NomadNet config, falling back to env/default.""" + try: + path = _CONFIG_PATHS["nomadnet"]() + if path.exists(): + for line in path.read_text(encoding="utf-8").splitlines(): + stripped = line.strip() + if stripped.startswith("node_name"): + _, _, val = stripped.partition("=") + val = val.strip() + if val: + return val + except Exception: + pass + return os.environ.get("NOMADNET_NODE_NAME", "Micronomicon") _lock = threading.Lock() _started = False _subscribers: list[asyncio.Queue] = [] @@ -79,7 +93,7 @@ class _AnnounceHandler: except Exception: pass - is_self = name == _own_name + is_self = name == _own_name() # Determine which interface this announce arrived on iface_name = None @@ -183,6 +197,7 @@ def start_browser() -> None: _CONFIG_PATHS = { "reticulum": lambda: Path(os.environ.get("RNS_SERVER_CONFIG_DIR", os.environ.get("RNS_CONFIG_DIR", str(Path.home() / ".reticulum")))) / "config", + "reticulum-client": lambda: Path(os.environ.get("RNS_CONFIG_DIR", str(Path.home() / ".reticulum"))) / "config", "nomadnet": lambda: Path(os.environ.get("NOMADNET_CONFIG_DIR", str(Path.home() / ".nomadnetwork"))) / "config", } @@ -225,7 +240,7 @@ def _build_snapshot() -> list[dict]: if not any(n["is_self"] for n in nodes): nodes.insert(0, { "hash": _own_hash or "self", - "name": _own_name, + "name": _own_name(), "last_seen": time.time(), "is_self": True, "type": "node", @@ -406,6 +421,22 @@ async def _request_remote_page(hash_hex: str, path: str) -> str | None: # Reticulum config endpoints # --------------------------------------------------------------------------- +@router.get("/browse/identity") +async def get_identity(): + """Return the node's RNS identity hash and configured name.""" + identity_hash = None + try: + import RNS + if _reticulum and RNS.Transport.identity: + identity_hash = RNS.hexrep(RNS.Transport.identity.hash, delimit=False) + except Exception: + pass + return { + "name": _own_name(), + "hash": _own_hash or identity_hash, + } + + @router.post("/browse/restart") async def restart_services(): """Restart NomadNet to apply config changes.""" diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 54d7e8c..9c91fb3 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -226,13 +226,23 @@ export async function saveEnv(content: string): Promise { // Config (Reticulum + NomadNet) // --------------------------------------------------------------------------- -export async function fetchConfig(kind: "reticulum" | "nomadnet"): Promise { +export interface NodeIdentity { + name: string; + hash: string | null; +} + +export async function fetchIdentity(): Promise { + const res = await fetch("/api/browse/identity"); + return json(res); +} + +export async function fetchConfig(kind: "reticulum" | "reticulum-client" | "nomadnet"): Promise { const res = await fetch(`/api/browse/config/${kind}`); const data = await json<{ content: string }>(res); return data.content; } -export async function saveConfig(kind: "reticulum" | "nomadnet", content: string): Promise { +export async function saveConfig(kind: "reticulum" | "reticulum-client" | "nomadnet", content: string): Promise { const res = await fetch(`/api/browse/config/${kind}`, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/frontend/src/routes/SettingsView.tsx b/frontend/src/routes/SettingsView.tsx index c9ddcc9..6e6eb26 100644 --- a/frontend/src/routes/SettingsView.tsx +++ b/frontend/src/routes/SettingsView.tsx @@ -16,7 +16,7 @@ import type { ManagedWindow } from "@/hooks/useWindowManager"; const iniExtensions = iniHighlight(); -type ConfigKind = "reticulum" | "nomadnet"; +type ConfigKind = "reticulum" | "reticulum-client" | "nomadnet"; interface ConfigWinData { kind: ConfigKind; @@ -28,8 +28,13 @@ export default function SettingsView() { open, update, close, focus, } = useWindowManager({ w: 560, h: 440 }); + const [identity, setIdentity] = useState<{ name: string; hash: string | null } | null>(null); const [restarting, setRestarting] = useState(false); + useEffect(() => { + api.fetchIdentity().then(setIdentity).catch(() => {}); + }, []); + const handleRestart = useCallback(async () => { setRestarting(true); try { @@ -48,6 +53,16 @@ export default function SettingsView() { return (
+ {identity && ( +
+

Identity

+ {identity.name} + {identity.hash && ( + {identity.hash} + )} +
+ )} +

Configuration

@@ -55,7 +70,13 @@ export default function SettingsView() { variant="outline" onClick={() => open("reticulum", { kind: "reticulum" })} > - Reticulum + Reticulum Server + +