nexaroa 0.0.111__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neuroshard/__init__.py +93 -0
- neuroshard/__main__.py +4 -0
- neuroshard/cli.py +466 -0
- neuroshard/core/__init__.py +92 -0
- neuroshard/core/consensus/verifier.py +252 -0
- neuroshard/core/crypto/__init__.py +20 -0
- neuroshard/core/crypto/ecdsa.py +392 -0
- neuroshard/core/economics/__init__.py +52 -0
- neuroshard/core/economics/constants.py +387 -0
- neuroshard/core/economics/ledger.py +2111 -0
- neuroshard/core/economics/market.py +975 -0
- neuroshard/core/economics/wallet.py +168 -0
- neuroshard/core/governance/__init__.py +74 -0
- neuroshard/core/governance/proposal.py +561 -0
- neuroshard/core/governance/registry.py +545 -0
- neuroshard/core/governance/versioning.py +332 -0
- neuroshard/core/governance/voting.py +453 -0
- neuroshard/core/model/__init__.py +30 -0
- neuroshard/core/model/dynamic.py +4186 -0
- neuroshard/core/model/llm.py +905 -0
- neuroshard/core/model/registry.py +164 -0
- neuroshard/core/model/scaler.py +387 -0
- neuroshard/core/model/tokenizer.py +568 -0
- neuroshard/core/network/__init__.py +56 -0
- neuroshard/core/network/connection_pool.py +72 -0
- neuroshard/core/network/dht.py +130 -0
- neuroshard/core/network/dht_plan.py +55 -0
- neuroshard/core/network/dht_proof_store.py +516 -0
- neuroshard/core/network/dht_protocol.py +261 -0
- neuroshard/core/network/dht_service.py +506 -0
- neuroshard/core/network/encrypted_channel.py +141 -0
- neuroshard/core/network/nat.py +201 -0
- neuroshard/core/network/nat_traversal.py +695 -0
- neuroshard/core/network/p2p.py +929 -0
- neuroshard/core/network/p2p_data.py +150 -0
- neuroshard/core/swarm/__init__.py +106 -0
- neuroshard/core/swarm/aggregation.py +729 -0
- neuroshard/core/swarm/buffers.py +643 -0
- neuroshard/core/swarm/checkpoint.py +709 -0
- neuroshard/core/swarm/compute.py +624 -0
- neuroshard/core/swarm/diloco.py +844 -0
- neuroshard/core/swarm/factory.py +1288 -0
- neuroshard/core/swarm/heartbeat.py +669 -0
- neuroshard/core/swarm/logger.py +487 -0
- neuroshard/core/swarm/router.py +658 -0
- neuroshard/core/swarm/service.py +640 -0
- neuroshard/core/training/__init__.py +29 -0
- neuroshard/core/training/checkpoint.py +600 -0
- neuroshard/core/training/distributed.py +1602 -0
- neuroshard/core/training/global_tracker.py +617 -0
- neuroshard/core/training/production.py +276 -0
- neuroshard/governance_cli.py +729 -0
- neuroshard/grpc_server.py +895 -0
- neuroshard/runner.py +3223 -0
- neuroshard/sdk/__init__.py +92 -0
- neuroshard/sdk/client.py +990 -0
- neuroshard/sdk/errors.py +101 -0
- neuroshard/sdk/types.py +282 -0
- neuroshard/tracker/__init__.py +0 -0
- neuroshard/tracker/server.py +864 -0
- neuroshard/ui/__init__.py +0 -0
- neuroshard/ui/app.py +102 -0
- neuroshard/ui/templates/index.html +1052 -0
- neuroshard/utils/__init__.py +0 -0
- neuroshard/utils/autostart.py +81 -0
- neuroshard/utils/hardware.py +121 -0
- neuroshard/utils/serialization.py +90 -0
- neuroshard/version.py +1 -0
- nexaroa-0.0.111.dist-info/METADATA +283 -0
- nexaroa-0.0.111.dist-info/RECORD +78 -0
- nexaroa-0.0.111.dist-info/WHEEL +5 -0
- nexaroa-0.0.111.dist-info/entry_points.txt +4 -0
- nexaroa-0.0.111.dist-info/licenses/LICENSE +190 -0
- nexaroa-0.0.111.dist-info/top_level.txt +2 -0
- protos/__init__.py +0 -0
- protos/neuroshard.proto +651 -0
- protos/neuroshard_pb2.py +160 -0
- protos/neuroshard_pb2_grpc.py +1298 -0
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>NeuroShard Node Dashboard</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
11
|
+
<script>
|
|
12
|
+
tailwind.config = {
|
|
13
|
+
darkMode: 'class',
|
|
14
|
+
theme: {
|
|
15
|
+
extend: {
|
|
16
|
+
colors: {
|
|
17
|
+
neuro: {
|
|
18
|
+
50: '#e6fff9',
|
|
19
|
+
100: '#b3ffed',
|
|
20
|
+
200: '#80ffe0',
|
|
21
|
+
300: '#4dffd4',
|
|
22
|
+
400: '#1affc7',
|
|
23
|
+
500: '#00d4aa',
|
|
24
|
+
600: '#00a888',
|
|
25
|
+
700: '#007d66',
|
|
26
|
+
800: '#005244',
|
|
27
|
+
900: '#002922',
|
|
28
|
+
},
|
|
29
|
+
dark: {
|
|
30
|
+
50: '#f7f7f8',
|
|
31
|
+
100: '#eeeef0',
|
|
32
|
+
200: '#d9d9de',
|
|
33
|
+
300: '#b8b8c1',
|
|
34
|
+
400: '#91919f',
|
|
35
|
+
500: '#737384',
|
|
36
|
+
600: '#5d5d6c',
|
|
37
|
+
700: '#4c4c58',
|
|
38
|
+
800: '#1a1a1f',
|
|
39
|
+
900: '#0f0f12',
|
|
40
|
+
950: '#09090b',
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
fontFamily: {
|
|
44
|
+
sans: ['Space Grotesk', 'system-ui', 'sans-serif'],
|
|
45
|
+
mono: ['JetBrains Mono', 'Menlo', 'monospace'],
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
<style>
|
|
52
|
+
/* Custom scrollbar */
|
|
53
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
54
|
+
::-webkit-scrollbar-track { background: #1a1a1f; }
|
|
55
|
+
::-webkit-scrollbar-thumb { background: #4c4c58; border-radius: 4px; }
|
|
56
|
+
::-webkit-scrollbar-thumb:hover { background: #5d5d6c; }
|
|
57
|
+
|
|
58
|
+
/* Animations */
|
|
59
|
+
@keyframes pulse-glow {
|
|
60
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(0, 212, 170, 0.4); }
|
|
61
|
+
50% { box-shadow: 0 0 0 8px rgba(0, 212, 170, 0); }
|
|
62
|
+
}
|
|
63
|
+
.pulse-glow { animation: pulse-glow 2s infinite; }
|
|
64
|
+
|
|
65
|
+
@keyframes slide-up {
|
|
66
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
67
|
+
to { opacity: 1; transform: translateY(0); }
|
|
68
|
+
}
|
|
69
|
+
.animate-slide-up { animation: slide-up 0.3s ease-out; }
|
|
70
|
+
|
|
71
|
+
/* Log colors */
|
|
72
|
+
.log-error { color: #ef4444; }
|
|
73
|
+
.log-warning { color: #f59e0b; }
|
|
74
|
+
.log-success { color: #22c55e; }
|
|
75
|
+
.log-neuro { color: #00d4aa; }
|
|
76
|
+
.log-training { color: #a855f7; }
|
|
77
|
+
.log-info { color: #94a3b8; }
|
|
78
|
+
.log-timestamp { color: #64748b; }
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body class="bg-dark-950 text-gray-100 font-sans h-screen overflow-hidden">
|
|
82
|
+
<div class="flex h-screen">
|
|
83
|
+
<!-- Sidebar -->
|
|
84
|
+
<aside class="w-72 bg-dark-900 border-r border-dark-700 flex flex-col overflow-y-auto">
|
|
85
|
+
<!-- Logo -->
|
|
86
|
+
<div class="p-6 border-b border-dark-700">
|
|
87
|
+
<div class="flex items-center gap-3">
|
|
88
|
+
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-neuro-400 to-neuro-600 flex items-center justify-center">
|
|
89
|
+
<span class="text-dark-950 font-bold text-xl">N</span>
|
|
90
|
+
</div>
|
|
91
|
+
<div>
|
|
92
|
+
<h1 class="font-bold text-lg tracking-tight">NEUROSHARD</h1>
|
|
93
|
+
<p class="text-xs text-dark-400" id="version">v0.1.0</p>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Status -->
|
|
99
|
+
<div class="p-4">
|
|
100
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
101
|
+
<div class="flex items-center gap-2">
|
|
102
|
+
<div id="status-dot" class="w-2.5 h-2.5 rounded-full bg-dark-500"></div>
|
|
103
|
+
<span id="status-text" class="font-semibold text-dark-400">CONNECTING</span>
|
|
104
|
+
</div>
|
|
105
|
+
<p id="status-detail" class="text-sm text-dark-500 mt-2">Initializing node...</p>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- Balance -->
|
|
110
|
+
<div class="px-4 pb-4">
|
|
111
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
112
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">NEURO Balance (Local)</p>
|
|
113
|
+
<p id="balance" class="text-3xl font-bold text-neuro-500 mt-1">0.0000</p>
|
|
114
|
+
<p class="text-xs text-dark-500 mt-2">
|
|
115
|
+
Earned: <span id="total-earned" class="text-neuro-400">0.0000</span>
|
|
116
|
+
</p>
|
|
117
|
+
<p id="balance-note" class="text-xs text-dark-600 mt-2 hidden">
|
|
118
|
+
<span class="text-yellow-500">⚠</span> Solo proofs pending network confirmation
|
|
119
|
+
</p>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- System Resources -->
|
|
124
|
+
<div class="px-4 pb-4">
|
|
125
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider mb-3 px-1">System Resources</p>
|
|
126
|
+
|
|
127
|
+
<!-- CPU -->
|
|
128
|
+
<div class="bg-dark-800 rounded-xl p-3 mb-2">
|
|
129
|
+
<div class="flex justify-between text-sm mb-2">
|
|
130
|
+
<span class="text-dark-400">CPU Usage</span>
|
|
131
|
+
<span id="cpu-percent" class="font-mono font-medium">0%</span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="h-2 bg-dark-700 rounded-full overflow-hidden">
|
|
134
|
+
<div id="cpu-bar" class="h-full bg-neuro-500 rounded-full transition-all duration-500" style="width: 0%"></div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- RAM -->
|
|
139
|
+
<div class="bg-dark-800 rounded-xl p-3">
|
|
140
|
+
<div class="flex justify-between text-sm mb-2">
|
|
141
|
+
<span class="text-dark-400">Memory</span>
|
|
142
|
+
<span id="ram-percent" class="font-mono font-medium text-xs">0 / 0 GB</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="h-2 bg-dark-700 rounded-full overflow-hidden">
|
|
145
|
+
<div id="ram-bar" class="h-full bg-neuro-500 rounded-full transition-all duration-500" style="width: 0%"></div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- DiLoCo Progress -->
|
|
151
|
+
<div class="px-4 pb-4" id="diloco-section" style="display: none;">
|
|
152
|
+
<div class="bg-dark-800 rounded-xl p-3">
|
|
153
|
+
<div class="flex justify-between text-sm mb-2">
|
|
154
|
+
<span class="text-dark-400">DiLoCo Sync</span>
|
|
155
|
+
<span id="diloco-progress" class="font-mono font-medium text-xs">0/500</span>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="h-2 bg-dark-700 rounded-full overflow-hidden">
|
|
158
|
+
<div id="diloco-bar" class="h-full bg-purple-500 rounded-full transition-all duration-500" style="width: 0%"></div>
|
|
159
|
+
</div>
|
|
160
|
+
<p class="text-xs text-dark-500 mt-2">Outer step: <span id="diloco-outer">0</span></p>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<!-- Spacer -->
|
|
165
|
+
<div class="flex-1"></div>
|
|
166
|
+
|
|
167
|
+
<!-- Stop Button -->
|
|
168
|
+
<div class="px-4 pb-4">
|
|
169
|
+
<button onclick="stopNode()" id="stop-btn" class="w-full px-4 py-3 bg-red-600/20 hover:bg-red-600/30 border border-red-600/50 rounded-xl text-red-400 font-medium transition-colors flex items-center justify-center gap-2">
|
|
170
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
171
|
+
<rect x="6" y="6" width="12" height="12" rx="2" stroke-width="2"/>
|
|
172
|
+
</svg>
|
|
173
|
+
Stop Node
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<!-- Footer -->
|
|
178
|
+
<div class="p-4 border-t border-dark-700">
|
|
179
|
+
<p class="text-xs text-dark-500 text-center">Decentralized AI Network</p>
|
|
180
|
+
<p class="text-xs text-dark-600 text-center mt-1">
|
|
181
|
+
<a href="https://neuroshard.com" target="_blank" class="hover:text-neuro-500 transition-colors">neuroshard.com</a>
|
|
182
|
+
</p>
|
|
183
|
+
</div>
|
|
184
|
+
</aside>
|
|
185
|
+
|
|
186
|
+
<!-- Main Content -->
|
|
187
|
+
<main class="flex-1 flex flex-col min-h-0 overflow-hidden">
|
|
188
|
+
<!-- Tab Navigation -->
|
|
189
|
+
<nav class="bg-dark-900 border-b border-dark-700 px-6 flex-shrink-0">
|
|
190
|
+
<div class="flex gap-1">
|
|
191
|
+
<button onclick="showTab('dashboard')" id="tab-dashboard" class="tab-btn px-5 py-4 text-sm font-medium border-b-2 border-neuro-500 text-neuro-500">
|
|
192
|
+
Dashboard
|
|
193
|
+
</button>
|
|
194
|
+
<button onclick="showTab('logs')" id="tab-logs" class="tab-btn px-5 py-4 text-sm font-medium border-b-2 border-transparent text-dark-400 hover:text-dark-200 transition-colors">
|
|
195
|
+
Logs
|
|
196
|
+
</button>
|
|
197
|
+
<button onclick="showTab('settings')" id="tab-settings" class="tab-btn px-5 py-4 text-sm font-medium border-b-2 border-transparent text-dark-400 hover:text-dark-200 transition-colors">
|
|
198
|
+
Settings
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
</nav>
|
|
202
|
+
|
|
203
|
+
<!-- Tab Content -->
|
|
204
|
+
<div id="tab-content-area" class="flex-1 flex flex-col overflow-hidden">
|
|
205
|
+
<!-- Dashboard Tab -->
|
|
206
|
+
<div id="content-dashboard" class="p-6 animate-slide-up overflow-auto flex-1">
|
|
207
|
+
<!-- Stats Grid -->
|
|
208
|
+
<div class="grid grid-cols-4 gap-4 mb-6">
|
|
209
|
+
<!-- Role -->
|
|
210
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
211
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Role</p>
|
|
212
|
+
<p id="stat-role" class="text-2xl font-bold mt-1">-</p>
|
|
213
|
+
</div>
|
|
214
|
+
<!-- Layers -->
|
|
215
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
216
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Layers</p>
|
|
217
|
+
<p id="stat-layers" class="text-2xl font-bold mt-1">0</p>
|
|
218
|
+
</div>
|
|
219
|
+
<!-- Parameters -->
|
|
220
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
221
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Parameters</p>
|
|
222
|
+
<p id="stat-params" class="text-2xl font-bold mt-1">0M</p>
|
|
223
|
+
</div>
|
|
224
|
+
<!-- Device -->
|
|
225
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
226
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Device</p>
|
|
227
|
+
<p id="stat-device" class="text-2xl font-bold mt-1">CPU</p>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<!-- Training Stats -->
|
|
232
|
+
<div class="grid grid-cols-4 gap-4 mb-6">
|
|
233
|
+
<!-- Tokens -->
|
|
234
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
235
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Tokens Processed</p>
|
|
236
|
+
<p id="stat-tokens" class="text-2xl font-bold mt-1">0</p>
|
|
237
|
+
</div>
|
|
238
|
+
<!-- Training Rounds -->
|
|
239
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
240
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Training Rounds</p>
|
|
241
|
+
<p id="stat-training" class="text-2xl font-bold mt-1">0</p>
|
|
242
|
+
</div>
|
|
243
|
+
<!-- Loss -->
|
|
244
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
245
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Current Loss</p>
|
|
246
|
+
<p id="stat-loss" class="text-2xl font-bold mt-1">∞</p>
|
|
247
|
+
</div>
|
|
248
|
+
<!-- Earned -->
|
|
249
|
+
<div class="bg-dark-800 rounded-xl p-4">
|
|
250
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Total Earned</p>
|
|
251
|
+
<p id="stat-earned" class="text-2xl font-bold text-neuro-500 mt-1">0.00</p>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<!-- Network Info -->
|
|
256
|
+
<div class="bg-dark-800 rounded-xl p-6">
|
|
257
|
+
<h3 class="font-semibold mb-4">Network Information</h3>
|
|
258
|
+
|
|
259
|
+
<div class="space-y-3">
|
|
260
|
+
<div class="flex items-center justify-between py-2 border-b border-dark-700">
|
|
261
|
+
<span class="text-dark-400">Wallet ID</span>
|
|
262
|
+
<span id="wallet-id" class="font-mono text-neuro-500">Not connected</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="flex items-center justify-between py-2 border-b border-dark-700">
|
|
265
|
+
<span class="text-dark-400">Instance</span>
|
|
266
|
+
<span id="instance-id" class="font-mono text-sm text-dark-400">-</span>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="flex items-center justify-between py-2 border-b border-dark-700">
|
|
269
|
+
<span class="text-dark-400">Inference Price</span>
|
|
270
|
+
<span id="inference-price" class="font-mono text-sm text-blue-400">-</span>
|
|
271
|
+
</div>
|
|
272
|
+
<div class="flex items-center justify-between py-2">
|
|
273
|
+
<span class="text-dark-400">Tracker</span>
|
|
274
|
+
<span id="tracker-status" class="text-dark-500">Disconnected</span>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<!-- Swarm Status -->
|
|
280
|
+
<div class="bg-dark-800 rounded-xl p-6 mt-6" id="swarm-section">
|
|
281
|
+
<h3 class="font-semibold mb-4">Swarm Architecture</h3>
|
|
282
|
+
|
|
283
|
+
<div class="grid grid-cols-3 gap-4">
|
|
284
|
+
<div class="bg-dark-700 rounded-lg p-3">
|
|
285
|
+
<p class="text-xs text-dark-500 uppercase">Inbound Buffer</p>
|
|
286
|
+
<p id="swarm-inbound" class="text-lg font-mono mt-1">0%</p>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="bg-dark-700 rounded-lg p-3">
|
|
289
|
+
<p class="text-xs text-dark-500 uppercase">Outbound Buffer</p>
|
|
290
|
+
<p id="swarm-outbound" class="text-lg font-mono mt-1">0%</p>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="bg-dark-700 rounded-lg p-3">
|
|
293
|
+
<p class="text-xs text-dark-500 uppercase">Known Peers</p>
|
|
294
|
+
<p id="swarm-peers" class="text-lg font-mono mt-1">0</p>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<!-- Logs Tab -->
|
|
301
|
+
<div id="content-logs" class="hidden flex flex-col flex-1 min-h-0">
|
|
302
|
+
<!-- Filter Bar -->
|
|
303
|
+
<div class="bg-dark-900 border-b border-dark-700 px-6 py-3 flex items-center gap-4 flex-shrink-0">
|
|
304
|
+
<span class="text-sm font-medium text-dark-400">Filter:</span>
|
|
305
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
306
|
+
<input type="checkbox" id="filter-all" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
|
|
307
|
+
<span class="text-sm">All</span>
|
|
308
|
+
</label>
|
|
309
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
310
|
+
<input type="checkbox" id="filter-neuro" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
|
|
311
|
+
<span class="text-sm text-neuro-500">Rewards</span>
|
|
312
|
+
</label>
|
|
313
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
314
|
+
<input type="checkbox" id="filter-training" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
|
|
315
|
+
<span class="text-sm text-purple-400">Training</span>
|
|
316
|
+
</label>
|
|
317
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
318
|
+
<input type="checkbox" id="filter-error" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
|
|
319
|
+
<span class="text-sm text-red-400">Errors</span>
|
|
320
|
+
</label>
|
|
321
|
+
<div class="flex-1"></div>
|
|
322
|
+
<button onclick="clearLogs()" class="px-3 py-1.5 text-sm bg-dark-700 hover:bg-dark-600 rounded transition-colors">
|
|
323
|
+
Clear Logs
|
|
324
|
+
</button>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<!-- Log Output -->
|
|
328
|
+
<div id="log-container" class="flex-1 overflow-y-auto min-h-0 bg-dark-950 p-4 font-mono text-sm">
|
|
329
|
+
<div id="log-output" class="space-y-1">
|
|
330
|
+
<p class="log-info">[00:00:00] NeuroShard Dashboard initialized</p>
|
|
331
|
+
<p class="log-info">[00:00:00] Connecting to node...</p>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<!-- Settings Tab -->
|
|
337
|
+
<div id="content-settings" class="hidden p-6 animate-slide-up overflow-auto flex-1">
|
|
338
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
339
|
+
<!-- Left Column -->
|
|
340
|
+
<div class="space-y-6">
|
|
341
|
+
<!-- Node Info -->
|
|
342
|
+
<div class="bg-dark-800 rounded-xl p-6">
|
|
343
|
+
<h3 class="font-semibold mb-4 flex items-center gap-2">
|
|
344
|
+
<svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
345
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
|
346
|
+
</svg>
|
|
347
|
+
Node Configuration
|
|
348
|
+
</h3>
|
|
349
|
+
<p class="text-sm text-dark-400 mb-4">
|
|
350
|
+
Current node settings. Restart to change.
|
|
351
|
+
</p>
|
|
352
|
+
|
|
353
|
+
<div class="space-y-4">
|
|
354
|
+
<div class="grid grid-cols-2 gap-4">
|
|
355
|
+
<div>
|
|
356
|
+
<label class="text-xs text-dark-500 uppercase tracking-wider">Port</label>
|
|
357
|
+
<p id="setting-port" class="text-lg font-mono text-neuro-400 mt-1">8000</p>
|
|
358
|
+
</div>
|
|
359
|
+
<div>
|
|
360
|
+
<label class="text-xs text-dark-500 uppercase tracking-wider">Wallet ID</label>
|
|
361
|
+
<p id="setting-wallet-id" class="text-sm font-mono text-dark-300 mt-1 truncate">-</p>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
<div>
|
|
365
|
+
<label class="text-xs text-dark-500 uppercase tracking-wider">Tracker</label>
|
|
366
|
+
<p id="setting-tracker" class="text-sm font-mono text-dark-400 mt-1 truncate">https://neuroshard.com/api/tracker</p>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<!-- Resource Limits -->
|
|
372
|
+
<div class="bg-dark-800 rounded-xl p-6">
|
|
373
|
+
<h3 class="font-semibold mb-4 flex items-center gap-2">
|
|
374
|
+
<svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
375
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
|
|
376
|
+
</svg>
|
|
377
|
+
Resource Limits
|
|
378
|
+
</h3>
|
|
379
|
+
<p class="text-sm text-dark-400 mb-4">
|
|
380
|
+
Adjust CPU and memory limits. Changes take effect immediately.
|
|
381
|
+
</p>
|
|
382
|
+
|
|
383
|
+
<div class="space-y-6">
|
|
384
|
+
<!-- CPU Slider -->
|
|
385
|
+
<div>
|
|
386
|
+
<div class="flex justify-between mb-2">
|
|
387
|
+
<label class="text-sm text-dark-400">Max CPU Threads</label>
|
|
388
|
+
<span id="cpu-limit-value" class="text-sm font-mono text-neuro-400">4</span>
|
|
389
|
+
</div>
|
|
390
|
+
<input id="cpu-limit-slider" type="range" min="1" max="16" value="4"
|
|
391
|
+
class="w-full h-2 bg-dark-700 rounded-lg appearance-none cursor-pointer accent-neuro-500"
|
|
392
|
+
oninput="updateCpuLimit(this.value)">
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<!-- Memory Slider -->
|
|
396
|
+
<div>
|
|
397
|
+
<div class="flex justify-between mb-2">
|
|
398
|
+
<label class="text-sm text-dark-400">Max Memory</label>
|
|
399
|
+
<span id="mem-limit-value" class="text-sm font-mono text-neuro-400">4096 MB</span>
|
|
400
|
+
</div>
|
|
401
|
+
<input id="mem-limit-slider" type="range" min="512" max="65536" value="4096" step="512"
|
|
402
|
+
class="w-full h-2 bg-dark-700 rounded-lg appearance-none cursor-pointer accent-neuro-500"
|
|
403
|
+
oninput="updateMemLimit(this.value)">
|
|
404
|
+
<p class="text-xs text-dark-500 mt-1">Memory for model layers (more = more layers = more rewards)</p>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<!-- Storage Slider -->
|
|
408
|
+
<div>
|
|
409
|
+
<div class="flex justify-between mb-2">
|
|
410
|
+
<label class="text-sm text-dark-400">Training Data Storage</label>
|
|
411
|
+
<span id="storage-limit-value" class="text-sm font-mono text-neuro-400">100 MB</span>
|
|
412
|
+
</div>
|
|
413
|
+
<input id="storage-limit-slider" type="range" min="50" max="10000" value="100" step="50"
|
|
414
|
+
class="w-full h-2 bg-dark-700 rounded-lg appearance-none cursor-pointer accent-neuro-500"
|
|
415
|
+
oninput="updateStorageLimit(this.value)">
|
|
416
|
+
<p class="text-xs text-dark-500 mt-1">Disk space for training data shards (~10 shards per 100MB)</p>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<button onclick="applyThrottle()" class="mt-6 w-full px-4 py-2.5 bg-neuro-600 hover:bg-neuro-500 rounded-lg text-dark-950 font-medium transition-colors">
|
|
421
|
+
Apply Changes
|
|
422
|
+
</button>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<!-- Right Column -->
|
|
427
|
+
<div class="space-y-6">
|
|
428
|
+
<!-- Network Info -->
|
|
429
|
+
<div class="bg-dark-800 rounded-xl p-6">
|
|
430
|
+
<h3 class="font-semibold mb-4 flex items-center gap-2">
|
|
431
|
+
<svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
432
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
|
|
433
|
+
</svg>
|
|
434
|
+
Network Status
|
|
435
|
+
</h3>
|
|
436
|
+
|
|
437
|
+
<div class="grid grid-cols-2 gap-4">
|
|
438
|
+
<div class="bg-dark-900 rounded-lg p-4">
|
|
439
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Peers</p>
|
|
440
|
+
<p id="setting-peers" class="text-2xl font-bold text-neuro-400 mt-1">0</p>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="bg-dark-900 rounded-lg p-4">
|
|
443
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Layers</p>
|
|
444
|
+
<p id="setting-layers" class="text-2xl font-bold text-neuro-400 mt-1">0</p>
|
|
445
|
+
</div>
|
|
446
|
+
<div class="bg-dark-900 rounded-lg p-4">
|
|
447
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Training</p>
|
|
448
|
+
<p id="setting-training" class="text-lg font-semibold mt-1">-</p>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="bg-dark-900 rounded-lg p-4">
|
|
451
|
+
<p class="text-xs text-dark-500 uppercase tracking-wider">Device</p>
|
|
452
|
+
<p id="setting-device" class="text-lg font-semibold mt-1">CPU</p>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<!-- CLI Reference -->
|
|
458
|
+
<div class="bg-dark-800 rounded-xl p-6">
|
|
459
|
+
<h3 class="font-semibold mb-4 flex items-center gap-2">
|
|
460
|
+
<svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
461
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
462
|
+
</svg>
|
|
463
|
+
CLI Quick Reference
|
|
464
|
+
</h3>
|
|
465
|
+
<pre class="bg-dark-950 p-4 rounded-lg text-xs overflow-x-auto font-mono text-neuro-400 leading-relaxed"><code># Basic
|
|
466
|
+
neuroshard-node --token YOUR_TOKEN
|
|
467
|
+
|
|
468
|
+
# Custom port
|
|
469
|
+
neuroshard-node --port 9000 --token YOUR_TOKEN
|
|
470
|
+
|
|
471
|
+
# Resource limits
|
|
472
|
+
neuroshard-node --token YOUR_TOKEN \
|
|
473
|
+
--memory 8192 --cpu-threads 8
|
|
474
|
+
|
|
475
|
+
# Inference only
|
|
476
|
+
neuroshard-node --token YOUR_TOKEN --no-training
|
|
477
|
+
|
|
478
|
+
# Headless mode
|
|
479
|
+
neuroshard-node --token YOUR_TOKEN --headless</code></pre>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</main>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<script>
|
|
489
|
+
// State
|
|
490
|
+
let logs = [];
|
|
491
|
+
let nodeId = '';
|
|
492
|
+
let lastLogId = 0; // Track last fetched log ID for polling
|
|
493
|
+
let clientLogIdCounter = -1; // Negative IDs for client-side logs (won't conflict with backend)
|
|
494
|
+
|
|
495
|
+
// Tab switching
|
|
496
|
+
function showTab(tabName) {
|
|
497
|
+
// Hide all content
|
|
498
|
+
document.querySelectorAll('[id^="content-"]').forEach(el => el.classList.add('hidden'));
|
|
499
|
+
// Remove active state from all tabs
|
|
500
|
+
document.querySelectorAll('.tab-btn').forEach(el => {
|
|
501
|
+
el.classList.remove('border-neuro-500', 'text-neuro-500');
|
|
502
|
+
el.classList.add('border-transparent', 'text-dark-400');
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Show selected content
|
|
506
|
+
document.getElementById(`content-${tabName}`).classList.remove('hidden');
|
|
507
|
+
// Set active tab
|
|
508
|
+
const tab = document.getElementById(`tab-${tabName}`);
|
|
509
|
+
tab.classList.remove('border-transparent', 'text-dark-400');
|
|
510
|
+
tab.classList.add('border-neuro-500', 'text-neuro-500');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Log management
|
|
514
|
+
function addLog(message, type = 'info') {
|
|
515
|
+
const now = new Date();
|
|
516
|
+
const timestamp = now.toLocaleTimeString('en-US', { hour12: false });
|
|
517
|
+
const epoch = now.getTime(); // Milliseconds since Unix epoch
|
|
518
|
+
|
|
519
|
+
logs.push({
|
|
520
|
+
id: clientLogIdCounter--,
|
|
521
|
+
epoch,
|
|
522
|
+
timestamp,
|
|
523
|
+
message,
|
|
524
|
+
type
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Keep last 500 logs
|
|
528
|
+
if (logs.length > 500) logs = logs.slice(-300);
|
|
529
|
+
|
|
530
|
+
renderLogs();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
let isFirstLoad = true;
|
|
534
|
+
|
|
535
|
+
function renderLogs() {
|
|
536
|
+
const output = document.getElementById('log-output');
|
|
537
|
+
const scrollContainer = document.getElementById('log-container');
|
|
538
|
+
if (!output || !scrollContainer) return;
|
|
539
|
+
|
|
540
|
+
const showAll = document.getElementById('filter-all').checked;
|
|
541
|
+
const showNeuro = document.getElementById('filter-neuro').checked;
|
|
542
|
+
const showTraining = document.getElementById('filter-training').checked;
|
|
543
|
+
const showError = document.getElementById('filter-error').checked;
|
|
544
|
+
|
|
545
|
+
// Check if user is at the bottom BEFORE updating (within 100px tolerance)
|
|
546
|
+
// Also check if scrollHeight is equal to clientHeight (no scrollbar yet)
|
|
547
|
+
const isAtBottom = (scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight) < 100 ||
|
|
548
|
+
(scrollContainer.scrollHeight <= scrollContainer.clientHeight);
|
|
549
|
+
|
|
550
|
+
// Sort logs by epoch timestamp (ascending - oldest first)
|
|
551
|
+
const sortedLogs = [...logs].sort((a, b) => a.epoch - b.epoch);
|
|
552
|
+
|
|
553
|
+
let html = '';
|
|
554
|
+
sortedLogs.forEach(log => {
|
|
555
|
+
let show = showAll;
|
|
556
|
+
if (!showAll) {
|
|
557
|
+
if (log.type === 'neuro' && showNeuro) show = true;
|
|
558
|
+
if (log.type === 'training' && showTraining) show = true;
|
|
559
|
+
if (log.type === 'error' && showError) show = true;
|
|
560
|
+
if (log.type === 'info') show = true;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (show) {
|
|
564
|
+
html += `<p class="log-${log.type}"><span class="log-timestamp">[${log.timestamp}]</span> ${escapeHtml(log.message)}</p>`;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
output.innerHTML = html || '<p class="text-dark-500">No logs to display</p>';
|
|
569
|
+
|
|
570
|
+
// Auto-scroll to bottom of the scrollable container
|
|
571
|
+
if (isFirstLoad || isAtBottom) {
|
|
572
|
+
requestAnimationFrame(() => {
|
|
573
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
574
|
+
});
|
|
575
|
+
if (isFirstLoad && logs.length > 0) isFirstLoad = false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function escapeHtml(text) {
|
|
580
|
+
const div = document.createElement('div');
|
|
581
|
+
div.textContent = text;
|
|
582
|
+
return div.innerHTML;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function applyLogFilter() {
|
|
586
|
+
renderLogs();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function clearLogs() {
|
|
590
|
+
logs = [];
|
|
591
|
+
// Don't reset lastLogId - we still want to poll only new logs from backend
|
|
592
|
+
addLog('Logs cleared', 'info');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Resource limit updates
|
|
596
|
+
function updateCpuLimit(value) {
|
|
597
|
+
document.getElementById('cpu-limit-value').textContent = value;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function updateMemLimit(value) {
|
|
601
|
+
document.getElementById('mem-limit-value').textContent = value + ' MB';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function updateStorageLimit(value) {
|
|
605
|
+
document.getElementById('storage-limit-value').textContent = value + ' MB';
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function applyThrottle() {
|
|
609
|
+
const cpu = document.getElementById('cpu-limit-slider').value;
|
|
610
|
+
const mem = document.getElementById('mem-limit-slider').value;
|
|
611
|
+
const storage = document.getElementById('storage-limit-slider').value;
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
const resp = await fetch('/api/throttle', {
|
|
615
|
+
method: 'POST',
|
|
616
|
+
headers: { 'Content-Type': 'application/json' },
|
|
617
|
+
body: JSON.stringify({
|
|
618
|
+
cpu_threads: parseInt(cpu),
|
|
619
|
+
memory_mb: parseInt(mem),
|
|
620
|
+
storage_mb: parseInt(storage)
|
|
621
|
+
})
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
if (resp.ok) {
|
|
625
|
+
addLog(`Settings updated: ${cpu} threads, ${mem}MB memory, ${storage}MB storage`, 'success');
|
|
626
|
+
} else {
|
|
627
|
+
addLog('Failed to update settings', 'error');
|
|
628
|
+
}
|
|
629
|
+
} catch (e) {
|
|
630
|
+
addLog('Error updating settings: ' + e.message, 'error');
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Stop node gracefully
|
|
635
|
+
async function stopNode() {
|
|
636
|
+
if (!confirm('Are you sure you want to stop the node? Your checkpoint will be saved.')) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
addLog('Stopping node...', 'warning');
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const resp = await fetch('/api/shutdown', {
|
|
644
|
+
method: 'POST',
|
|
645
|
+
headers: { 'Content-Type': 'application/json' }
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
if (resp.ok) {
|
|
649
|
+
const data = await resp.json();
|
|
650
|
+
addLog(data.message, 'success');
|
|
651
|
+
|
|
652
|
+
// Update UI to show stopping state
|
|
653
|
+
setStatus('STOPPING', 'yellow', 'Saving checkpoint...');
|
|
654
|
+
document.getElementById('stop-btn').disabled = true;
|
|
655
|
+
document.getElementById('stop-btn').classList.add('opacity-50', 'cursor-not-allowed');
|
|
656
|
+
|
|
657
|
+
// Show message that browser tab can be closed
|
|
658
|
+
setTimeout(() => {
|
|
659
|
+
setStatus('STOPPED', 'red', 'Node stopped. You can close this tab.');
|
|
660
|
+
}, 2000);
|
|
661
|
+
} else {
|
|
662
|
+
addLog('Failed to stop node', 'error');
|
|
663
|
+
}
|
|
664
|
+
} catch (e) {
|
|
665
|
+
addLog('Error stopping node: ' + e.message, 'error');
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Status helpers
|
|
670
|
+
function setStatus(status, color, detail) {
|
|
671
|
+
const dot = document.getElementById('status-dot');
|
|
672
|
+
const text = document.getElementById('status-text');
|
|
673
|
+
const detailEl = document.getElementById('status-detail');
|
|
674
|
+
|
|
675
|
+
dot.className = 'w-2.5 h-2.5 rounded-full';
|
|
676
|
+
|
|
677
|
+
if (color === 'green') {
|
|
678
|
+
dot.classList.add('bg-green-500', 'pulse-glow');
|
|
679
|
+
text.classList.remove('text-dark-400', 'text-yellow-500', 'text-red-500');
|
|
680
|
+
text.classList.add('text-green-500');
|
|
681
|
+
} else if (color === 'yellow') {
|
|
682
|
+
dot.classList.add('bg-yellow-500');
|
|
683
|
+
text.classList.remove('text-dark-400', 'text-green-500', 'text-red-500');
|
|
684
|
+
text.classList.add('text-yellow-500');
|
|
685
|
+
} else if (color === 'red') {
|
|
686
|
+
dot.classList.add('bg-red-500');
|
|
687
|
+
text.classList.remove('text-dark-400', 'text-green-500', 'text-yellow-500');
|
|
688
|
+
text.classList.add('text-red-500');
|
|
689
|
+
} else {
|
|
690
|
+
dot.classList.add('bg-dark-500');
|
|
691
|
+
text.classList.remove('text-green-500', 'text-yellow-500', 'text-red-500');
|
|
692
|
+
text.classList.add('text-dark-400');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
text.textContent = status;
|
|
696
|
+
detailEl.textContent = detail;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function setProgressBarColor(bar, percent) {
|
|
700
|
+
bar.classList.remove('bg-neuro-500', 'bg-yellow-500', 'bg-red-500');
|
|
701
|
+
if (percent > 85) {
|
|
702
|
+
bar.classList.add('bg-red-500');
|
|
703
|
+
} else if (percent > 70) {
|
|
704
|
+
bar.classList.add('bg-yellow-500');
|
|
705
|
+
} else {
|
|
706
|
+
bar.classList.add('bg-neuro-500');
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Format numbers
|
|
711
|
+
function formatNumber(num) {
|
|
712
|
+
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
|
713
|
+
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
|
|
714
|
+
return num.toString();
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Track connection state
|
|
718
|
+
let isConnected = false;
|
|
719
|
+
let lastTotalEarned = 0;
|
|
720
|
+
|
|
721
|
+
// Fetch and update stats
|
|
722
|
+
async function updateStats() {
|
|
723
|
+
try {
|
|
724
|
+
const resp = await fetch('/api/stats');
|
|
725
|
+
if (!resp.ok) throw new Error('Stats unavailable');
|
|
726
|
+
|
|
727
|
+
const data = await resp.json();
|
|
728
|
+
|
|
729
|
+
// Log first successful connection
|
|
730
|
+
if (!isConnected) {
|
|
731
|
+
isConnected = true;
|
|
732
|
+
addLog('Connected to node successfully', 'success');
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Update status
|
|
736
|
+
const training = data.training_enabled;
|
|
737
|
+
const status = data.training_status || 'idle';
|
|
738
|
+
|
|
739
|
+
if (status === 'running') {
|
|
740
|
+
setStatus('TRAINING', 'green', `${data.my_layers?.length || 0} layers • ${(data.my_params_m || 0).toFixed(0)}M params`);
|
|
741
|
+
} else if (training) {
|
|
742
|
+
setStatus('ONLINE', 'green', `${data.my_layers?.length || 0} layers • Ready to train`);
|
|
743
|
+
} else {
|
|
744
|
+
setStatus('INFERENCE', 'yellow', 'Training disabled');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Update tracker status
|
|
748
|
+
document.getElementById('tracker-status').textContent = 'Connected';
|
|
749
|
+
document.getElementById('tracker-status').className = 'text-green-500';
|
|
750
|
+
|
|
751
|
+
// Update version
|
|
752
|
+
if (data.version) {
|
|
753
|
+
document.getElementById('version').textContent = 'v' + data.version;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Update stats
|
|
757
|
+
document.getElementById('stat-role').textContent = data.role || 'Worker';
|
|
758
|
+
document.getElementById('stat-layers').textContent = data.my_layers?.length || 0;
|
|
759
|
+
document.getElementById('stat-params').textContent = (data.my_params_m || 0).toFixed(0) + 'M';
|
|
760
|
+
document.getElementById('stat-device').textContent = (data.device || 'cpu').toUpperCase();
|
|
761
|
+
|
|
762
|
+
// Device color
|
|
763
|
+
const deviceEl = document.getElementById('stat-device');
|
|
764
|
+
if (data.device === 'cuda' || data.device === 'mps') {
|
|
765
|
+
deviceEl.classList.add('text-green-500');
|
|
766
|
+
} else {
|
|
767
|
+
deviceEl.classList.remove('text-green-500');
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Training stats
|
|
771
|
+
document.getElementById('stat-tokens').textContent = formatNumber(data.token_count || 0);
|
|
772
|
+
document.getElementById('stat-training').textContent = data.training_rounds || 0;
|
|
773
|
+
|
|
774
|
+
// Update settings page values
|
|
775
|
+
const settingsLayers = document.getElementById('setting-layers');
|
|
776
|
+
const settingsPeers = document.getElementById('setting-peers');
|
|
777
|
+
const settingsTraining = document.getElementById('setting-training');
|
|
778
|
+
const settingsDevice = document.getElementById('setting-device');
|
|
779
|
+
|
|
780
|
+
if (settingsLayers) settingsLayers.textContent = data.my_layers?.length || 0;
|
|
781
|
+
if (settingsPeers) settingsPeers.textContent = data.peer_count || 0;
|
|
782
|
+
if (settingsTraining) {
|
|
783
|
+
settingsTraining.textContent = training ? 'Enabled' : 'Disabled';
|
|
784
|
+
settingsTraining.className = training ? 'text-lg font-semibold mt-1 text-green-500' : 'text-lg font-semibold mt-1 text-dark-400';
|
|
785
|
+
}
|
|
786
|
+
if (settingsDevice) {
|
|
787
|
+
const dev = (data.device || 'cpu').toUpperCase();
|
|
788
|
+
settingsDevice.textContent = dev;
|
|
789
|
+
settingsDevice.className = (data.device === 'cuda' || data.device === 'mps')
|
|
790
|
+
? 'text-lg font-semibold mt-1 text-green-500'
|
|
791
|
+
: 'text-lg font-semibold mt-1 text-dark-400';
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Update instance ID in dashboard
|
|
795
|
+
if (data.instance_id) {
|
|
796
|
+
const instanceEl = document.getElementById('instance-id');
|
|
797
|
+
if (instanceEl) instanceEl.textContent = data.instance_id.substring(0, 12) + '...';
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const loss = data.current_loss;
|
|
801
|
+
document.getElementById('stat-loss').textContent = loss && loss < Infinity ? loss.toFixed(4) : '∞';
|
|
802
|
+
|
|
803
|
+
// DiLoCo progress
|
|
804
|
+
if (data.diloco) {
|
|
805
|
+
document.getElementById('diloco-section').style.display = 'block';
|
|
806
|
+
const inner = data.diloco.inner_step || 0;
|
|
807
|
+
const total = data.diloco.inner_total || 500;
|
|
808
|
+
const progress = (inner / total * 100).toFixed(0);
|
|
809
|
+
document.getElementById('diloco-progress').textContent = `${inner}/${total}`;
|
|
810
|
+
document.getElementById('diloco-bar').style.width = progress + '%';
|
|
811
|
+
document.getElementById('diloco-outer').textContent = data.diloco.outer_step || 0;
|
|
812
|
+
} else {
|
|
813
|
+
document.getElementById('diloco-section').style.display = 'none';
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Initialize sliders with actual config values (once)
|
|
817
|
+
if (data.config && !window.slidersInitialized) {
|
|
818
|
+
window.slidersInitialized = true;
|
|
819
|
+
|
|
820
|
+
// CPU threads
|
|
821
|
+
if (data.config.cpu_threads) {
|
|
822
|
+
const cpuSlider = document.getElementById('cpu-limit-slider');
|
|
823
|
+
if (cpuSlider) {
|
|
824
|
+
cpuSlider.value = data.config.cpu_threads;
|
|
825
|
+
document.getElementById('cpu-limit-value').textContent = data.config.cpu_threads;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Memory
|
|
830
|
+
if (data.config.memory_mb) {
|
|
831
|
+
const memSlider = document.getElementById('mem-limit-slider');
|
|
832
|
+
if (memSlider) {
|
|
833
|
+
memSlider.value = data.config.memory_mb;
|
|
834
|
+
document.getElementById('mem-limit-value').textContent = data.config.memory_mb + ' MB';
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Storage
|
|
839
|
+
if (data.config.storage_mb) {
|
|
840
|
+
const storageSlider = document.getElementById('storage-limit-slider');
|
|
841
|
+
if (storageSlider) {
|
|
842
|
+
storageSlider.value = data.config.storage_mb;
|
|
843
|
+
document.getElementById('storage-limit-value').textContent = data.config.storage_mb + ' MB';
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
addLog(`Config loaded: CPU=${data.config.cpu_threads || 'auto'}, Memory=${data.config.memory_mb || 'auto'}MB, Storage=${data.config.storage_mb || 100}MB`, 'info');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
} catch (e) {
|
|
851
|
+
setStatus('OFFLINE', 'red', 'Cannot connect to node');
|
|
852
|
+
document.getElementById('tracker-status').textContent = 'Disconnected';
|
|
853
|
+
document.getElementById('tracker-status').className = 'text-dark-500';
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Track peer count for balance warning (-1 = not yet fetched)
|
|
858
|
+
let lastKnownPeerCount = -1;
|
|
859
|
+
|
|
860
|
+
// Fetch balance
|
|
861
|
+
async function updateBalance() {
|
|
862
|
+
try {
|
|
863
|
+
const resp = await fetch('/api/neuro');
|
|
864
|
+
if (!resp.ok) return;
|
|
865
|
+
|
|
866
|
+
const data = await resp.json();
|
|
867
|
+
|
|
868
|
+
const totalEarned = data.total_earned || 0;
|
|
869
|
+
const balance = data.balance || 0;
|
|
870
|
+
|
|
871
|
+
document.getElementById('balance').textContent = balance.toFixed(4);
|
|
872
|
+
document.getElementById('total-earned').textContent = totalEarned.toFixed(4);
|
|
873
|
+
document.getElementById('stat-earned').textContent = totalEarned.toFixed(4);
|
|
874
|
+
|
|
875
|
+
// Show warning if earning while solo (proofs won't be network-confirmed)
|
|
876
|
+
// Only show if we've actually fetched peer count (lastKnownPeerCount >= 0)
|
|
877
|
+
const balanceNote = document.getElementById('balance-note');
|
|
878
|
+
if (balanceNote) {
|
|
879
|
+
// Show warning if we have balance but no/few peers AND we've checked
|
|
880
|
+
if (totalEarned > 0 && lastKnownPeerCount === 0) {
|
|
881
|
+
balanceNote.classList.remove('hidden');
|
|
882
|
+
} else {
|
|
883
|
+
balanceNote.classList.add('hidden');
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Detect new earnings and log them
|
|
888
|
+
if (lastTotalEarned > 0 && totalEarned > lastTotalEarned) {
|
|
889
|
+
const earned = totalEarned - lastTotalEarned;
|
|
890
|
+
addLog(`Earned +${earned.toFixed(6)} NEURO (PoNW reward)`, 'neuro');
|
|
891
|
+
|
|
892
|
+
// Only warn if we've confirmed no peers (not just haven't checked yet)
|
|
893
|
+
if (lastKnownPeerCount === 0) {
|
|
894
|
+
addLog('⚠ Earning while solo - proofs need peers to be network-confirmed', 'warning');
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
lastTotalEarned = totalEarned;
|
|
898
|
+
|
|
899
|
+
// Update wallet and node IDs
|
|
900
|
+
if (data.wallet_id) {
|
|
901
|
+
document.getElementById('wallet-id').textContent = data.wallet_id.substring(0, 16) + '...';
|
|
902
|
+
// Also update settings page
|
|
903
|
+
const settingWallet = document.getElementById('setting-wallet-id');
|
|
904
|
+
if (settingWallet) settingWallet.textContent = data.wallet_id.substring(0, 24) + '...';
|
|
905
|
+
}
|
|
906
|
+
if (data.node_id) {
|
|
907
|
+
nodeId = data.node_id;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
} catch (e) {
|
|
911
|
+
// Silent fail
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Fetch swarm status
|
|
916
|
+
async function updateSwarm() {
|
|
917
|
+
try {
|
|
918
|
+
const resp = await fetch('/api/swarm');
|
|
919
|
+
if (!resp.ok) return;
|
|
920
|
+
|
|
921
|
+
const data = await resp.json();
|
|
922
|
+
|
|
923
|
+
document.getElementById('swarm-inbound').textContent = ((data.inbound_fill_rate || 0) * 100).toFixed(0) + '%';
|
|
924
|
+
document.getElementById('swarm-outbound').textContent = ((data.outbound_fill_rate || 0) * 100).toFixed(0) + '%';
|
|
925
|
+
document.getElementById('swarm-peers').textContent = data.known_peers || 0;
|
|
926
|
+
|
|
927
|
+
// Track peer count for balance warning
|
|
928
|
+
lastKnownPeerCount = data.known_peers || 0;
|
|
929
|
+
|
|
930
|
+
} catch (e) {
|
|
931
|
+
// Silent fail
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Fetch market data for inference price
|
|
936
|
+
async function updateMarket() {
|
|
937
|
+
try {
|
|
938
|
+
const resp = await fetch('/api/market');
|
|
939
|
+
if (!resp.ok) return;
|
|
940
|
+
|
|
941
|
+
const data = await resp.json();
|
|
942
|
+
|
|
943
|
+
// Update inference price
|
|
944
|
+
const priceEl = document.getElementById('inference-price');
|
|
945
|
+
if (priceEl && data.current_price !== undefined) {
|
|
946
|
+
const price = data.current_price;
|
|
947
|
+
priceEl.textContent = price.toFixed(6) + ' NEURO/tok';
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
} catch (e) {
|
|
951
|
+
// Silent fail - market may not be available
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Fetch system resources
|
|
956
|
+
async function updateSystem() {
|
|
957
|
+
try {
|
|
958
|
+
const resp = await fetch('/api/stats');
|
|
959
|
+
if (!resp.ok) return;
|
|
960
|
+
|
|
961
|
+
const data = await resp.json();
|
|
962
|
+
const system = data.system || {};
|
|
963
|
+
|
|
964
|
+
// CPU - actual system usage
|
|
965
|
+
const cpuPercent = Math.round(system.cpu_percent || 0);
|
|
966
|
+
document.getElementById('cpu-percent').textContent = cpuPercent + '%';
|
|
967
|
+
document.getElementById('cpu-bar').style.width = cpuPercent + '%';
|
|
968
|
+
setProgressBarColor(document.getElementById('cpu-bar'), cpuPercent);
|
|
969
|
+
|
|
970
|
+
// RAM - actual usage with total
|
|
971
|
+
const ramUsed = system.ram_used_gb || 0;
|
|
972
|
+
const ramTotal = system.ram_total_gb || 0;
|
|
973
|
+
const ramPercent = system.ram_percent || 0;
|
|
974
|
+
document.getElementById('ram-percent').textContent = `${ramUsed.toFixed(1)} / ${ramTotal.toFixed(1)} GB`;
|
|
975
|
+
|
|
976
|
+
// Update RAM progress bar
|
|
977
|
+
const ramBar = document.getElementById('ram-bar');
|
|
978
|
+
if (ramBar) {
|
|
979
|
+
ramBar.style.width = ramPercent + '%';
|
|
980
|
+
setProgressBarColor(ramBar, ramPercent);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
} catch (e) {
|
|
984
|
+
// Silent fail
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Fetch backend logs
|
|
989
|
+
async function updateLogs() {
|
|
990
|
+
try {
|
|
991
|
+
const resp = await fetch(`/api/logs?since_id=${lastLogId}&limit=100`);
|
|
992
|
+
if (!resp.ok) return;
|
|
993
|
+
|
|
994
|
+
const data = await resp.json();
|
|
995
|
+
|
|
996
|
+
// Add new logs from backend
|
|
997
|
+
if (data.logs && data.logs.length > 0) {
|
|
998
|
+
data.logs.forEach(log => {
|
|
999
|
+
// Avoid duplicates by checking log ID
|
|
1000
|
+
const isDuplicate = logs.some(existing => existing.id === log.id);
|
|
1001
|
+
|
|
1002
|
+
if (!isDuplicate) {
|
|
1003
|
+
logs.push({
|
|
1004
|
+
id: log.id,
|
|
1005
|
+
epoch: log.epoch,
|
|
1006
|
+
timestamp: log.timestamp,
|
|
1007
|
+
message: log.message,
|
|
1008
|
+
type: log.type || 'info'
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// Keep last 500 logs
|
|
1014
|
+
if (logs.length > 500) logs = logs.slice(-300);
|
|
1015
|
+
|
|
1016
|
+
renderLogs();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Update ID for next poll
|
|
1020
|
+
lastLogId = data.latest_id || lastLogId;
|
|
1021
|
+
|
|
1022
|
+
} catch (e) {
|
|
1023
|
+
// Silent fail
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Initial setup
|
|
1028
|
+
addLog('Dashboard loaded', 'info');
|
|
1029
|
+
addLog('Connecting to node...', 'info');
|
|
1030
|
+
|
|
1031
|
+
// Start update loops
|
|
1032
|
+
updateStats();
|
|
1033
|
+
updateBalance();
|
|
1034
|
+
updateSwarm();
|
|
1035
|
+
updateSystem();
|
|
1036
|
+
updateMarket(); // Fetch market prices
|
|
1037
|
+
updateLogs(); // Fetch initial logs
|
|
1038
|
+
|
|
1039
|
+
setInterval(updateStats, 3000);
|
|
1040
|
+
setInterval(updateBalance, 5000); // Check for new earnings every 5s
|
|
1041
|
+
setInterval(updateSwarm, 5000);
|
|
1042
|
+
setInterval(updateSystem, 5000);
|
|
1043
|
+
setInterval(updateMarket, 10000); // Market prices every 10s
|
|
1044
|
+
setInterval(updateLogs, 2000); // Poll logs every 2s
|
|
1045
|
+
|
|
1046
|
+
// Get current URL port for settings
|
|
1047
|
+
const port = window.location.port || '8000';
|
|
1048
|
+
const settingPort = document.getElementById('setting-port');
|
|
1049
|
+
if (settingPort) settingPort.textContent = port;
|
|
1050
|
+
</script>
|
|
1051
|
+
</body>
|
|
1052
|
+
</html>
|