|
1 | 1 | // Access-logs dashboard: filters, analytics overview (charts + user/top tables), |
2 | 2 | // on-demand event search, node status, purge. |
3 | 3 | // |
4 | | -// All DuckDB-backed data comes from a single /api/analytics call (the server |
5 | | -// runs the whole overview in one worker spawn). The raw-event search is a |
6 | | -// separate on-demand request. Requests are issued sequentially, never in |
7 | | -// parallel, because the panel intentionally allows only one heavy DuckDB query |
8 | | -// at a time (weak-hardware constraint) and parallel calls would be rejected. |
| 4 | +// The whole overview comes from a single /api/analytics call (the server fans |
| 5 | +// the aggregate queries out to ClickHouse). The raw-event search is a separate |
| 6 | +// on-demand request. Requests are issued sequentially so the slower analytics |
| 7 | +// response does not delay the search rows the user is usually after. |
9 | 8 | (function () { |
10 | 9 | 'use strict'; |
11 | 10 |
|
|
43 | 42 | return n.toFixed(i ? 1 : 0) + ' ' + u[i]; |
44 | 43 | } |
45 | 44 |
|
46 | | - // DuckDB returns naive UTC timestamps ("2026-07-10 10:05:00", no zone). Parse |
47 | | - // them as UTC so the whole page displays in the viewer's LOCAL time — |
| 45 | + // The server returns naive UTC timestamps ("2026-07-10 10:05:00", no zone). |
| 46 | + // Parse them as UTC so the whole page displays in the viewer's LOCAL time — |
48 | 47 | // consistent with the datetime-local filter inputs (which are local and get |
49 | 48 | // converted to UTC for the query). Without this the shown times would be |
50 | 49 | // silently shifted by the timezone offset. |
|
359 | 358 | renderUsers(data.users); |
360 | 359 | renderTops(data); |
361 | 360 |
|
362 | | - // In degraded mode the DuckDB-only widgets are empty; hint why. |
363 | | - if (data.duckdbRequired) { |
364 | | - const note = (span) => '<tr><td class="al-empty" colspan="' + span + '">' + esc(I18N.duckdbRequired) + '</td></tr>'; |
| 361 | + // Degraded mode (ClickHouse not configured/unreachable): widgets are |
| 362 | + // empty; hint why. |
| 363 | + if (data.chRequired) { |
| 364 | + const note = (span) => '<tr><td class="al-empty" colspan="' + span + '">' + esc(I18N.chRequired) + '</td></tr>'; |
365 | 365 | if (!(data.users || []).length) { $('alUsersByIp').innerHTML = note(5); $('alUsersByFanout').innerHTML = note(4); } |
366 | 366 | if (!(data.topPorts || []).length) $('alTopPorts').innerHTML = note(2); |
367 | 367 | if (!(data.topBlocked || []).length) $('alTopBlocked').innerHTML = note(2); |
|
417 | 417 | } |
418 | 418 | } |
419 | 419 |
|
420 | | - // Sequential refresh: analytics -> search -> status (never parallel, to |
421 | | - // respect the single-concurrent-DuckDB-query limit). |
| 420 | + // Sequential refresh: analytics -> search -> status. Keeps the page calm and |
| 421 | + // avoids piling concurrent aggregate queries onto ClickHouse from one tab. |
422 | 422 | async function refreshAll() { |
423 | 423 | await loadAnalytics(); |
424 | 424 | await loadSearch(); |
|
0 commit comments