From d1a1400db93184e8624627866cfa87f3c65c7fbf Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 14 Jul 2026 12:19:25 +0200 Subject: [PATCH] Move tracker hive to admin and expand nodes panel. Give Nodes & coverage full width on overview with inference prices and live speed, and expose model pricing on /v1/models. Co-authored-by: Cursor --- .../tracker/meshnet_tracker/dashboard.html | 100 +++++++++++++++--- packages/tracker/meshnet_tracker/server.py | 12 +++ 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/packages/tracker/meshnet_tracker/dashboard.html b/packages/tracker/meshnet_tracker/dashboard.html index 7e881a5..9d41c32 100644 --- a/packages/tracker/meshnet_tracker/dashboard.html +++ b/packages/tracker/meshnet_tracker/dashboard.html @@ -247,8 +247,7 @@

Account

loading…
-

Tracker hive

loading…
-

Nodes & coverage

loading…
+

Nodes & coverage

loading…

Model usage (RPM)

loading…

Routing (learned)

loading…

Model inference speed

loading…
@@ -287,6 +286,7 @@

Request history

login required

Node pending payouts

admin login required

Settlement history

admin login required
+

Tracker hive

loading…

All accounts (admin)

Strikes / bans / forfeitures

admin login required

Client balances

admin login required
@@ -524,7 +524,67 @@ function renderHive(raft) { (raft.peers ? `
peers: ${esc(Array.isArray(raft.peers) ? raft.peers.join(", ") : raft.peers)}` : ""); } -function renderNodes(map) { +function nodeLiveTps(node) { + const reqs = (node.stats && node.stats.current_requests) || []; + let best = null; + for (const req of reqs) { + const rate = req.tokens_per_sec; + if (rate == null) continue; + if (best === null || rate > best) best = rate; + } + return best; +} + +function buildModelPriceMap(modelsResp) { + const prices = new Map(); + for (const model of (modelsResp && modelsResp.data) || []) { + if (!model.pricing) continue; + for (const key of modelLookupKeys(model)) { + prices.set(key, model.pricing); + prices.set(modelAliasKey(key), model.pricing); + } + } + return prices; +} + +function lookupModelPricing(modelKey, aliasMap, priceMap) { + const candidates = [modelKey]; + const alias = aliasMap.get(modelAliasKey(modelKey)); + if (alias) candidates.push(alias); + for (const candidate of candidates) { + const hit = priceMap.get(candidate) || priceMap.get(modelAliasKey(candidate)); + if (hit) return hit; + } + return null; +} + +function formatInferencePrice(pricing) { + if (!pricing) return ''; + const input = pricing.input_per_1k_usdt; + const output = pricing.output_per_1k_usdt; + if (input == null && output == null) return ''; + if (input === output || output == null) { + return `${usdt(input ?? output)}/1k`; + } + return `${usdt(input)}/${usdt(output)} in/out per 1k`; +} + +function modelGroupTypicalTps(modelKey, aliasMap, routing) { + const sampleNode = { hf_repo: modelKey, model: modelKey }; + const resolved = resolveModelGroup(sampleNode, aliasMap); + const model = { + id: resolved, + name: resolved, + hf_repo: modelKey.includes("/") ? modelKey : null, + aliases: [modelKey, resolved].filter(Boolean), + }; + return chatModelTypicalTps(model, routing); +} + +function renderNodes(payload) { + const map = payload && payload.map; + const routing = payload && payload.routing; + const priceMap = payload && payload.priceMap; const nodes = (map && map.nodes) || []; if (!nodes.length) { $("nodes").innerHTML = '
no nodes registered
'; return; @@ -543,16 +603,22 @@ function renderNodes(map) { .map(n => n.model_supply && n.model_supply.served_model_copies) .filter(v => v !== null && v !== undefined); const served = servedValues.length ? Math.max(...servedValues) : undefined; - html += `
${esc(model)} (${group.length} node${group.length===1?"":"s"} · ${esc(copies(served))} served)
`; - html += table(["node", "shard", "tps (1h)", "queue", "served"], group.map(n => { + const typicalTps = modelGroupTypicalTps(model, aliasMap, routing); + const pricing = lookupModelPricing(model, aliasMap, priceMap); + html += `
${esc(model)} (` + + `${group.length} node${group.length === 1 ? "" : "s"} · ${esc(copies(served))} served · ` + + `${esc(tps(typicalTps))} tok/s · price ${formatInferencePrice(pricing)})
`; + html += table(["node", "shard", "last tps", "tps (1h)", "queue", "served"], group.map(n => { const modelStats = (n.throughput && (n.throughput[n.hf_repo] || n.throughput[n.model])) || {}; return [ - nodeDisplayCell(n), - esc(`${n.shard_start ?? "?"}-${n.shard_end ?? "?"}`), - `${esc(tps(modelStats.tokens_per_sec_last_hour))}`, - esc(String((n.stats && n.stats.queue_depth) ?? 0)), - `${esc(copies(n.model_supply && n.model_supply.served_model_copies))}`, - ]; })); + nodeDisplayCell(n), + esc(`${n.shard_start ?? "?"}-${n.shard_end ?? "?"}`), + `${esc(tps(nodeLiveTps(n)))}`, + `${esc(tps(modelStats.tokens_per_sec_last_hour))}`, + esc(String((n.stats && n.stats.queue_depth) ?? 0)), + `${esc(copies(n.model_supply && n.model_supply.served_model_copies))}`, + ]; + })); } $("nodes").innerHTML = html; } @@ -2293,21 +2359,21 @@ function markRefreshed() { async function fetchOverviewTab() { const fetches = [ - fetchJson("/v1/raft/status"), fetchJson("/v1/network/map"), + fetchJson("/v1/models"), fetchJson("/v1/stats"), fetchJson("/v1/routing"), fetchJson("/v1/console"), ]; if (isLoggedIn) fetches.push(apiCall("/v1/account")); const results = await Promise.all(fetches); - const [raft, map, stats, routing, consoleData, accountResp] = results; + const [map, models, stats, routing, consoleData, accountResp] = results; lastStats = stats; lastRouting = routing; lastNetworkMap = map; if (accountResp && accountResp.ok) applyAccountSummary(accountResp.data, false); - renderIfChanged("hive", raft, renderHive); - renderIfChanged("nodes", map, renderNodes); + const nodesPayload = { map, routing, priceMap: buildModelPriceMap(models) }; + renderIfChanged("nodes", nodesPayload, renderNodes); renderIfChanged("stats", stats, renderStats); renderIfChanged("routing", routing, renderRouting); const speedModels = Object.keys((routing && routing.models) || {}); @@ -2348,13 +2414,15 @@ async function fetchBillingTab() { async function fetchAdminTab() { const fetches = [ + fetchJson("/v1/raft/status"), fetchJson("/v1/console"), fetchJson("/v1/billing/summary"), fetchJson("/v1/registry/wallets"), ]; if (isAdmin) fetches.push(apiCall("/v1/admin/accounts")); const results = await Promise.all(fetches); - const [consoleData, summary, wallets, adminResp] = results; + const [raft, consoleData, summary, wallets, adminResp] = results; + renderIfChanged("hive", raft, renderHive); renderIfChanged("console", consoleData, renderConsole); renderIfChanged("billing-summary", summary, data => renderBilling(data)); renderIfChanged("fraud", { wallets, summary }, data => renderFraud(data.wallets, data.summary)); diff --git a/packages/tracker/meshnet_tracker/server.py b/packages/tracker/meshnet_tracker/server.py index 2683036..ebe495e 100644 --- a/packages/tracker/meshnet_tracker/server.py +++ b/packages/tracker/meshnet_tracker/server.py @@ -3145,6 +3145,16 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler): self.send_response(404) self.end_headers() + def _model_pricing_payload(self, model: str) -> dict | None: + server: _TrackerHTTPServer = self.server # type: ignore[assignment] + if server.billing is None: + return None + in_rate, out_rate = server.billing.prices_for(model) + return { + "input_per_1k_usdt": in_rate, + "output_per_1k_usdt": out_rate, + } + def _handle_models(self): server: _TrackerHTTPServer = self.server # type: ignore[assignment] created = int(time.time()) @@ -3200,6 +3210,7 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler): "shard_coverage_percentage": coverage, "served_model_copies": served_copies, "quantizations": quantizations, + "pricing": self._model_pricing_payload(name), }) seen_ids.add(name) # Note: the preset's hf_repo is deliberately NOT added to seen_ids — @@ -3251,6 +3262,7 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler): required_start, required_end, ), + "pricing": self._model_pricing_payload(model_id), }) seen_ids.add(model_id) self._send_json(200, {"object": "list", "data": data})