node and account names

This commit is contained in:
Dobromir Popov
2026-07-08 21:33:42 +03:00
parent 52629d7762
commit 1e44e8e578
7 changed files with 275 additions and 8 deletions

View File

@@ -570,7 +570,7 @@ class _NodeEntry:
"benchmark_tokens_per_sec", "quantization", "managed_assignment",
"model_tokens_per_sec",
"pending_directives", "last_heartbeat", "tracker_mode",
"relay_addr", "cert_fingerprint", "peer_id",
"relay_addr", "cert_fingerprint", "peer_id", "friendly_name",
# heartbeat stats (reported by node, cumulative)
"total_requests", "failed_requests", "queue_depth", "proxy_inflight", "uptime_seconds",
"current_requests",
@@ -606,6 +606,7 @@ class _NodeEntry:
relay_addr: str | None = None,
cert_fingerprint: str | None = None,
peer_id: str | None = None,
friendly_name: str | None = None,
) -> None:
self.node_id = node_id
self.endpoint = endpoint
@@ -632,6 +633,7 @@ class _NodeEntry:
self.relay_addr = relay_addr
self.cert_fingerprint = cert_fingerprint
self.peer_id = peer_id
self.friendly_name = friendly_name
self.pending_directives: list[dict] = []
self.last_heartbeat: float = time.monotonic()
self.total_requests: int = 0
@@ -2231,6 +2233,22 @@ def _registration_ban_error(contracts: Any | None, wallet_address: str | None) -
return None
_MAX_FRIENDLY_NAME_LENGTH = 64
def _normalize_friendly_name(value: object) -> str | None:
if value is None:
return None
if not isinstance(value, str):
raise ValueError("friendly_name must be a string")
name = value.strip()
if not name:
return None
if len(name) > _MAX_FRIENDLY_NAME_LENGTH:
raise ValueError(f"friendly_name must be at most {_MAX_FRIENDLY_NAME_LENGTH} characters")
return name
def _tracker_log(
server: "_TrackerHTTPServer",
level: str,
@@ -2606,6 +2624,9 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
if self.path == "/v1/auth/logout":
self._handle_auth_logout()
return
if self.path == "/v1/account/profile":
self._handle_account_profile()
return
if self.path == "/v1/account/keys":
self._handle_account_key_create()
return
@@ -2923,6 +2944,7 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
"nodes": [
{
"node_id": node.node_id,
"friendly_name": node.friendly_name,
"endpoint": node.endpoint,
"relay_addr": node.relay_addr,
"peer_id": node.peer_id,
@@ -4001,6 +4023,11 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
relay_addr = body.get("relay_addr") or None
cert_fingerprint = body.get("cert_fingerprint") or None
peer_id = body.get("peer_id") or None
try:
friendly_name = _normalize_friendly_name(body.get("friendly_name"))
except ValueError as exc:
self._send_json(400, {"error": str(exc)})
return
node_id = _node_id_for_registration(
endpoint,
@@ -4035,6 +4062,7 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
relay_addr=relay_addr,
cert_fingerprint=cert_fingerprint,
peer_id=peer_id,
friendly_name=friendly_name,
)
with server.lock:
self._purge_expired_nodes()
@@ -4053,6 +4081,12 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
for eid in stale_ids:
old = server.registry.pop(eid)
stale_entries.append((eid, old))
if friendly_name is None:
for _eid, old in stale_entries:
if old.friendly_name:
friendly_name = old.friendly_name
break
entry.friendly_name = friendly_name
is_topology_change = node_id not in stale_ids or any(
(old.shard_start, old.shard_end) != (entry.shard_start, entry.shard_end)
for eid, old in stale_entries
@@ -4162,6 +4196,12 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
entry.uptime_seconds = float(body["uptime_seconds"])
if "status" in body and body["status"] in ("ready", "loading"):
entry.status = body["status"]
if "friendly_name" in body:
try:
entry.friendly_name = _normalize_friendly_name(body.get("friendly_name"))
except ValueError as exc:
self._send_json(400, {"error": str(exc)})
return
directives = list(entry.pending_directives)
entry.pending_directives.clear()
new_assignment = entry.pending_new_assignment
@@ -4443,6 +4483,7 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
email=body.get("email"),
wallet=body.get("wallet"),
password=str(body.get("password") or ""),
nickname=body.get("nickname"),
)
except ValueError as exc:
self._send_json(400, {"error": str(exc)})
@@ -4518,6 +4559,27 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
"topup_amount": server.devnet_topup_amount if server.billing is not None else 0.0,
})
def _handle_account_profile(self):
accounts = self._require_accounts()
if accounts is None:
return
account = self._session_account()
if account is None:
self._send_json(401, {"error": "login required"})
return
body = self._read_json_body()
if body is None:
return
try:
updated = accounts.update_profile(
account["account_id"],
nickname=body.get("nickname"),
)
except ValueError as exc:
self._send_json(400, {"error": str(exc)})
return
self._send_json(200, {"account": updated})
def _handle_account_key_create(self):
accounts = self._require_accounts()
if accounts is None:
@@ -6076,6 +6138,10 @@ class TrackerServer:
shard_end = int(payload["shard_end"]) if payload.get("shard_end") is not None else None
except (TypeError, ValueError):
return
try:
friendly_name = _normalize_friendly_name(payload.get("friendly_name"))
except ValueError:
friendly_name = None
entry = _NodeEntry(
node_id=node_id,
endpoint=endpoint.rstrip("/"),
@@ -6095,6 +6161,7 @@ class TrackerServer:
if isinstance(payload.get("downloaded_models"), list)
else None
),
friendly_name=_normalize_friendly_name(payload.get("friendly_name")),
)
with self._lock:
self._registry[node_id] = entry