feat: configs
This commit is contained in:
@@ -26,7 +26,21 @@ log = logging.getLogger("browse")
|
|||||||
|
|
||||||
_nodes: dict[str, dict] = {} # hash_hex -> node info
|
_nodes: dict[str, dict] = {} # hash_hex -> node info
|
||||||
_own_hash: str | None = None
|
_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()
|
_lock = threading.Lock()
|
||||||
_started = False
|
_started = False
|
||||||
_subscribers: list[asyncio.Queue] = []
|
_subscribers: list[asyncio.Queue] = []
|
||||||
@@ -79,7 +93,7 @@ class _AnnounceHandler:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
is_self = name == _own_name
|
is_self = name == _own_name()
|
||||||
|
|
||||||
# Determine which interface this announce arrived on
|
# Determine which interface this announce arrived on
|
||||||
iface_name = None
|
iface_name = None
|
||||||
@@ -183,6 +197,7 @@ def start_browser() -> None:
|
|||||||
|
|
||||||
_CONFIG_PATHS = {
|
_CONFIG_PATHS = {
|
||||||
"reticulum": lambda: Path(os.environ.get("RNS_SERVER_CONFIG_DIR", os.environ.get("RNS_CONFIG_DIR", str(Path.home() / ".reticulum")))) / "config",
|
"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",
|
"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):
|
if not any(n["is_self"] for n in nodes):
|
||||||
nodes.insert(0, {
|
nodes.insert(0, {
|
||||||
"hash": _own_hash or "self",
|
"hash": _own_hash or "self",
|
||||||
"name": _own_name,
|
"name": _own_name(),
|
||||||
"last_seen": time.time(),
|
"last_seen": time.time(),
|
||||||
"is_self": True,
|
"is_self": True,
|
||||||
"type": "node",
|
"type": "node",
|
||||||
@@ -406,6 +421,22 @@ async def _request_remote_page(hash_hex: str, path: str) -> str | None:
|
|||||||
# Reticulum config endpoints
|
# 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")
|
@router.post("/browse/restart")
|
||||||
async def restart_services():
|
async def restart_services():
|
||||||
"""Restart NomadNet to apply config changes."""
|
"""Restart NomadNet to apply config changes."""
|
||||||
|
|||||||
@@ -226,13 +226,23 @@ export async function saveEnv(content: string): Promise<void> {
|
|||||||
// Config (Reticulum + NomadNet)
|
// Config (Reticulum + NomadNet)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export async function fetchConfig(kind: "reticulum" | "nomadnet"): Promise<string> {
|
export interface NodeIdentity {
|
||||||
|
name: string;
|
||||||
|
hash: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchIdentity(): Promise<NodeIdentity> {
|
||||||
|
const res = await fetch("/api/browse/identity");
|
||||||
|
return json(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchConfig(kind: "reticulum" | "reticulum-client" | "nomadnet"): Promise<string> {
|
||||||
const res = await fetch(`/api/browse/config/${kind}`);
|
const res = await fetch(`/api/browse/config/${kind}`);
|
||||||
const data = await json<{ content: string }>(res);
|
const data = await json<{ content: string }>(res);
|
||||||
return data.content;
|
return data.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveConfig(kind: "reticulum" | "nomadnet", content: string): Promise<void> {
|
export async function saveConfig(kind: "reticulum" | "reticulum-client" | "nomadnet", content: string): Promise<void> {
|
||||||
const res = await fetch(`/api/browse/config/${kind}`, {
|
const res = await fetch(`/api/browse/config/${kind}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type { ManagedWindow } from "@/hooks/useWindowManager";
|
|||||||
|
|
||||||
const iniExtensions = iniHighlight();
|
const iniExtensions = iniHighlight();
|
||||||
|
|
||||||
type ConfigKind = "reticulum" | "nomadnet";
|
type ConfigKind = "reticulum" | "reticulum-client" | "nomadnet";
|
||||||
|
|
||||||
interface ConfigWinData {
|
interface ConfigWinData {
|
||||||
kind: ConfigKind;
|
kind: ConfigKind;
|
||||||
@@ -28,8 +28,13 @@ export default function SettingsView() {
|
|||||||
open, update, close, focus,
|
open, update, close, focus,
|
||||||
} = useWindowManager<ConfigWinData>({ w: 560, h: 440 });
|
} = useWindowManager<ConfigWinData>({ w: 560, h: 440 });
|
||||||
|
|
||||||
|
const [identity, setIdentity] = useState<{ name: string; hash: string | null } | null>(null);
|
||||||
const [restarting, setRestarting] = useState(false);
|
const [restarting, setRestarting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api.fetchIdentity().then(setIdentity).catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleRestart = useCallback(async () => {
|
const handleRestart = useCallback(async () => {
|
||||||
setRestarting(true);
|
setRestarting(true);
|
||||||
try {
|
try {
|
||||||
@@ -48,6 +53,16 @@ export default function SettingsView() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-full gap-4">
|
<div className="flex flex-col items-center justify-center h-full gap-4">
|
||||||
|
{identity && (
|
||||||
|
<div className="flex flex-col gap-1.5 text-center">
|
||||||
|
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Identity</h2>
|
||||||
|
<span className="text-sm font-medium">{identity.name}</span>
|
||||||
|
{identity.hash && (
|
||||||
|
<span className="text-[11px] font-mono text-muted-foreground select-all">{identity.hash}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 text-center">
|
<div className="flex flex-col gap-3 text-center">
|
||||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Configuration</h2>
|
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Configuration</h2>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
@@ -55,7 +70,13 @@ export default function SettingsView() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => open("reticulum", { kind: "reticulum" })}
|
onClick={() => open("reticulum", { kind: "reticulum" })}
|
||||||
>
|
>
|
||||||
Reticulum
|
Reticulum Server
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => open("reticulum-client", { kind: "reticulum-client" })}
|
||||||
|
>
|
||||||
|
Reticulum Client
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -92,7 +113,8 @@ export default function SettingsView() {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const TITLES: Record<ConfigKind, string> = {
|
const TITLES: Record<ConfigKind, string> = {
|
||||||
reticulum: "Reticulum Config",
|
reticulum: "Reticulum Server",
|
||||||
|
"reticulum-client": "Reticulum Client",
|
||||||
nomadnet: "NomadNet Config",
|
nomadnet: "NomadNet Config",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ glyphs = unicode
|
|||||||
[node]
|
[node]
|
||||||
# Enable page-serving node
|
# Enable page-serving node
|
||||||
enable_node = yes
|
enable_node = yes
|
||||||
node_name = Micronomicon
|
node_name = Yopalito
|
||||||
|
|
||||||
# Announce on the network
|
# Announce on the network
|
||||||
announce_interval = 360
|
announce_interval = 360
|
||||||
|
|||||||
Reference in New Issue
Block a user