Author SHA1 Message Date
cedricandClaude Opus 5.5 556d5ee767 Rename the architecture diagram to docs/architecture.*
docs/logstream-schema-logique.{png,excalidraw} become
docs/architecture.{png,excalidraw}; the README image and source link
follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:32:56 +02:00
cedricandClaude Opus 5.5 eb51eb15b4 Architecture diagram: version 2 from the project
Replaces the diagram with the corrected version 2 (title without commit
hash, Query with the from/to zoom bounds, Histogram box linked to the API
and VictoriaLogs, arrow labels on a white background), and adds a
Timeline bullet to the README legend.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:18:17 +02:00
cedricandClaude Opus 5.5 459d5cb79b Architecture diagram: current main, timeline histogram
- Title without the commit hash (the hand-drawn font turned "32593b9" into
  "3259369"); the subtitle dates the diagram instead.
- New Histogram (histogram.go) box: API -> histogram -> VictoriaLogs (stats).
- Query mentions the from/to range (zoom); the web UI mentions the
  timeline, columns and live view.
- Multi-segment arrows drawn straight, routed between the containers.
- PNG regenerated from the .excalidraw source (Excalidraw 0.18 renderer).
- README: histogram.go in the Search flow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:17:52 +02:00
cedricandClaude Opus 5.5 b3b7041ee9 README: architecture diagram
Adds the logical diagram of the project (PNG and its editable Excalidraw
source in docs/) and an Architecture section after the introduction,
with a short description of the data flows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:09:08 +02:00
cedricandClaude Opus 5.5 9a1b60fa2b Back-to-top button in the status bar
A small arrow button at the right end of the status bar, shown once the
page has scrolled 400 px, scrolls smoothly back to the top (instantly
when the system asks for reduced motion). Living in the fixed status bar,
it never covers a log row or the details panel, on desktop and phones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 22:13:30 +02:00
6 changed files with 3777 additions and 0 deletions

No files matched your search

+18
View File
@@ -10,6 +10,24 @@ devices ──514 udp/tcp──▶ logstream (Go) ──HTTP batches──▶ Vi
└──── API / UI ◀─────────┘
```
## Architecture
![Schéma logique de Logstream](docs/architecture.png)
- **Ingestion**: syslog (UDP/TCP) and Docker container logs both go through `sink()` (reverse DNS
on IP hosts), then the `Store` queue, which sends them in batches to VictoriaLogs.
- **Live view**: `sink()` also publishes each message to the `Hub`, which streams it to the
browsers over SSE.
- **Search**: the HTTP API turns the UI filters into LogsQL queries sent to VictoriaLogs.
- **Timeline**: `/api/histogram` (`histogram.go`) counts the logs per interval and severity,
aligned on the local time; the zoom bounds (`from`/`to`) also apply to the list and the export.
- **State**: tags and source settings live in `/data` (`logstream-data` volume); the logs
themselves in the `vlogs-data` volume.
The editable source of the diagram is
[`docs/architecture.excalidraw`](docs/architecture.excalidraw)
(open it on [excalidraw.com](https://excalidraw.com)).
## Getting started
```bash
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 884 KiB

+11
View File
@@ -126,6 +126,7 @@ const I18N = {
hDivision: (s) => `interval ${s}`, hCapped: 'enlarged',
hCappedTitle: 'The chosen division would exceed 300 intervals over this range: it has been enlarged.',
hUnzoom: '× Reset zoom', unitDay: 'd',
toTop: 'Back to top',
colRcv: 'Received', colMt: 'Message time', colSev: 'Severity', colHost: 'Host', colApp: 'App', colMsg: 'Message',
colGrip: 'Drag to resize, double-click for the automatic width',
resetCols: 'Reset column widths', colsReset: 'Column widths reset',
@@ -254,6 +255,7 @@ const I18N = {
hDivision: (s) => `intervalle ${s}`, hCapped: 'élargi',
hCappedTitle: 'La division choisie dépasserait 300 intervalles sur cette plage : elle a été élargie.',
hUnzoom: '× Annuler le zoom', unitDay: 'j',
toTop: 'Revenir en haut',
colRcv: 'Réception', colMt: 'Heure message', colSev: 'Sévérité', colHost: 'Hôte', colApp: 'App', colMsg: 'Message',
colGrip: 'Glisser pour redimensionner, double-clic pour la largeur automatique',
resetCols: 'Réinitialiser les colonnes', colsReset: 'Largeurs de colonnes réinitialisées',
@@ -1517,6 +1519,15 @@ $('#newPill').addEventListener('click', () => {
});
window.addEventListener('scroll', debounce(() => { if (window.scrollY <= 80) flushPending(); }, 150), { passive: true });
// "Back to top" button, shown once the page has scrolled a little.
const syncToTop = () => { $('#toTop').hidden = window.scrollY < 400; };
window.addEventListener('scroll', syncToTop, { passive: true });
$('#toTop').addEventListener('click', () => {
const smooth = !matchMedia('(prefers-reduced-motion: reduce)').matches;
window.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' });
});
syncToTop();
$('#live').addEventListener('click', () => {
state.live = !state.live;
store.set('live', state.live ? '1' : '0');
+3
View File
@@ -115,6 +115,9 @@
<footer class="status">
<span id="conn" class="conn"></span>
<span id="stats" class="muted"></span>
<button id="toTop" class="icon-btn to-top" type="button" data-i18n-title="toTop" data-i18n-aria="toTop" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
</button>
</footer>
<dialog id="settingsDlg" class="settings" aria-labelledby="settingsTitle">
+4
View File
@@ -425,6 +425,10 @@ mark.hit { background: var(--hit); color: inherit; border-radius: 3px; padding:
.conn.ok::before { color: var(--sev-info); }
.conn.ko::before { color: var(--sev-err); }
#stats .bad { color: var(--sev-err); }
/* "Back to top", at the right end of the status bar: never over a log row */
.to-top { width: 24px; height: 24px; margin: -5px -6px -5px auto; flex: none; } /* no taller bar */
.to-top svg { width: 16px; height: 16px; }
.to-top[hidden] { display: none; }
.toast {
position: fixed; bottom: 48px; left: 50%; transform: translateX(-50%); z-index: 50;