Compare commits

...
Author SHA1 Message Date
claude BotandClaude Opus 5.5 78e363652b Header: lowercase oidc badge
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 17:27:30 +02:00
claude BotandClaude Opus 5.5 a57531c95f Header: show the logged-in user with an OIDC / local badge, or (no auth)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 17:25:22 +02:00
claude BotandClaude Opus 5.5 7f053a0fe9 Settings: clearer tab names
EN: Localization, Patterns, Log sources, Appearance, Database.
FR: Localisation et langue, Motifs de recherche, Sources de logs,
Affichage, Base de données. Internal tab ids are unchanged, so the
remembered tab still opens. READMEs and preset docs follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 17:16:19 +02:00
claude BotandClaude Opus 5.5 5b28bff48b Settings: size the side menu to its longest label
"Motif de recherche" overflowed the fixed 176px menu column. The column
now grows to its longest label (176px minimum), and on narrow screens the
tab labels wrap on spaces instead of being cut.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 17:08:05 +02:00
claude BotandClaude Opus 5.5 c2c3926135 Rename the color tags "Filters" tab and column to "Pattern" (FR: "Motif de recherche")
Only the color highlight rules are renamed: the Settings tab, the codes
column of the log list and the docs. Real filters (source, severity,
range, search) keep their name. The internal "filters" tab id is kept so
the saved settings tab still opens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 17:01:35 +02:00
7 changed files with 76 additions and 51 deletions

No files matched your search

+15 -15
View File
@@ -55,7 +55,7 @@ Ouvrez ensuite <http://localhost:8080>.
- **Test manuel** : `logger -n 127.0.0.1 -P 514 -d "hello error"` (util-linux) ou - **Test manuel** : `logger -n 127.0.0.1 -P 514 -d "hello error"` (util-linux) ou
`echo "<14>test ok" | nc -u -w1 127.0.0.1 514`. `echo "<14>test ok" | nc -u -w1 127.0.0.1 514`.
**Paramètres > Sources > Syslog** active ou désactive la réception syslog et choisit les **Paramètres > Sources de logs > Syslog** active ou désactive la réception syslog et choisit les
protocoles (UDP, TCP) sans redémarrage ; le choix est enregistré dans `/data/syslog.json`. Cet protocoles (UDP, TCP) sans redémarrage ; le choix est enregistré dans `/data/syslog.json`. Cet
onglet affiche aussi l'état d'écoute (et l'erreur si le port est déjà utilisé). Le port onglet affiche aussi l'état d'écoute (et l'erreur si le port est déjà utilisé). Le port
lui-même est publié par docker-compose : modifiez `SYSLOG_PORT` dans `.env`, puis lancez lui-même est publié par docker-compose : modifiez `SYSLOG_PORT` dans `.env`, puis lancez
@@ -98,7 +98,7 @@ affiche tous ses champs.
Les en-têtes de colonnes restent visibles pendant le défilement. Faites glisser le bord d'un Les en-têtes de colonnes restent visibles pendant le défilement. Faites glisser le bord d'un
en-tête (réception, heure message, sévérité, hôte, app) pour redimensionner la colonne, et en-tête (réception, heure message, sévérité, hôte, app) pour redimensionner la colonne, et
double-cliquez dessus pour revenir à la largeur automatique ; le message occupe l'espace double-cliquez dessus pour revenir à la largeur automatique ; le message occupe l'espace
restant. Les largeurs sont mémorisées par le navigateur (**Paramètres > Interface > restant. Les largeurs sont mémorisées par le navigateur (**Paramètres > Affichage >
Réinitialiser les colonnes** les rétablit toutes). Sur téléphone, la liste garde sa Réinitialiser les colonnes** les rétablit toutes). Sur téléphone, la liste garde sa
présentation sur deux lignes, sans colonnes. présentation sur deux lignes, sans colonnes.
@@ -126,7 +126,7 @@ lignes).
chaque nouvel intervalle. Rien n'est rafraîchi tant que l'onglet du navigateur est masqué ; chaque nouvel intervalle. Rien n'est rafraîchi tant que l'onglet du navigateur est masqué ;
la frise se met à jour dès qu'il redevient visible. la frise se met à jour dès qu'il redevient visible.
**Paramètres > Interface > Frise** (mémorisé par navigateur) : échelle (linéaire, √ par défaut, **Paramètres > Affichage > Frise** (mémorisé par navigateur) : échelle (linéaire, √ par défaut,
log), hauteur (S/M/L : 40/80/120 px), couleur (empilement par sévérité, intensité comparée à log), hauteur (S/M/L : 40/80/120 px), couleur (empilement par sévérité, intensité comparée à
la médiane de la fenêtre : calme, rafale au-delà de 3×, anomalie au-delà de 10×, ou aucune), la médiane de la fenêtre : calme, rafale au-delà de 3×, anomalie au-delà de 10×, ou aucune),
barres ou aire, division (automatique, environ 100 intervalles, ou fixe : 1 s, 10 s, 1 min, barres ou aire, division (automatique, environ 100 intervalles, ou fixe : 1 s, 10 s, 1 min,
@@ -155,7 +155,7 @@ colorés et exportés comme les messages syslog :
(`"level":"error"`), logfmt (`level=warn`), `[ERROR]`, ou un niveau en majuscules au début de (`"level":"error"`), logfmt (`level=warn`), `[ERROR]`, ou un niveau en majuscules au début de
la ligne (`ERROR`, `WARN`…). Sinon, elle vaut `info`. Les codes de couleur du terminal sont la ligne (`ERROR`, `WARN`…). Sinon, elle vaut `info`. Les codes de couleur du terminal sont
supprimés. supprimés.
- **Paramètres > Sources** affiche une étiquette par conteneur (`project/service`) dans un seul - **Paramètres > Sources de logs** affiche une étiquette par conteneur (`project/service`) dans un seul
champ : les conteneurs suivis en couleur, puis les non suivis en gris ; un clic bascule une champ : les conteneurs suivis en couleur, puis les non suivis en gris ; un clic bascule une
étiquette. La couleur identifie le projet compose, et les noms d'applications Docker de la étiquette. La couleur identifie le projet compose, et les noms d'applications Docker de la
liste utilisent la même couleur. Les conteneurs arrêtés sont masqués par défaut (« Afficher liste utilisent la même couleur. Les conteneurs arrêtés sont masqués par défaut (« Afficher
@@ -181,7 +181,7 @@ moteur (`GET` uniquement).
Logstream peut aussi collecter les logs système de la machine qui héberge la stack, sans rien Logstream peut aussi collecter les logs système de la machine qui héberge la stack, sans rien
configurer sur l'hôte. La source est **désactivée par défaut** : activez-la dans configurer sur l'hôte. La source est **désactivée par défaut** : activez-la dans
**Paramètres > Sources > Logs système de l'hôte** (enregistré dans `/data/hostlogs.json`). **Paramètres > Sources de logs > Logs système de l'hôte** (enregistré dans `/data/hostlogs.json`).
- `docker-compose.yml` monte `/var/log` et `/run/log/journal` en lecture seule sous `/host`. - `docker-compose.yml` monte `/var/log` et `/run/log/journal` en lecture seule sous `/host`.
- Si l'hôte utilise systemd, Logstream lit directement les fichiers du **journal systemd** - Si l'hôte utilise systemd, Logstream lit directement les fichiers du **journal systemd**
@@ -196,7 +196,7 @@ configurer sur l'hôte. La source est **désactivée par défaut** : activez-la
- **Droits** : le conteneur tourne avec un utilisateur sans privilège et reçoit le groupe `adm` - **Droits** : le conteneur tourne avec un utilisateur sans privilège et reçoit le groupe `adm`
(gid 4), qui peut lire le journal et `/var/log` sur Debian et Ubuntu. Sur d'autres systèmes, (gid 4), qui peut lire le journal et `/var/log` sur Debian et Ubuntu. Sur d'autres systèmes,
réglez `HOST_LOGS_GID` dans `.env` sur le gid de `systemd-journal` réglez `HOST_LOGS_GID` dans `.env` sur le gid de `systemd-journal`
(`getent group systemd-journal | cut -d: -f3`). Paramètres > Sources affiche un message clair (`getent group systemd-journal | cut -d: -f3`). Paramètres > Sources de logs affiche un message clair
si l'accès est refusé. si l'accès est refusé.
- Limites : les champs du journal compressés par journald (messages de plus de 512 octets, - Limites : les champs du journal compressés par journald (messages de plus de 512 octets,
compressés en zstd/lz4/xz) ne peuvent pas être décodés sans bibliothèque supplémentaire ; ils compressés en zstd/lz4/xz) ne peuvent pas être décodés sans bibliothèque supplémentaire ; ils
@@ -207,7 +207,7 @@ configurer sur l'hôte. La source est **désactivée par défaut** : activez-la
Logstream peut collecter le buffer circulaire du noyau de l'hôte (ce qu'affiche `dmesg` : Logstream peut collecter le buffer circulaire du noyau de l'hôte (ce qu'affiche `dmesg` :
matériel, pilotes, OOM killer, pare-feu…) en lisant `/dev/kmsg`. La source est **désactivée par matériel, pilotes, OOM killer, pare-feu…) en lisant `/dev/kmsg`. La source est **désactivée par
défaut** : activez-la dans **Paramètres > Sources > Messages du noyau (dmesg)** (enregistré dans défaut** : activez-la dans **Paramètres > Sources de logs > Messages du noyau (dmesg)** (enregistré dans
`/data/dmesg.json`). `/data/dmesg.json`).
- **Périphérique** : le conteneur ne voit pas `/dev/kmsg` par défaut. Décommentez la section - **Périphérique** : le conteneur ne voit pas `/dev/kmsg` par défaut. Décommentez la section
@@ -223,7 +223,7 @@ défaut** : activez-la dans **Paramètres > Sources > Messages du noyau (dmesg)*
- ou lancer Logstream en root avec la capacité `SYSLOG` : décommentez `user: "0:0"` et - ou lancer Logstream en root avec la capacité `SYSLOG` : décommentez `user: "0:0"` et
`cap_add: [SYSLOG]` dans `docker-compose.yml`. `cap_add: [SYSLOG]` dans `docker-compose.yml`.
Paramètres > Sources indique si le périphérique est absent ou l'accès refusé ; le reste de Paramètres > Sources de logs indique si le périphérique est absent ou l'accès refusé ; le reste de
Logstream continue de fonctionner et le périphérique est réessayé toutes les 10 secondes. Logstream continue de fonctionner et le périphérique est réessayé toutes les 10 secondes.
- **Champs** : **app** vaut `kernel` (ou le nom du programme pour les messages écrits par des - **Champs** : **app** vaut `kernel` (ou le nom du programme pour les messages écrits par des
programmes, comme `systemd[1]: …`), la sévérité et la facility viennent de l'enregistrement, programmes, comme `systemd[1]: …`), la sévérité et la facility viennent de l'enregistrement,
@@ -330,13 +330,13 @@ En ligne de commande : `curl -o logs.csv 'localhost:8080/api/export.csv?q=error&
L'icône en forme d'engrenage ouvre les paramètres, organisés en onglets. Tout, sauf les tags de L'icône en forme d'engrenage ouvre les paramètres, organisés en onglets. Tout, sauf les tags de
couleur, est mémorisé par navigateur. couleur, est mémorisé par navigateur.
- **Localisation** - **Localisation et langue**
- *Langue* : anglais ou français. - *Langue* : anglais ou français.
- *Date et heure* : fuseau horaire (celui du navigateur, UTC ou environ 80 fuseaux courants) - *Date et heure* : fuseau horaire (celui du navigateur, UTC ou environ 80 fuseaux courants)
et format d'affichage de l'heure de réception : `DD/MM/YYYY HH:MM:SS` (par défaut, et format d'affichage de l'heure de réception : `DD/MM/YYYY HH:MM:SS` (par défaut,
l'affichage français habituel), avec millisecondes, `YYYY-MM-DD`, horloge sur 12 heures, l'affichage français habituel), avec millisecondes, `YYYY-MM-DD`, horloge sur 12 heures,
ISO 8601 ou epoch Unix. Le fuseau horaire s'applique à toutes les dates affichées. ISO 8601 ou epoch Unix. Le fuseau horaire s'applique à toutes les dates affichées.
- **Filtres** : tags de couleur. Chaque tag a un mot-clé, une couleur de fond (le texte passe - **Motifs de recherche** : tags de couleur. Chaque tag a un mot-clé, une couleur de fond (le texte passe
automatiquement en noir ou en blanc pour rester lisible) et des options : mot entier, respect automatiquement en noir ou en blanc pour rester lisible) et des options : mot entier, respect
de la casse, expression régulière, actif. Les tags sont stockés sur le serveur dans de la casse, expression régulière, actif. Les tags sont stockés sur le serveur dans
`/data/tags.json` (volume `logstream-data`) : ils sont donc partagés par tous les navigateurs. `/data/tags.json` (volume `logstream-data`) : ils sont donc partagés par tous les navigateurs.
@@ -355,10 +355,10 @@ couleur, est mémorisé par navigateur.
les préréglages s'en servent pour colorer le code de statut ou la méthode, pas le texte autour. les préréglages s'en servent pour colorer le code de statut ou la méthode, pas le texte autour.
Chaque tag reçoit un code à deux chiffres (`01`, `02`…) attribué par le serveur : il reste Chaque tag reçoit un code à deux chiffres (`01`, `02`…) attribué par le serveur : il reste
attaché au tag jusqu'à sa suppression (les tags créés par les versions précédentes en reçoivent attaché au tag jusqu'à sa suppression (les tags créés par les versions précédentes en reçoivent
un aussi). La colonne *Filtres* de la liste affiche, en badges gris, les codes des tags actifs un aussi). La colonne *Motif de recherche* de la liste affiche, en badges gris, les codes des tags actifs
trouvés dans chaque message ; elle a la place pour 3, au-delà elle en affiche 2 et `+N`, et trouvés dans chaque message ; elle a la place pour 3, au-delà elle en affiche 2 et `+N`, et
l'infobulle les liste tous. l'infobulle les liste tous.
- **Interface** - **Affichage**
- *Thème* : Système (suit la préférence de l'ordinateur ou du téléphone), Clair ou Sombre. Le - *Thème* : Système (suit la préférence de l'ordinateur ou du téléphone), Clair ou Sombre. Le
bouton soleil/lune de l'en-tête bascule entre clair et sombre. bouton soleil/lune de l'en-tête bascule entre clair et sombre.
- *Affichage des logs* : taille du texte (très petite, petite, moyenne, grande), densité - *Affichage des logs* : taille du texte (très petite, petite, moyenne, grande), densité
@@ -372,7 +372,7 @@ couleur, est mémorisé par navigateur.
européen de polices respectueux de la vie privée ; sans accès à internet, la police du européen de polices respectueux de la vie privée ; sans accès à internet, la police du
système est utilisée. Les ligatures sont désactivées pour que `->` ou `!=` s'affichent tels système est utilisée. Les ligatures sont désactivées pour que `->` ou `!=` s'affichent tels
quels. Le réglage le plus dense est Très petite + Compacte + Inconsolata Condensed. quels. Le réglage le plus dense est Très petite + Compacte + Inconsolata Condensed.
- **Données** : chiffres de la base lus dans VictoriaLogs (actualisés au plus toutes les 30 s) : - **Base de données** : chiffres de la base lus dans VictoriaLogs (actualisés au plus toutes les 30 s) :
lignes stockées, taille sur disque (index compris), taille brute et taux de compression, lignes stockées, taille sur disque (index compris), taille brute et taux de compression,
période couverte avec la rétention, lignes des dernières 24 h et de la dernière heure, hôtes période couverte avec la rétention, lignes des dernières 24 h et de la dernière heure, hôtes
et applications distincts, espace disque libre. Les tailles sont en Ko/Mo/Go (KB/MB/GB en et applications distincts, espace disque libre. Les tailles sont en Ko/Mo/Go (KB/MB/GB en
@@ -543,7 +543,7 @@ Pour mettre à jour l'une d'elles :
VictoriaLogs conserve son format de stockage entre versions mineures ; lisez le changelog VictoriaLogs conserve son format de stockage entre versions mineures ; lisez le changelog
avant un changement de version majeure. avant un changement de version majeure.
2. Modifiez la version dans le fichier indiqué ci-dessus, puis lancez `docker compose up -d --build`. 2. Modifiez la version dans le fichier indiqué ci-dessus, puis lancez `docker compose up -d --build`.
3. Vérifiez la barre du bas (reçus / stockés / perdus) et **Paramètres > Sources**. Pour revenir 3. Vérifiez la barre du bas (reçus / stockés / perdus) et **Paramètres > Sources de logs**. Pour revenir
en arrière, remettez la version précédente et lancez la même commande. en arrière, remettez la version précédente et lancez la même commande.
## Organisation du code ## Organisation du code
@@ -561,7 +561,7 @@ Pour mettre à jour l'une d'elles :
| `rdns.go` | résolutions DNS inverses avec cache | | `rdns.go` | résolutions DNS inverses avec cache |
| `export.go` | export CSV en flux | | `export.go` | export CSV en flux |
| `docker.go` | logs des conteneurs Docker (API, lecteurs, positions, détection du niveau) | | `docker.go` | logs des conteneurs Docker (API, lecteurs, positions, détection du niveau) |
| `syslogserver.go` | écoutes syslog ouvertes et fermées depuis Paramètres > Sources | | `syslogserver.go` | écoutes syslog ouvertes et fermées depuis Paramètres > Sources de logs |
| `hostlogs.go`, `journal.go` | logs système de l'hôte : lecteur du journal systemd (sans `journalctl`) et suivi de `/var/log` | | `hostlogs.go`, `journal.go` | logs système de l'hôte : lecteur du journal systemd (sans `journalctl`) et suivi de `/var/log` |
| `dmesg.go` | messages du noyau : lecture de `/dev/kmsg`, analyse des enregistrements, position par identifiant de démarrage et numéro de séquence | | `dmesg.go` | messages du noyau : lecture de `/dev/kmsg`, analyse des enregistrements, position par identifiant de démarrage et numéro de séquence |
| `audit.go` | audit Linux : suivi de `audit.log` (rotation, position enregistrée), analyse des enregistrements, regroupement par événement, résumé | | `audit.go` | audit Linux : suivi de `audit.log` (rotation, position enregistrée), analyse des enregistrements, regroupement par événement, résumé |
+18 -18
View File
@@ -50,7 +50,7 @@ Then open <http://localhost:8080>.
- **Manual test**: `logger -n 127.0.0.1 -P 514 -d "hello error"` (util-linux) or - **Manual test**: `logger -n 127.0.0.1 -P 514 -d "hello error"` (util-linux) or
`echo "<14>test ok" | nc -u -w1 127.0.0.1 514`. `echo "<14>test ok" | nc -u -w1 127.0.0.1 514`.
**Settings > Sources > Syslog** turns syslog reception on or off and chooses the protocols **Settings > Log sources > Syslog** turns syslog reception on or off and chooses the protocols
(UDP, TCP) without restarting; the choice is saved in `/data/syslog.json`. It also shows the (UDP, TCP) without restarting; the choice is saved in `/data/syslog.json`. It also shows the
listening state (and the error if the port is already in use). The port itself is published listening state (and the error if the port is already in use). The port itself is published
by docker-compose: change `SYSLOG_PORT` in `.env`, then run `docker compose up -d`. by docker-compose: change `SYSLOG_PORT` in `.env`, then run `docker compose up -d`.
@@ -89,7 +89,7 @@ Shortcuts: `/` focuses the search box, `Esc` clears it. Clicking a row shows all
The column headers stay visible while scrolling. Drag the edge of a header (received, message The column headers stay visible while scrolling. Drag the edge of a header (received, message
time, severity, host, app) to resize the column, double-click it to go back to the automatic time, severity, host, app) to resize the column, double-click it to go back to the automatic
width; the message takes the remaining space. Widths are remembered by the browser width; the message takes the remaining space. Widths are remembered by the browser
(**Settings > Interface > Reset column widths** restores them all). On phones the list keeps its (**Settings > Appearance > Reset column widths** restores them all). On phones the list keeps its
two-line layout without columns. two-line layout without columns.
## Stream and Time range modes ## Stream and Time range modes
@@ -114,7 +114,7 @@ time** on the server clock (so it matches the times shown in the rows).
- In live mode the last interval grows as messages arrive, and the timeline reloads at each new - In live mode the last interval grows as messages arrive, and the timeline reloads at each new
interval. Nothing is refreshed while the browser tab is hidden; it catches up when shown again. interval. Nothing is refreshed while the browser tab is hidden; it catches up when shown again.
**Settings > Interface > Timeline** (remembered per browser): scale (linear, √ by default, log), **Settings > Appearance > Timeline** (remembered per browser): scale (linear, √ by default, log),
height (S/M/L: 40/80/120 px), color (stacked by severity, intensity compared with the median of height (S/M/L: 40/80/120 px), color (stacked by severity, intensity compared with the median of
the window: calm, burst above 3×, anomaly above 10×, or none), bars or area, division the window: calm, burst above 3×, anomaly above 10×, or none), bars or area, division
(automatic, about 100 intervals, or fixed: 1 s, 10 s, 1 min, 5 min, 1 h, 1 day) and refresh (automatic, about 100 intervals, or fixed: 1 s, 10 s, 1 min, 5 min, 1 h, 1 day) and refresh
@@ -141,7 +141,7 @@ colored and exported like syslog messages:
- The severity comes from the line itself when the application writes it: JSON - The severity comes from the line itself when the application writes it: JSON
(`"level":"error"`), logfmt (`level=warn`), `[ERROR]`, or an upper-case level word at the (`"level":"error"`), logfmt (`level=warn`), `[ERROR]`, or an upper-case level word at the
start of the line (`ERROR`, `WARN`…). Otherwise it is `info`. Terminal color codes are removed. start of the line (`ERROR`, `WARN`…). Otherwise it is `info`. Terminal color codes are removed.
- **Settings > Sources** shows one label per container (`project/service`) in a single field: - **Settings > Log sources** shows one label per container (`project/service`) in a single field:
followed containers in color, then not followed ones in grey; a click switches a label. followed containers in color, then not followed ones in grey; a click switches a label.
The color identifies the compose project, and Docker app names in the log list use the The color identifies the compose project, and Docker app names in the log list use the
same color. Stopped containers are hidden by default ("Show stopped containers" displays same color. Stopped containers are hidden by default ("Show stopped containers" displays
@@ -165,7 +165,7 @@ which only lets through listing containers, reading logs, events and engine info
Logstream can also collect the system logs of the machine hosting the stack, without Logstream can also collect the system logs of the machine hosting the stack, without
configuring anything on the host. The source is **off by default**: turn it on in configuring anything on the host. The source is **off by default**: turn it on in
**Settings > Sources > Host system logs** (saved in `/data/hostlogs.json`). **Settings > Log sources > Host system logs** (saved in `/data/hostlogs.json`).
- `docker-compose.yml` mounts `/var/log` and `/run/log/journal` read-only under `/host`. - `docker-compose.yml` mounts `/var/log` and `/run/log/journal` read-only under `/host`.
- When the host runs systemd, Logstream reads the **systemd journal** files directly (no - When the host runs systemd, Logstream reads the **systemd journal** files directly (no
@@ -180,7 +180,7 @@ configuring anything on the host. The source is **off by default**: turn it on i
- **Permissions**: the container runs as an unprivileged user and gets the `adm` group - **Permissions**: the container runs as an unprivileged user and gets the `adm` group
(gid 4), which can read the journal and `/var/log` on Debian and Ubuntu. On other systems, (gid 4), which can read the journal and `/var/log` on Debian and Ubuntu. On other systems,
set `HOST_LOGS_GID` in `.env` to the gid of `systemd-journal` set `HOST_LOGS_GID` in `.env` to the gid of `systemd-journal`
(`getent group systemd-journal | cut -d: -f3`). Settings > Sources shows a clear message when (`getent group systemd-journal | cut -d: -f3`). Settings > Log sources shows a clear message when
access is denied. access is denied.
- Limits: journal fields compressed by journald (messages longer than 512 bytes, compressed - Limits: journal fields compressed by journald (messages longer than 512 bytes, compressed
with zstd/lz4/xz) cannot be decoded without extra libraries; they are counted in Settings and with zstd/lz4/xz) cannot be decoded without extra libraries; they are counted in Settings and
@@ -191,7 +191,7 @@ configuring anything on the host. The source is **off by default**: turn it on i
Logstream can collect the kernel ring buffer of the host (what `dmesg` shows: hardware, Logstream can collect the kernel ring buffer of the host (what `dmesg` shows: hardware,
drivers, OOM killer, firewall…) by reading `/dev/kmsg`. The source is **off by default**: turn it drivers, OOM killer, firewall…) by reading `/dev/kmsg`. The source is **off by default**: turn it
on in **Settings > Sources > Kernel messages (dmesg)** (saved in `/data/dmesg.json`). on in **Settings > Log sources > Kernel messages (dmesg)** (saved in `/data/dmesg.json`).
- **Device**: the container does not see `/dev/kmsg` by default. Uncomment the `devices` - **Device**: the container does not see `/dev/kmsg` by default. Uncomment the `devices`
section of `docker-compose.yml` (`/dev/kmsg:/dev/kmsg:r`, read-only), then section of `docker-compose.yml` (`/dev/kmsg:/dev/kmsg:r`, read-only), then
@@ -205,7 +205,7 @@ on in **Settings > Sources > Kernel messages (dmesg)** (saved in `/data/dmesg.js
- or run Logstream as root with the `SYSLOG` capability: uncomment `user: "0:0"` and - or run Logstream as root with the `SYSLOG` capability: uncomment `user: "0:0"` and
`cap_add: [SYSLOG]` in `docker-compose.yml`. `cap_add: [SYSLOG]` in `docker-compose.yml`.
Settings > Sources says when the device is missing or access is denied; the rest of Settings > Log sources says when the device is missing or access is denied; the rest of
Logstream keeps working and the device is tried again every 10 seconds. Logstream keeps working and the device is tried again every 10 seconds.
- **Fields**: **app** is `kernel` (or the program name for messages written by programs such - **Fields**: **app** is `kernel` (or the program name for messages written by programs such
as `systemd[1]: …`), severity and facility come from the record, **host** is `localhost`, as `systemd[1]: …`), severity and facility come from the record, **host** is `localhost`,
@@ -224,7 +224,7 @@ on in **Settings > Sources > Kernel messages (dmesg)** (saved in `/data/dmesg.js
Logstream can follow the log of the Linux audit daemon (`/var/log/audit/audit.log`): logins, Logstream can follow the log of the Linux audit daemon (`/var/log/audit/audit.log`): logins,
`sudo`, watched files, syscalls matched by your `auditctl` rules… The source is **off by `sudo`, watched files, syscalls matched by your `auditctl` rules… The source is **off by
default**: turn it on in **Settings > Sources > Linux audit (auditd)**, where the file path can default**: turn it on in **Settings > Log sources > Linux audit (auditd)**, where the file path can
also be changed (saved in `/data/audit.json`; `AUDIT_LOG` sets the default path). also be changed (saved in `/data/audit.json`; `AUDIT_LOG` sets the default path).
- **Mount**: uncomment `/var/log/audit:/var/log/audit:ro` in `docker-compose.yml`, then - **Mount**: uncomment `/var/log/audit:/var/log/audit:ro` in `docker-compose.yml`, then
@@ -240,7 +240,7 @@ also be changed (saved in `/data/audit.json`; `AUDIT_LOG` sets the default path)
``` ```
auditd then creates every rotated file with mode `0640` and group `adm`. An ACL also works auditd then creates every rotated file with mode `0640` and group `adm`. An ACL also works
(`sudo setfacl -m u:10001:rx /var/log/audit && sudo setfacl -m u:10001:r /var/log/audit/audit.log`) (`sudo setfacl -m u:10001:rx /var/log/audit && sudo setfacl -m u:10001:r /var/log/audit/audit.log`)
but must be applied again to each new file after a rotation. Settings > Sources says when the but must be applied again to each new file after a rotation. Settings > Log sources says when the
file is missing or access is denied; it is tried again every second. file is missing or access is denied; it is tried again every second.
- **Events**: the records of one event (`SYSCALL`, `EXECVE`, `CWD`, `PATH`, `PROCTITLE`…) share - **Events**: the records of one event (`SYSCALL`, `EXECVE`, `CWD`, `PATH`, `PROCTITLE`…) share
the same `msg=audit(time:serial)` and become **one** log, once no record came for 2 seconds. the same `msg=audit(time:serial)` and become **one** log, once no record came for 2 seconds.
@@ -271,7 +271,7 @@ Logstream records its own events as logs, with **host** `logstream`, **app** the
(`store`, `syslog`, `docker`, `host`, `dmesg`, `audit`, `main`) and the `logstream` source: (`store`, `syslog`, `docker`, `host`, `dmesg`, `audit`, `main`) and the `logstream` source:
VictoriaLogs unreachable and back, lost batches, batches refused from the disk buffer, syslog VictoriaLogs unreachable and back, lost batches, batches refused from the disk buffer, syslog
listener errors, Docker API or container streams lost, sources that cannot read, start and listener errors, Docker API or container streams lost, sources that cannot read, start and
stop. They are on by default from level **warning**; **Settings > Sources > LogStream internal stop. They are on by default from level **warning**; **Settings > Log sources > LogStream internal
logs** turns them off or changes the minimum level (saved in `/data/selflog.json`). The same logs** turns them off or changes the minimum level (saved in `/data/selflog.json`). The same
event (component and message) is recorded at most once every 30 seconds, with the number of event (component and message) is recorded at most once every 30 seconds, with the number of
repeats left out; events go through a bounded queue and never wait for the storage, so an repeats left out; events go through a bounded queue and never wait for the storage, so an
@@ -307,7 +307,7 @@ remembered per browser.
format of the reception time: `DD/MM/YYYY HH:MM:SS` (default, the usual French display), format of the reception time: `DD/MM/YYYY HH:MM:SS` (default, the usual French display),
with milliseconds, `YYYY-MM-DD`, 12-hour clock, ISO 8601 or Unix epoch. The time zone with milliseconds, `YYYY-MM-DD`, 12-hour clock, ISO 8601 or Unix epoch. The time zone
applies to every date shown. applies to every date shown.
- **Filters**: color tags. Each tag has a keyword, a background color (the text automatically - **Patterns**: color tags. Each tag has a keyword, a background color (the text automatically
switches to black or white to stay readable) and options: whole word, match case, switches to black or white to stay readable) and options: whole word, match case,
regular expression, active. Tags are stored on the server in `/data/tags.json` regular expression, active. Tags are stored on the server in `/data/tags.json`
(`logstream-data` volume), so they are shared by every browser. Default tags (pastel): (`logstream-data` volume), so they are shared by every browser. Default tags (pastel):
@@ -324,10 +324,10 @@ remembered per browser.
or the method, not the text around it. or the method, not the text around it.
Each tag gets a two-digit code (`01`, `02`…) assigned by the server: it stays with the tag Each tag gets a two-digit code (`01`, `02`…) assigned by the server: it stays with the tag
until the tag is deleted (codes are also given to tags created by earlier versions). The until the tag is deleted (codes are also given to tags created by earlier versions). The
*Filters* column of the log list shows, as grey badges, the codes of the active tags found in *Pattern* column of the log list shows, as grey badges, the codes of the active tags found in
each message; it has room for 3, beyond that it shows 2 and `+N`, and the tooltip lists them each message; it has room for 3, beyond that it shows 2 and `+N`, and the tooltip lists them
all. all.
- **Interface** - **Appearance**
- *Theme*: System (follows the computer/phone preference), Light or Dark. The sun/moon - *Theme*: System (follows the computer/phone preference), Light or Dark. The sun/moon
button in the header switches between light and dark. button in the header switches between light and dark.
- *Log display*: font size (tiny, small, medium, large), density (normal, or compact to - *Log display*: font size (tiny, small, medium, large), density (normal, or compact to
@@ -339,7 +339,7 @@ remembered per browser.
[Bunny Fonts](https://fonts.bunny.net), a privacy-friendly European font service; without [Bunny Fonts](https://fonts.bunny.net), a privacy-friendly European font service; without
internet access, the system font is used. Ligatures are disabled so `->` or `!=` show as internet access, the system font is used. Ligatures are disabled so `->` or `!=` show as
typed. The densest setting is Tiny + Compact + Inconsolata Condensed. typed. The densest setting is Tiny + Compact + Inconsolata Condensed.
- **Data**: database figures read from VictoriaLogs (refreshed at most every 30 s): stored - **Database**: database figures read from VictoriaLogs (refreshed at most every 30 s): stored
lines, size on disk (index included), raw size and compression ratio, period covered with lines, size on disk (index included), raw size and compression ratio, period covered with
the retention, lines of the last 24 h and last hour, distinct hosts and apps, free disk the retention, lines of the last 24 h and last hour, distinct hosts and apps, free disk
space. Sizes use KB/MB/GB (Ko/Mo/Go in French). "Delete all logs" permanently erases every stored log (you must type space. Sizes use KB/MB/GB (Ko/Mo/Go in French). "Delete all logs" permanently erases every stored log (you must type
@@ -457,7 +457,7 @@ are only known by your router or a local DNS (Pi-hole, AdGuard, Unbound…), set
| `HOST_LOGS_ROOT` | `/host` | where the host directories are mounted | | `HOST_LOGS_ROOT` | `/host` | where the host directories are mounted |
| `DMESG_BACKFILL` | `1h` | history read when the kernel messages source is turned on | | `DMESG_BACKFILL` | `1h` | history read when the kernel messages source is turned on |
| `DMESG_DEVICE` | `/dev/kmsg` | kernel log device read by the kernel messages source | | `DMESG_DEVICE` | `/dev/kmsg` | kernel log device read by the kernel messages source |
| `AUDIT_LOG` | `/var/log/audit/audit.log` | default path of the Linux audit log (can be changed in Settings > Sources) | | `AUDIT_LOG` | `/var/log/audit/audit.log` | default path of the Linux audit log (can be changed in Settings > Log sources) |
| `AUDIT_BACKFILL` | `1h` | history read when the Linux audit source is turned on | | `AUDIT_BACKFILL` | `1h` | history read when the Linux audit source is turned on |
| `AUDIT_HOST` | empty | host name of the audit events when the records carry no `node=` | | `AUDIT_HOST` | empty | host name of the audit events when the records carry no `node=` |
| `TZ` | `Europe/Paris` | time zone for RFC 3164 timestamps (which carry none) | | `TZ` | `Europe/Paris` | time zone for RFC 3164 timestamps (which carry none) |
@@ -498,7 +498,7 @@ To update one of them:
VictoriaLogs keeps its storage format across minor versions; read the changelog before a VictoriaLogs keeps its storage format across minor versions; read the changelog before a
major version change. major version change.
2. Change the version in the file above, then run `docker compose up -d --build`. 2. Change the version in the file above, then run `docker compose up -d --build`.
3. Check the bottom bar (received / stored / dropped) and **Settings > Sources**. To go back, 3. Check the bottom bar (received / stored / dropped) and **Settings > Log sources**. To go back,
restore the previous version and run the same command. restore the previous version and run the same command.
## Code layout ## Code layout
@@ -516,7 +516,7 @@ To update one of them:
| `rdns.go` | cached reverse DNS lookups | | `rdns.go` | cached reverse DNS lookups |
| `export.go` | streamed CSV export | | `export.go` | streamed CSV export |
| `docker.go` | Docker container logs (API, followers, positions, level detection) | | `docker.go` | Docker container logs (API, followers, positions, level detection) |
| `syslogserver.go` | syslog listeners opened and closed from Settings > Sources | | `syslogserver.go` | syslog listeners opened and closed from Settings > Log sources |
| `hostlogs.go`, `journal.go` | host system logs: systemd journal reader (no `journalctl`) and `/var/log` follower | | `hostlogs.go`, `journal.go` | host system logs: systemd journal reader (no `journalctl`) and `/var/log` follower |
| `dmesg.go` | kernel messages: `/dev/kmsg` reader, record parsing, position by boot ID and sequence number | | `dmesg.go` | kernel messages: `/dev/kmsg` reader, record parsing, position by boot ID and sequence number |
| `audit.go` | Linux audit: `audit.log` follower (rotation, saved offset), record parsing, grouping by event, summary | | `audit.go` | Linux audit: `audit.log` follower (rotation, saved offset), record parsing, grouping by event, summary |
+1 -1
View File
@@ -2,7 +2,7 @@
# Préréglages de tags de couleur # Préréglages de tags de couleur
Dans **Paramètres › Filtres**, le menu **+ Préréglage…** ajoute d'un clic un groupe de tags de Dans **Paramètres › Motifs de recherche**, le menu **+ Préréglage…** ajoute d'un clic un groupe de tags de
couleur tout faits. Les tags ajoutés sont des tags ordinaires : vous pouvez changer leur couleur, couleur tout faits. Les tags ajoutés sont des tags ordinaires : vous pouvez changer leur couleur,
leur motif ou leurs options, ou les supprimer. Un tag dont le motif est déjà dans la liste n'est leur motif ou leurs options, ou les supprimer. Un tag dont le motif est déjà dans la liste n'est
pas ajouté en double. pas ajouté en double.
+1 -1
View File
@@ -2,7 +2,7 @@
# Color tag presets # Color tag presets
In **Settings › Filters**, the **+ Preset…** menu adds a group of ready-made color tags in one In **Settings › Patterns**, the **+ Preset…** menu adds a group of ready-made color tags in one
click. Added tags are ordinary tags: you can change their color, pattern or options, or delete click. Added tags are ordinary tags: you can change their color, pattern or options, or delete
them. A tag whose pattern is already in the list is not added twice. them. A tag whose pattern is already in the list is not added twice.
+20 -7
View File
@@ -75,7 +75,7 @@ const I18N = {
msgTimeTitle: 'Timestamp from the message', msgTimeTitle: 'Timestamp from the message',
fHostName: 'host name (DNS)', fHostIp: 'host IP', fHostName: 'host name (DNS)', fHostIp: 'host IP',
clickHost: 'Click to filter on this host', clickApp: 'Click to filter on this app', clickHost: 'Click to filter on this host', clickApp: 'Click to filter on this app',
sourceAria: 'Source', srcAll: 'All sources', tabSources: 'Sources', sourceAria: 'Source', srcAll: 'All sources', tabSources: 'Log sources',
srcHost: 'Host system', srcHost: 'Host system',
srcKernel: 'Kernel (dmesg)', srcKernel: 'Kernel (dmesg)',
auditTitle: 'Linux audit (auditd)', auditTitle: 'Linux audit (auditd)',
@@ -142,7 +142,7 @@ const I18N = {
exportNote: (n) => `Up to ${n} logs matching the current filters, newest first. Dates use the time zone chosen in Settings.`, exportNote: (n) => `Up to ${n} logs matching the current filters, newest first. Dates use the time zone chosen in Settings.`,
exporting: 'Exporting…', exportDone: (f) => `Downloaded: ${f}`, exportError: 'Export failed: ', exporting: 'Exporting…', exportDone: (f) => `Downloaded: ${f}`, exportError: 'Export failed: ',
err_export_pipes: 'Export does not support LogsQL pipes (| …): keep only the filter part', err_export_pipes: 'Export does not support LogsQL pipes (| …): keep only the filter part',
tabLocale: 'Localization', tabFilters: 'Filters', tabInterface: 'Interface', tabData: 'Data', tabLocale: 'Localization', tabFilters: 'Patterns', tabInterface: 'Appearance', tabData: 'Database',
themeTitle: 'Theme', themeSystem: 'System', themeLight: 'Light', themeDark: 'Dark', themeTitle: 'Theme', themeSystem: 'System', themeLight: 'Light', themeDark: 'Dark',
themeHelp: 'System follows the light/dark preference of your computer or phone.', themeHelp: 'System follows the light/dark preference of your computer or phone.',
logDisplay: 'Log display', fontSize: 'Font size', logDisplay: 'Log display', fontSize: 'Font size',
@@ -196,7 +196,7 @@ const I18N = {
hCappedTitle: 'The chosen division would exceed 300 intervals over this range: it has been enlarged.', hCappedTitle: 'The chosen division would exceed 300 intervals over this range: it has been enlarged.',
hUnzoom: '× Back to stream', unitDay: 'd', hUnzoom: '× Back to stream', unitDay: 'd',
toTop: 'Back to top', toTop: 'Back to top',
colRcv: 'Received', colMt: 'Message time', colSev: 'Severity', colHost: 'Host', colApp: 'App', colCodes: 'Filters', colMsg: 'Message', colRcv: 'Received', colMt: 'Message time', colSev: 'Severity', colHost: 'Host', colApp: 'App', colCodes: 'Pattern', colMsg: 'Message',
codesTitle: 'Codes of the color tags found in the message', codesTitle: 'Codes of the color tags found in the message',
tagCodeTitle: 'Code shown on the log lines this tag matches', tagCodeTitle: 'Code shown on the log lines this tag matches',
colGrip: 'Drag to resize, double-click for the automatic width', colGrip: 'Drag to resize, double-click for the automatic width',
@@ -275,7 +275,7 @@ const I18N = {
msgTimeTitle: 'Horodatage contenu dans le message', msgTimeTitle: 'Horodatage contenu dans le message',
fHostName: 'nom d\'hôte (DNS)', fHostIp: 'IP de l\'hôte', fHostName: 'nom d\'hôte (DNS)', fHostIp: 'IP de l\'hôte',
clickHost: 'Cliquer pour filtrer sur cet hôte', clickApp: 'Cliquer pour filtrer sur cette appli', clickHost: 'Cliquer pour filtrer sur cet hôte', clickApp: 'Cliquer pour filtrer sur cette appli',
sourceAria: 'Source', srcAll: 'Toutes les sources', tabSources: 'Sources', sourceAria: 'Source', srcAll: 'Toutes les sources', tabSources: 'Sources de logs',
srcHost: 'Système hôte', srcHost: 'Système hôte',
srcKernel: 'Noyau (dmesg)', srcKernel: 'Noyau (dmesg)',
auditTitle: 'Audit Linux (auditd)', auditTitle: 'Audit Linux (auditd)',
@@ -342,7 +342,7 @@ const I18N = {
exportNote: (n) => `Jusqu'à ${n} logs correspondant aux filtres, du plus récent au plus ancien. Dates dans le fuseau choisi dans Paramètres.`, exportNote: (n) => `Jusqu'à ${n} logs correspondant aux filtres, du plus récent au plus ancien. Dates dans le fuseau choisi dans Paramètres.`,
exporting: 'Export…', exportDone: (f) => `Téléchargé : ${f}`, exportError: 'Échec de l\'export : ', exporting: 'Export…', exportDone: (f) => `Téléchargé : ${f}`, exportError: 'Échec de l\'export : ',
err_export_pipes: 'L\'export ne gère pas les pipes LogsQL (| …) : gardez seulement la partie filtre', err_export_pipes: 'L\'export ne gère pas les pipes LogsQL (| …) : gardez seulement la partie filtre',
tabLocale: 'Localisation', tabFilters: 'Filtres', tabInterface: 'Interface', tabData: 'Données', tabLocale: 'Localisation et langue', tabFilters: 'Motifs de recherche', tabInterface: 'Affichage', tabData: 'Base de données',
themeTitle: 'Thème', themeSystem: 'Système', themeLight: 'Clair', themeDark: 'Sombre', themeTitle: 'Thème', themeSystem: 'Système', themeLight: 'Clair', themeDark: 'Sombre',
themeHelp: 'Système suit la préférence clair/sombre de votre ordinateur ou de votre téléphone.', themeHelp: 'Système suit la préférence clair/sombre de votre ordinateur ou de votre téléphone.',
logDisplay: 'Affichage des logs', fontSize: 'Taille du texte', logDisplay: 'Affichage des logs', fontSize: 'Taille du texte',
@@ -396,7 +396,7 @@ const I18N = {
hCappedTitle: 'La division choisie dépasserait 300 intervalles sur cette plage : elle a été élargie.', hCappedTitle: 'La division choisie dépasserait 300 intervalles sur cette plage : elle a été élargie.',
hUnzoom: '× Revenir au flux', unitDay: 'j', hUnzoom: '× Revenir au flux', unitDay: 'j',
toTop: 'Revenir en haut', toTop: 'Revenir en haut',
colRcv: 'Réception', colMt: 'Heure message', colSev: 'Sévérité', colHost: 'Hôte', colApp: 'App', colCodes: 'Filtres', colMsg: 'Message', colRcv: 'Réception', colMt: 'Heure message', colSev: 'Sévérité', colHost: 'Hôte', colApp: 'App', colCodes: 'Motif de recherche', colMsg: 'Message',
codesTitle: 'Codes des tags de couleur trouvés dans le message', codesTitle: 'Codes des tags de couleur trouvés dans le message',
tagCodeTitle: 'Code affiché sur les lignes de log où ce tag est trouvé', tagCodeTitle: 'Code affiché sur les lignes de log où ce tag est trouvé',
colGrip: 'Glisser pour redimensionner, double-clic pour la largeur automatique', colGrip: 'Glisser pour redimensionner, double-clic pour la largeur automatique',
@@ -2476,7 +2476,7 @@ function renderTagList() {
filterTagList(); filterTagList();
} }
// Settings > Filters: shows only the tags whose keyword, label or code contains the filter. // Settings > Pattern: shows only the tags whose keyword, label or code contains the filter.
function filterTagList() { function filterTagList() {
const q = $('#tagFilter').value.trim().toLowerCase(); const q = $('#tagFilter').value.trim().toLowerCase();
let shown = 0; let shown = 0;
@@ -2638,6 +2638,19 @@ renderPeriod();
fetch('/auth/me').then((res) => (res.ok ? res.json() : null)).then((me) => { fetch('/auth/me').then((res) => (res.ok ? res.json() : null)).then((me) => {
if (!me) return; if (!me) return;
if (me.mode === 'none' && store.get('authWarnHidden') !== '1') $('#authWarn').hidden = false; if (me.mode === 'none' && store.get('authWarnHidden') !== '1') $('#authWarn').hidden = false;
const who = $('#whoami');
if (me.mode === 'none') {
who.classList.add('none');
who.querySelector('.who-name').textContent = '(no auth)';
who.hidden = false;
} else if (me.user) {
who.querySelector('.who-name').textContent = me.user;
who.title = me.user;
const badge = who.querySelector('.who-badge');
badge.textContent = me.mode;
badge.classList.add(me.mode);
who.hidden = false;
}
if (me.role === 'viewer') { if (me.role === 'viewer') {
document.body.classList.add('read-only'); document.body.classList.add('read-only');
for (const p of document.querySelectorAll('[data-panel="filters"], [data-panel="sources"], [data-panel="data"]')) { for (const p of document.querySelectorAll('[data-panel="filters"], [data-panel="sources"], [data-panel="data"]')) {
+7 -6
View File
@@ -41,6 +41,7 @@
<svg class="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg> <svg class="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
<svg class="moon" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg> <svg class="moon" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
</button> </button>
<span id="whoami" class="whoami" hidden><span class="who-name"></span><span class="who-badge"></span></span>
<a id="logoutBtn" class="icon-btn" href="/auth/logout" hidden data-i18n-title="logout" data-i18n-aria="logout"> <a id="logoutBtn" class="icon-btn" href="/auth/logout" hidden data-i18n-title="logout" data-i18n-aria="logout">
<!-- Log out icon (Lucide "log-out", ISC license) --> <!-- Log out icon (Lucide "log-out", ISC license) -->
<svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/></svg> <svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/></svg>
@@ -143,7 +144,7 @@
<span data-col="sev"><span class="lbl" data-i18n="colSev">Severity</span><i class="grip" data-i18n-title="colGrip"></i></span> <span data-col="sev"><span class="lbl" data-i18n="colSev">Severity</span><i class="grip" data-i18n-title="colGrip"></i></span>
<span data-col="host"><span class="lbl" data-i18n="colHost">Host</span><i class="grip" data-i18n-title="colGrip"></i></span> <span data-col="host"><span class="lbl" data-i18n="colHost">Host</span><i class="grip" data-i18n-title="colGrip"></i></span>
<span data-col="app"><span class="lbl" data-i18n="colApp">App</span><i class="grip" data-i18n-title="colGrip"></i></span> <span data-col="app"><span class="lbl" data-i18n="colApp">App</span><i class="grip" data-i18n-title="colGrip"></i></span>
<span data-col="codes"><span class="lbl" data-i18n="colCodes" data-i18n-title="codesTitle">Filters</span></span> <span data-col="codes"><span class="lbl" data-i18n="colCodes" data-i18n-title="codesTitle">Pattern</span></span>
<span data-col="msg"><span class="lbl" data-i18n="colMsg">Message</span></span> <span data-col="msg"><span class="lbl" data-i18n="colMsg">Message</span></span>
</div> </div>
<main id="list" class="list"></main> <main id="list" class="list"></main>
@@ -179,19 +180,19 @@
</button> </button>
<button type="button" role="tab" data-tab="filters"> <button type="button" role="tab" data-tab="filters">
<svg viewBox="0 0 24 24"><path d="M3 12V4a1 1 0 0 1 1-1h8l9 9-9 9z"/><circle cx="8" cy="8" r="1.5"/></svg> <svg viewBox="0 0 24 24"><path d="M3 12V4a1 1 0 0 1 1-1h8l9 9-9 9z"/><circle cx="8" cy="8" r="1.5"/></svg>
<span data-i18n="tabFilters">Filters</span> <span data-i18n="tabFilters">Patterns</span>
</button> </button>
<button type="button" role="tab" data-tab="sources"> <button type="button" role="tab" data-tab="sources">
<svg viewBox="0 0 24 24"><path d="M21 8 12 3 3 8v8l9 5 9-5z"/><path d="m3 8 9 5 9-5M12 13v8"/></svg> <svg viewBox="0 0 24 24"><path d="M21 8 12 3 3 8v8l9 5 9-5z"/><path d="m3 8 9 5 9-5M12 13v8"/></svg>
<span data-i18n="tabSources">Sources</span> <span data-i18n="tabSources">Log sources</span>
</button> </button>
<button type="button" role="tab" data-tab="interface"> <button type="button" role="tab" data-tab="interface">
<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg> <svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg>
<span data-i18n="tabInterface">Interface</span> <span data-i18n="tabInterface">Appearance</span>
</button> </button>
<button type="button" role="tab" data-tab="data"> <button type="button" role="tab" data-tab="data">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.7 3.6 3 8 3s8-1.3 8-3V5M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg> <svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.7 3.6 3 8 3s8-1.3 8-3V5M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>
<span data-i18n="tabData">Data</span> <span data-i18n="tabData">Database</span>
</button> </button>
</nav> </nav>
@@ -218,7 +219,7 @@
</section> </section>
</div> </div>
<!-- Filters --> <!-- Pattern (color tags; the "filters" tab id is kept for saved settings) -->
<div class="set-panel wide" role="tabpanel" data-panel="filters" hidden> <div class="set-panel wide" role="tabpanel" data-panel="filters" hidden>
<section class="set-section tags-section"> <section class="set-section tags-section">
<div class="sec-head"> <div class="sec-head">
+14 -3
View File
@@ -189,6 +189,16 @@ svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
.chip.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); } .chip.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.actions { display: flex; align-items: center; gap: 6px; margin-left: auto; } .actions { display: flex; align-items: center; gap: 6px; margin-left: auto; }
/* Logged-in user, with how they logged in (oidc / local) */
.whoami { display: flex; align-items: center; gap: 6px; min-width: 0; margin-left: 6px; font-size: 13px; color: var(--muted); }
.whoami .who-name { font-weight: 600; color: var(--text); max-width: 18ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.whoami .who-badge {
flex: none; border: 1px solid var(--border); border-radius: 6px;
padding: 1px 6px; font-size: 10.5px; font-weight: 650; letter-spacing: .02em;
}
.whoami .who-badge.oidc { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.whoami .who-badge:empty { display: none; }
.whoami.none .who-name { font-weight: 400; color: var(--muted); }
.icon-btn { .icon-btn {
width: 36px; height: 36px; display: grid; place-items: center; width: 36px; height: 36px; display: grid; place-items: center;
border: 1px solid transparent; background: transparent; border-radius: 9px; color: var(--muted); border: 1px solid transparent; background: transparent; border-radius: 9px; color: var(--muted);
@@ -528,7 +538,7 @@ dialog.settings {
} }
dialog.settings .dlg { display: flex; flex-direction: column; height: 100%; padding: 0; } dialog.settings .dlg { display: flex; flex-direction: column; height: 100%; padding: 0; }
dialog.settings .dlg > header { padding: 14px 16px 14px 22px; border-bottom: 1px solid var(--border); } dialog.settings .dlg > header { padding: 14px 16px 14px 22px; border-bottom: 1px solid var(--border); }
.set-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 176px minmax(0, 1fr); } .set-body { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(176px, max-content) minmax(0, 1fr); }
.set-nav { .set-nav {
display: flex; flex-direction: column; gap: 2px; display: flex; flex-direction: column; gap: 2px;
padding: 12px 10px; border-right: 1px solid var(--border); padding: 12px 10px; border-right: 1px solid var(--border);
@@ -685,7 +695,7 @@ input.switch:checked::after { transform: translateX(14px); }
input.switch:disabled { cursor: not-allowed; } input.switch:disabled { cursor: not-allowed; }
input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Settings > Filters: toolbar kept in view while the list scrolls, dense rows. */ /* Settings > Pattern: toolbar kept in view while the list scrolls, dense rows. */
.tags-section { padding-top: 12px !important; } .tags-section { padding-top: 12px !important; }
.tag-toolbar { .tag-toolbar {
position: sticky; top: -16px; z-index: 2; position: sticky; top: -16px; z-index: 2;
@@ -750,6 +760,7 @@ input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2
.topbar { flex-wrap: wrap; padding: 8px 16px; gap: 8px 10px; } .topbar { flex-wrap: wrap; padding: 8px 16px; gap: 8px 10px; }
.search { order: 3; flex-basis: 100%; max-width: none; } .search { order: 3; flex-basis: 100%; max-width: none; }
.live .lbl { display: none; } .live .lbl { display: none; }
.whoami .who-name { max-width: 12ch; }
.filters { padding: 10px 16px 6px; } .filters { padding: 10px 16px 6px; }
.filters select { flex: 1 1 calc(50% - 8px); max-width: none; min-width: 0; } .filters select { flex: 1 1 calc(50% - 8px); max-width: none; min-width: 0; }
.view-mode { flex: 1 1 100%; } .view-mode { flex: 1 1 100%; }
@@ -794,7 +805,7 @@ input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2
flex-direction: column; justify-content: center; height: auto; padding: 6px 2px; gap: 3px; flex-direction: column; justify-content: center; height: auto; padding: 6px 2px; gap: 3px;
font-size: 11.5px; text-align: center; font-size: 11.5px; text-align: center;
} }
.set-nav button span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; } .set-nav button span { max-width: 100%; white-space: normal; line-height: 1.15; overflow: hidden; text-overflow: ellipsis; }
.set-panels { padding: 12px 12px 18px; } .set-panels { padding: 12px 12px 18px; }
.set-panel { grid-template-columns: minmax(0, 1fr); gap: 10px; } .set-panel { grid-template-columns: minmax(0, 1fr); gap: 10px; }
.set-panel > .set-section, .set-panel > .set-section + .set-section { padding: 12px; } .set-panel > .set-section, .set-panel > .set-section + .set-section { padding: 12px; }