infinityonline commited on
Commit
4355a9a
ยท
verified ยท
1 Parent(s): 86a6197

Update dashboard.html

Browse files
Files changed (1) hide show
  1. dashboard.html +170 -34
dashboard.html CHANGED
@@ -37,10 +37,72 @@
37
  .refresh-badge { text-align: center; color: #555; font-size: 0.75rem; margin-top: 10px; }
38
  .chart-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
39
  canvas { background: #141720; border-radius: 8px; border: 1px solid #2a2d3e; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  </style>
41
  </head>
42
  <body>
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  <h1>๐Ÿฆ† Duck.ai API โ€” ู„ูˆุญุฉ ุงู„ู…ุฑุงู‚ุจุฉ</h1>
45
 
46
  <!-- KPI Cards -->
@@ -114,44 +176,110 @@
114
  <div class="log-box" id="log_box"></div>
115
  </div>
116
 
117
- <div class="refresh-badge">ูŠุชุฌุฏุฏ ูƒู„ <span id="interval_val">5</span> ุซูˆุงู† โ€ข ุขุฎุฑ ุชุญุฏูŠุซ: <span id="last_update">โ€”</span></div>
 
 
 
118
 
119
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  const REFRESH = 5000;
121
  let reqHistory = Array(60).fill(0);
122
  let ramHistory = Array(60).fill(0);
123
- let logs = [];
124
- let lastTotal = 0;
 
125
 
126
- const todayKey = () => new Date().toISOString().slice(0,10);
127
- let todayCount = parseInt(localStorage.getItem('today_' + todayKey()) || '0');
128
- let lastDay = localStorage.getItem('last_day') || todayKey();
129
 
130
  async function fetchData() {
 
131
  try {
132
- const r = await fetch('/health');
 
 
 
 
 
 
 
 
 
 
133
  const d = await r.json();
 
134
 
135
- // KPIs
136
- document.getElementById('total_req').textContent = d.total_requests ?? 'โ€”';
137
  document.getElementById('rejected').textContent = d.rejected_requests ?? 0;
138
- document.getElementById('workers_free').textContent = d.workers_free ?? 'โ€”';
139
- document.getElementById('workers_busy').textContent = d.workers_busy ?? 'โ€”';
140
 
141
  if (d.ram) {
142
- document.getElementById('ram_used').textContent = d.ram.used_gb + ' GB';
143
  document.getElementById('ram_total').textContent = d.ram.total_gb + ' GB';
144
  }
145
  if (d.cpu !== undefined) {
146
  document.getElementById('cpu').textContent = d.cpu + '%';
147
  }
148
 
149
- // Today counter
150
  const diff = (d.total_requests ?? 0) - lastTotal;
151
  if (diff > 0) {
152
  if (lastDay !== todayKey()) {
153
  todayCount = 0;
154
- lastDay = todayKey();
155
  localStorage.setItem('last_day', lastDay);
156
  }
157
  todayCount += diff;
@@ -160,11 +288,13 @@ async function fetchData() {
160
  }
161
  document.getElementById('today_req').textContent = todayCount;
162
 
163
- // Workers table
164
  const tbody = document.getElementById('workers_table');
165
  tbody.innerHTML = '';
166
  (d.workers || []).forEach(w => {
167
- const pct = Math.round(((30 - w.requests_until_rotation) / 30) * 100);
 
 
168
  const warn = pct > 80 ? 'warn' : '';
169
  tbody.innerHTML += `
170
  <tr>
@@ -173,29 +303,34 @@ async function fetchData() {
173
  <td>${w.total_requests}</td>
174
  <td>${w.requests_until_rotation}</td>
175
  <td style="width:160px">
176
- <div class="bar-wrap"><div class="bar ${warn}" style="width:${pct}%"></div></div>
 
 
177
  </td>
178
  </tr>`;
179
  });
180
 
181
- // History
182
  reqHistory.push(diff > 0 ? diff : 0); reqHistory.shift();
183
  if (d.ram) { ramHistory.push(d.ram.percent); ramHistory.shift(); }
184
 
185
  drawChart('reqChart', reqHistory, '#4fc3f7');
186
  drawChart('ramChart', ramHistory, '#ce93d8', 100);
187
 
188
- // Log
189
- addLog(`โœ“ total=${d.total_requests} | free=${d.workers_free} | busy=${d.workers_busy}`, 'ok');
190
-
191
- document.getElementById('last_update').textContent = new Date().toLocaleTimeString('ar-EG');
 
 
 
192
 
193
- } catch(e) {
194
  addLog('โŒ ูุดู„ ุงู„ุงุชุตุงู„: ' + e.message, 'err');
195
  }
196
  }
197
 
198
- function addLog(msg, cls='info') {
199
  const now = new Date().toLocaleTimeString('ar-EG');
200
  logs.unshift(`<span class="${cls}">[${now}] ${msg}</span>`);
201
  if (logs.length > 80) logs.pop();
@@ -204,34 +339,35 @@ function addLog(msg, cls='info') {
204
 
205
  function drawChart(id, data, color, maxVal = null) {
206
  const canvas = document.getElementById(id);
207
- const ctx = canvas.getContext('2d');
208
- canvas.width = canvas.offsetWidth;
209
  canvas.height = 120;
210
  const W = canvas.width, H = canvas.height;
211
- const max = maxVal ?? (Math.max(...data, 1));
212
- ctx.clearRect(0,0,W,H);
213
 
214
- // grid lines
215
  ctx.strokeStyle = '#2a2d3e'; ctx.lineWidth = 1;
216
  [0.25, 0.5, 0.75].forEach(f => {
217
- ctx.beginPath(); ctx.moveTo(0, H*f); ctx.lineTo(W, H*f); ctx.stroke();
218
  });
219
 
220
- // area
221
  const step = W / (data.length - 1);
222
  ctx.beginPath();
223
  data.forEach((v, i) => {
224
- const x = i * step, y = H - (v / max) * (H - 10) - 5;
 
225
  i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
226
  });
227
  ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke();
228
-
229
  ctx.lineTo(W, H); ctx.lineTo(0, H); ctx.closePath();
230
  ctx.fillStyle = color + '22'; ctx.fill();
231
  }
232
 
 
 
 
233
  document.getElementById('interval_val').textContent = REFRESH / 1000;
234
- fetchData();
235
  setInterval(fetchData, REFRESH);
236
  </script>
237
  </body>
 
37
  .refresh-badge { text-align: center; color: #555; font-size: 0.75rem; margin-top: 10px; }
38
  .chart-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
39
  canvas { background: #141720; border-radius: 8px; border: 1px solid #2a2d3e; }
40
+
41
+ /* โ”€โ”€ Login Overlay โ”€โ”€ */
42
+ #login-overlay {
43
+ display: none;
44
+ position: fixed; inset: 0;
45
+ background: #0f1117ee;
46
+ z-index: 999;
47
+ align-items: center;
48
+ justify-content: center;
49
+ }
50
+ #login-overlay.show { display: flex; }
51
+ .login-box {
52
+ background: #1e2130;
53
+ border: 1px solid #2a2d3e;
54
+ border-radius: 16px;
55
+ padding: 40px;
56
+ width: 360px;
57
+ text-align: center;
58
+ }
59
+ .login-box h2 { color: #4fc3f7; margin-bottom: 8px; font-size: 1.3rem; }
60
+ .login-box p { color: #888; font-size: 0.85rem; margin-bottom: 24px; }
61
+ .login-box input {
62
+ width: 100%; padding: 12px 16px;
63
+ background: #0d1117; border: 1px solid #2a2d3e;
64
+ border-radius: 8px; color: #e0e0e0;
65
+ font-size: 0.95rem; margin-bottom: 16px;
66
+ outline: none; direction: ltr;
67
+ }
68
+ .login-box input:focus { border-color: #4fc3f7; }
69
+ .login-box button {
70
+ width: 100%; padding: 12px;
71
+ background: linear-gradient(90deg, #4fc3f7, #69f0ae);
72
+ border: none; border-radius: 8px;
73
+ color: #0f1117; font-weight: bold;
74
+ font-size: 1rem; cursor: pointer;
75
+ }
76
+ .login-box button:hover { opacity: 0.9; }
77
+ .login-err { color: #ef5350; font-size: 0.82rem; margin-top: 10px; display: none; }
78
+
79
+ /* โ”€โ”€ Logout btn โ”€โ”€ */
80
+ #logout-btn {
81
+ position: fixed; top: 16px; left: 16px;
82
+ background: #1e2130; border: 1px solid #2a2d3e;
83
+ color: #888; padding: 6px 14px;
84
+ border-radius: 8px; cursor: pointer;
85
+ font-size: 0.8rem; z-index: 100;
86
+ }
87
+ #logout-btn:hover { color: #ef5350; border-color: #ef535050; }
88
  </style>
89
  </head>
90
  <body>
91
 
92
+ <!-- Login Overlay -->
93
+ <div id="login-overlay" class="show">
94
+ <div class="login-box">
95
+ <h2>๐Ÿฆ† Duck.ai Dashboard</h2>
96
+ <p>ุฃุฏุฎู„ API Key ู„ู„ุฏุฎูˆู„</p>
97
+ <input type="password" id="key-input" placeholder="API Key..." />
98
+ <button onclick="doLogin()">ุฏุฎูˆู„ โ†</button>
99
+ <div class="login-err" id="login-err">โŒ ู…ูุชุงุญ ุฎุงุทุฆุŒ ุญุงูˆู„ ู…ุฌุฏุฏุงู‹</div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Logout -->
104
+ <button id="logout-btn" onclick="doLogout()">๐Ÿ”“ ุฎุฑูˆุฌ</button>
105
+
106
  <h1>๐Ÿฆ† Duck.ai API โ€” ู„ูˆุญุฉ ุงู„ู…ุฑุงู‚ุจุฉ</h1>
107
 
108
  <!-- KPI Cards -->
 
176
  <div class="log-box" id="log_box"></div>
177
  </div>
178
 
179
+ <div class="refresh-badge">
180
+ ูŠุชุฌุฏุฏ ูƒู„ <span id="interval_val">5</span> ุซูˆุงู† โ€ข
181
+ ุขุฎุฑ ุชุญุฏูŠุซ: <span id="last_update">โ€”</span>
182
+ </div>
183
 
184
  <script>
185
+ // ====================================================================
186
+ // Auth
187
+ // ====================================================================
188
+ const urlToken = new URLSearchParams(window.location.search).get('token');
189
+ if (urlToken) {
190
+ localStorage.setItem('duck_token', urlToken);
191
+ // ุฃุฒู„ token ู…ู† ุงู„ู€ URL ุจุนุฏ ุญูุธู‡
192
+ window.history.replaceState({}, '', window.location.pathname);
193
+ }
194
+
195
+ let TOKEN = localStorage.getItem('duck_token') || '';
196
+
197
+ function doLogin() {
198
+ const val = document.getElementById('key-input').value.trim();
199
+ if (!val) return;
200
+ TOKEN = val;
201
+ localStorage.setItem('duck_token', TOKEN);
202
+ document.getElementById('login-err').style.display = 'none';
203
+ fetchData(); // ุฌุฑุจ ู…ุจุงุดุฑุฉ
204
+ }
205
+
206
+ function doLogout() {
207
+ localStorage.removeItem('duck_token');
208
+ TOKEN = '';
209
+ document.getElementById('login-overlay').classList.add('show');
210
+ document.getElementById('key-input').value = '';
211
+ }
212
+
213
+ function showLogin(msg) {
214
+ document.getElementById('login-overlay').classList.add('show');
215
+ if (msg) {
216
+ const err = document.getElementById('login-err');
217
+ err.textContent = msg;
218
+ err.style.display = 'block';
219
+ }
220
+ }
221
+
222
+ // Enter key ููŠ input
223
+ document.getElementById('key-input').addEventListener('keydown', e => {
224
+ if (e.key === 'Enter') doLogin();
225
+ });
226
+
227
+ // ุฅุฐุง ุนู†ุฏู‡ token ู…ุญููˆุธ ุงุจุฏุฃ ู…ุจุงุดุฑุฉ
228
+ if (TOKEN) {
229
+ document.getElementById('login-overlay').classList.remove('show');
230
+ }
231
+
232
+ // ====================================================================
233
+ // Data & Charts
234
+ // ====================================================================
235
  const REFRESH = 5000;
236
  let reqHistory = Array(60).fill(0);
237
  let ramHistory = Array(60).fill(0);
238
+ let logs = [];
239
+ let lastTotal = 0;
240
+ let fetchTimer = null;
241
 
242
+ const todayKey = () => new Date().toISOString().slice(0, 10);
243
+ let todayCount = parseInt(localStorage.getItem('today_' + todayKey()) || '0');
244
+ let lastDay = localStorage.getItem('last_day') || todayKey();
245
 
246
  async function fetchData() {
247
+ if (!TOKEN) return;
248
  try {
249
+ const r = await fetch('/health', {
250
+ headers: { 'Authorization': 'Bearer ' + TOKEN }
251
+ });
252
+
253
+ if (r.status === 401) {
254
+ showLogin('โŒ ู…ูุชุงุญ ุฎุงุทุฆุŒ ุญุงูˆู„ ู…ุฌุฏุฏุงู‹');
255
+ TOKEN = '';
256
+ localStorage.removeItem('duck_token');
257
+ return;
258
+ }
259
+
260
  const d = await r.json();
261
+ document.getElementById('login-overlay').classList.remove('show');
262
 
263
+ // โ”€โ”€ KPIs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
264
+ document.getElementById('total_req').textContent = d.total_requests ?? 'โ€”';
265
  document.getElementById('rejected').textContent = d.rejected_requests ?? 0;
266
+ document.getElementById('workers_free').textContent = d.workers_free ?? 'โ€”';
267
+ document.getElementById('workers_busy').textContent = d.workers_busy ?? 'โ€”';
268
 
269
  if (d.ram) {
270
+ document.getElementById('ram_used').textContent = d.ram.used_gb + ' GB';
271
  document.getElementById('ram_total').textContent = d.ram.total_gb + ' GB';
272
  }
273
  if (d.cpu !== undefined) {
274
  document.getElementById('cpu').textContent = d.cpu + '%';
275
  }
276
 
277
+ // โ”€โ”€ Today counter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
278
  const diff = (d.total_requests ?? 0) - lastTotal;
279
  if (diff > 0) {
280
  if (lastDay !== todayKey()) {
281
  todayCount = 0;
282
+ lastDay = todayKey();
283
  localStorage.setItem('last_day', lastDay);
284
  }
285
  todayCount += diff;
 
288
  }
289
  document.getElementById('today_req').textContent = todayCount;
290
 
291
+ // โ”€โ”€ Workers table โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
292
  const tbody = document.getElementById('workers_table');
293
  tbody.innerHTML = '';
294
  (d.workers || []).forEach(w => {
295
+ const used = (parseInt(d.pool_size) || 30) - w.requests_until_rotation;
296
+ const max = parseInt(d.pool_size) || 30;
297
+ const pct = Math.round((used / max) * 100);
298
  const warn = pct > 80 ? 'warn' : '';
299
  tbody.innerHTML += `
300
  <tr>
 
303
  <td>${w.total_requests}</td>
304
  <td>${w.requests_until_rotation}</td>
305
  <td style="width:160px">
306
+ <div class="bar-wrap">
307
+ <div class="bar ${warn}" style="width:${pct}%"></div>
308
+ </div>
309
  </td>
310
  </tr>`;
311
  });
312
 
313
+ // โ”€โ”€ History charts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
314
  reqHistory.push(diff > 0 ? diff : 0); reqHistory.shift();
315
  if (d.ram) { ramHistory.push(d.ram.percent); ramHistory.shift(); }
316
 
317
  drawChart('reqChart', reqHistory, '#4fc3f7');
318
  drawChart('ramChart', ramHistory, '#ce93d8', 100);
319
 
320
+ // โ”€โ”€ Log โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
321
+ addLog(
322
+ `โœ“ total=${d.total_requests} | free=${d.workers_free} | busy=${d.workers_busy} | RAM=${d.ram?.percent ?? '?'}% | CPU=${d.cpu ?? '?'}%`,
323
+ 'ok'
324
+ );
325
+ document.getElementById('last_update').textContent =
326
+ new Date().toLocaleTimeString('ar-EG');
327
 
328
+ } catch (e) {
329
  addLog('โŒ ูุดู„ ุงู„ุงุชุตุงู„: ' + e.message, 'err');
330
  }
331
  }
332
 
333
+ function addLog(msg, cls = 'info') {
334
  const now = new Date().toLocaleTimeString('ar-EG');
335
  logs.unshift(`<span class="${cls}">[${now}] ${msg}</span>`);
336
  if (logs.length > 80) logs.pop();
 
339
 
340
  function drawChart(id, data, color, maxVal = null) {
341
  const canvas = document.getElementById(id);
342
+ const ctx = canvas.getContext('2d');
343
+ canvas.width = canvas.offsetWidth;
344
  canvas.height = 120;
345
  const W = canvas.width, H = canvas.height;
346
+ const max = maxVal ?? Math.max(...data, 1);
347
+ ctx.clearRect(0, 0, W, H);
348
 
 
349
  ctx.strokeStyle = '#2a2d3e'; ctx.lineWidth = 1;
350
  [0.25, 0.5, 0.75].forEach(f => {
351
+ ctx.beginPath(); ctx.moveTo(0, H * f); ctx.lineTo(W, H * f); ctx.stroke();
352
  });
353
 
 
354
  const step = W / (data.length - 1);
355
  ctx.beginPath();
356
  data.forEach((v, i) => {
357
+ const x = i * step;
358
+ const y = H - (v / max) * (H - 10) - 5;
359
  i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
360
  });
361
  ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke();
 
362
  ctx.lineTo(W, H); ctx.lineTo(0, H); ctx.closePath();
363
  ctx.fillStyle = color + '22'; ctx.fill();
364
  }
365
 
366
+ // ====================================================================
367
+ // Start
368
+ // ====================================================================
369
  document.getElementById('interval_val').textContent = REFRESH / 1000;
370
+ if (TOKEN) fetchData();
371
  setInterval(fetchData, REFRESH);
372
  </script>
373
  </body>