Source badge at the start of every log line

Each row now starts with a fixed-width badge naming its source, one color
per source: SYSLOG, DOCKER, KERNEL (dmesg), HOST (/var/log), AUDIT, FILE
and LS. The AUDIT/FILE/LS badges move there from before the app name; those
sources keep their colored edge and tint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude BotandClaude Opus 5.5 committed 2026-10-05 13:16:36 +02:00
1 parent 1a3b3ee23a
commit 2ca89b1c6d
5 files changed
+56 -26

No files matched your search

+6 -2
View File
@@ -92,6 +92,10 @@ une base propre.
Les badges de sévérité `err`/`crit` et `warning` reprennent les couleurs des tags `error` et Les badges de sévérité `err`/`crit` et `warning` reprennent les couleurs des tags `error` et
`warning`. `warning`.
Chaque ligne commence par un badge de largeur fixe qui indique sa source, avec une couleur par
source : **SYSLOG**, **DOCKER**, **KERNEL** (dmesg), **HOST** (fichiers de `/var/log`), **AUDIT**,
**FILE** et **LS** (logs de LogStream lui-même).
Raccourcis : `/` place le curseur dans la recherche, `Esc` la vide. Un clic sur une ligne Raccourcis : `/` place le curseur dans la recherche, `Esc` la vide. Un clic sur une ligne
affiche tous ses champs. affiche tous ses champs.
@@ -278,7 +282,7 @@ source est **désactivée par défaut** : activez-la dans **Réglages > Sources
ne peut pas être résolu), `exe`, `res` (`success` / `failed`, d'après `res=` ou `success=`) et ne peut pas être résolu), `exe`, `res` (`success` / `failed`, d'après `res=` ou `success=`) et
`key` (clé de la règle). La sévérité est `warning` en cas d'échec, `info` sinon ; facility `key` (clé de la règle). La sévérité est `warning` en cas d'échec, `info` sinon ; facility
`authpriv` ; source `audit`. Dans la liste, les lignes d'audit ont un liseré gauche violet, `authpriv` ; source `audit`. Dans la liste, les lignes d'audit ont un liseré gauche violet,
un fond légèrement teinté et un badge **AUDIT** devant l'app ; le filtre **Source** permet de un fond légèrement teinté et le badge de source **AUDIT** ; le filtre **Source** permet de
les afficher seules. les afficher seules.
- **Noms** : `user` vient du format enrichi (`log_format = ENRICHED`, le défaut des versions - **Noms** : `user` vient du format enrichi (`log_format = ENRICHED`, le défaut des versions
récentes d'auditd), sinon du `/etc/passwd` de l'hôte s'il est monté en `/host/etc/passwd`. récentes d'auditd), sinon du `/etc/passwd` de l'hôte s'il est monté en `/host/etc/passwd`.
@@ -338,7 +342,7 @@ conteneur, un **format** (*Automatique* par défaut), un **hôte** facultatif (p
qui portent le nom d'un champ de LogStream (`host`, `app`…) sont renommés `field_host`, qui portent le nom d'un champ de LogStream (`host`, `app`…) sont renommés `field_host`,
`field_app`… `field_app`…
- **Affichage** : source `file`, le chemin dans `log_file`. Dans la liste, ces lignes ont un - **Affichage** : source `file`, le chemin dans `log_file`. Dans la liste, ces lignes ont un
liseré gauche vert, une légère teinte et un badge **FILE** avant l'app ; le filtre **Source** liseré gauche vert, une légère teinte et le badge de source **FILE** ; le filtre **Source**
les affiche seules. les affiche seules.
- À l'activation d'un fichier, les lignes de la dernière heure déjà présentes sont lues d'abord - À l'activation d'un fichier, les lignes de la dernière heure déjà présentes sont lues d'abord
(`FILES_BACKFILL`, `0` = seulement les nouvelles lignes ; les lignes sans horodatage sont alors (`FILES_BACKFILL`, `0` = seulement les nouvelles lignes ; les lignes sans horodatage sont alors
+5 -2
View File
@@ -83,6 +83,9 @@ versions before this change are indexed by their message timestamp; purge them f
Settings to start clean. Settings to start clean.
Severity badges `err`/`crit` and `warning` use the colors of the `error` and `warning` tags. Severity badges `err`/`crit` and `warning` use the colors of the `error` and `warning` tags.
Each row starts with a fixed-width badge naming its source, one color per source: **SYSLOG**,
**DOCKER**, **KERNEL** (dmesg), **HOST** (files of `/var/log`), **AUDIT**, **FILE** and **LS**
(LogStream's own logs).
Shortcuts: `/` focuses the search box, `Esc` clears it. Clicking a row shows all its fields. Shortcuts: `/` focuses the search box, `Esc` clears it. Clicking a row shows all its fields.
@@ -254,7 +257,7 @@ also be changed (saved in `/data/audit.json`; `AUDIT_LOG` sets the default path)
`auid` (login user ID), `user` (its name, or the uid when it cannot be resolved), `exe`, `res` `auid` (login user ID), `user` (its name, or the uid when it cannot be resolved), `exe`, `res`
(`success` / `failed`, from `res=` or `success=`) and `key` (rule key). Severity is `warning` (`success` / `failed`, from `res=` or `success=`) and `key` (rule key). Severity is `warning`
when the result is a failure, `info` otherwise; facility `authpriv`; source `audit`. In the when the result is a failure, `info` otherwise; facility `authpriv`; source `audit`. In the
list, audit lines have a violet left edge, a light tint and an **AUDIT** badge before the app; list, audit lines have a violet left edge, a light tint and the **AUDIT** source badge;
the **Source** filter shows them alone. the **Source** filter shows them alone.
- **Names**: `user` comes from the enriched format (`log_format = ENRICHED`, the default of - **Names**: `user` comes from the enriched format (`log_format = ENRICHED`, the default of
recent auditd versions), else from the host's `/etc/passwd` when it is mounted as recent auditd versions), else from the host's `/etc/passwd` when it is mounted as
@@ -310,7 +313,7 @@ optional **host** (default: `/host/etc/hostname`, else `localhost`) and a switch
not fit the chosen format is kept as plain text. Fields named like LogStream's own ones not fit the chosen format is kept as plain text. Fields named like LogStream's own ones
(`host`, `app`…) are renamed `field_host`, `field_app`… (`host`, `app`…) are renamed `field_host`, `field_app`…
- **Display**: source `file`, the path in `log_file`. In the list these lines have a green left - **Display**: source `file`, the path in `log_file`. In the list these lines have a green left
edge, a light tint and a **FILE** badge before the app; the **Source** filter shows them alone. edge, a light tint and the **FILE** source badge; the **Source** filter shows them alone.
- When a file is turned on, the lines of the last hour already in it are read first - When a file is turned on, the lines of the last hour already in it are read first
(`FILES_BACKFILL`, `0` = only new lines; lines without a timestamp are then left out). The (`FILES_BACKFILL`, `0` = only new lines; lines without a timestamp are then left out). The
start of that hour is found by bisection on the dates of the lines, so a large file that holds start of that hour is found by bisection on the dates of the lines, so a large file that holds
+13 -10
View File
@@ -109,7 +109,7 @@ const I18N = {
file_not_found: 'No file at this path: mount it in the container (see docker-compose.yml) and check the path.', file_not_found: 'No file at this path: mount it in the container (see docker-compose.yml) and check the path.',
file_permission: 'Permission denied: the file must be readable by UID 10001 or the adm group (see the README). ', file_permission: 'Permission denied: the file must be readable by UID 10001 or the adm group (see the README). ',
file_read: 'Cannot read the file: ', file_read: 'Cannot read the file: ',
filesHelp: 'Follows the log file of another program, mounted read-only in the container (e.g. /var/log/traefik:/logs/traefik:ro, then /logs/traefik/access.log here). Once the path is filled in, the file is checked and its format detected from its last lines (JSON lines, logfmt, access logs of nginx, Apache or Traefik, else plain text); choose a format to force it, or a regular expression whose named groups time, level, msg, host and app fill these fields, the other groups giving extra fields. Each line gives an entry (badge FILE, app = the name); indented lines are added to the previous entry; for access logs 5xx are errors and 4xx warnings. When turned on, the last hour of dated lines is read first (FILES_BACKFILL); rotations are followed and reading resumes where it stopped.', filesHelp: 'Follows the log file of another program, mounted read-only in the container (e.g. /var/log/traefik:/logs/traefik:ro, then /logs/traefik/access.log here). Once the path is filled in, the file is checked and its format detected from its last lines (JSON lines, logfmt, access logs of nginx, Apache or Traefik, else plain text); choose a format to force it, or a regular expression whose named groups time, level, msg, host and app fill these fields, the other groups giving extra fields. Each line gives an entry (badge FILE at the start of the line, app = the name); indented lines are added to the previous entry; for access logs 5xx are errors and 4xx warnings. When turned on, the last hour of dated lines is read first (FILES_BACKFILL); rotations are followed and reading resumes where it stopped.',
auditTitle: 'Linux audit (auditd)', auditTitle: 'Linux audit (auditd)',
auditEnabled: 'Collect the audit events of this machine', auditEnabled: 'Collect the audit events of this machine',
auditPath: 'File', auditPath: 'File',
@@ -228,7 +228,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: 'Pattern', colMsg: 'Message', colSrc: 'Source', 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',
@@ -341,7 +341,7 @@ const I18N = {
file_not_found: 'Aucun fichier à ce chemin : montez-le dans le conteneur (voir docker-compose.yml) et vérifiez le chemin.', file_not_found: 'Aucun fichier à ce chemin : montez-le dans le conteneur (voir docker-compose.yml) et vérifiez le chemin.',
file_permission: 'Accès refusé : le fichier doit être lisible par l\'UID 10001 ou le groupe adm (voir le README). ', file_permission: 'Accès refusé : le fichier doit être lisible par l\'UID 10001 ou le groupe adm (voir le README). ',
file_read: 'Lecture du fichier impossible : ', file_read: 'Lecture du fichier impossible : ',
filesHelp: 'Suit le fichier de logs d\'un autre programme, monté en lecture seule dans le conteneur (par ex. /var/log/traefik:/logs/traefik:ro, puis /logs/traefik/access.log ici). Une fois le chemin saisi, le fichier est vérifié et son format détecté à partir de ses dernières lignes (JSON lines, logfmt, journaux d\'accès de nginx, Apache ou Traefik, sinon texte brut) ; choisissez un format pour le forcer, ou une expression régulière dont les groupes nommés time, level, msg, host et app remplissent ces champs, les autres groupes donnant des champs supplémentaires. Chaque ligne donne une entrée (badge FILE, app = le nom) ; les lignes indentées sont rattachées à l\'entrée précédente ; pour les journaux d\'accès, 5xx en erreur et 4xx en warning. À l\'activation, la dernière heure de lignes datées est lue d\'abord (FILES_BACKFILL) ; les rotations sont suivies et la lecture reprend où elle s\'était arrêtée.', filesHelp: 'Suit le fichier de logs d\'un autre programme, monté en lecture seule dans le conteneur (par ex. /var/log/traefik:/logs/traefik:ro, puis /logs/traefik/access.log ici). Une fois le chemin saisi, le fichier est vérifié et son format détecté à partir de ses dernières lignes (JSON lines, logfmt, journaux d\'accès de nginx, Apache ou Traefik, sinon texte brut) ; choisissez un format pour le forcer, ou une expression régulière dont les groupes nommés time, level, msg, host et app remplissent ces champs, les autres groupes donnant des champs supplémentaires. Chaque ligne donne une entrée (badge FILE en début de ligne, app = le nom) ; les lignes indentées sont rattachées à l\'entrée précédente ; pour les journaux d\'accès, 5xx en erreur et 4xx en warning. À l\'activation, la dernière heure de lignes datées est lue d\'abord (FILES_BACKFILL) ; les rotations sont suivies et la lecture reprend où elle s\'était arrêtée.',
auditTitle: 'Audit Linux (auditd)', auditTitle: 'Audit Linux (auditd)',
auditEnabled: 'Collecter les événements d\'audit de cette machine', auditEnabled: 'Collecter les événements d\'audit de cette machine',
auditPath: 'Fichier', auditPath: 'Fichier',
@@ -460,7 +460,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: 'Motif de recherche', colMsg: 'Message', colSrc: 'Source', 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',
@@ -985,15 +985,18 @@ function fmtTime(s) {
return `${day} ${p.h}:${p.m}:${p.s}<span class="ms">.${p.ms}</span>`; return `${day} ${p.h}:${p.m}:${p.s}<span class="ms">.${p.ms}</span>`;
} }
// Sources with their own look in the list: a colored edge and a badge // Every row starts with a badge naming its source, one color per source
// before the app name (the severity badge stays the same). // (--src-<type> in style.css). Audit events, log files and LogStream's own
const SRC_BADGE = { audit: 'AUDIT', logstream: 'LS', file: 'FILE' }; // logs also get a colored edge and a light tint (the severity badge stays the same).
const SRC_BADGE = { syslog: 'SYSLOG', docker: 'DOCKER', kernel: 'KERNEL', host: 'HOST', audit: 'AUDIT', file: 'FILE', logstream: 'LS' };
const srcOf = (r) => (SRC_BADGE[r.source_type] ? r.source_type : 'syslog'); // older entries have no source_type
function rowHTML(r, isNew) { function rowHTML(r, isNew) {
const sev = r.severity || ''; const sev = r.severity || '';
const cls = SEV_CLASS[sev] || 'info'; const cls = SEV_CLASS[sev] || 'info';
const badge = SRC_BADGE[r.source_type]; const src = srcOf(r);
return `<article class="row sev-${cls}${badge ? ' src-' + r.source_type : ''}${isNew ? ' new' : ''}" tabindex="0">` return `<article class="row sev-${cls} src-${src}${isNew ? ' new' : ''}" tabindex="0">`
+ `<b class="src-badge">${SRC_BADGE[src]}</b>`
+ `<time class="rcv" title="${esc(t('rcvTitle'))}">${r.received ? esc(fmtFull(r.received)) : '—'}</time>` + `<time class="rcv" title="${esc(t('rcvTitle'))}">${r.received ? esc(fmtFull(r.received)) : '—'}</time>`
+ `<time class="mt" title="${esc(t('msgTimeTitle'))}">${fmtTime(r.msg_time || r._time)}</time>` + `<time class="mt" title="${esc(t('msgTimeTitle'))}">${fmtTime(r.msg_time || r._time)}</time>`
+ `<span class="sev">${esc(sev || '—')}</span>` + `<span class="sev">${esc(sev || '—')}</span>`
@@ -1001,7 +1004,7 @@ function rowHTML(r, isNew) {
? `<span class="host" data-act="host" title="${esc(hostTitle(r) + ' · ' + t('clickHost'))}">${esc(r.host_name || r.host)}</span>` ? `<span class="host" data-act="host" title="${esc(hostTitle(r) + ' · ' + t('clickHost'))}">${esc(r.host_name || r.host)}</span>`
: '<span class="host"></span>') : '<span class="host"></span>')
+ (r.app + (r.app
? `<span class="app${r.source_type === 'docker' ? ' proj' : ''}" data-act="app"${r.source_type === 'docker' ? ` style="--h:${hueOf(r.compose_project || r.container || r.app)}"` : ''} title="${esc((r.compose_project ? r.compose_project + '/' : '') + (r.container ? r.container + ' · ' : '') + r.app + ' · ' + t('clickApp'))}">${r.source_type === 'docker' ? DOCKER_ICON : ''}${badge ? `<b class="src-badge">${badge}</b>` : ''}${esc(r.app)}</span>` ? `<span class="app${r.source_type === 'docker' ? ' proj' : ''}" data-act="app"${r.source_type === 'docker' ? ` style="--h:${hueOf(r.compose_project || r.container || r.app)}"` : ''} title="${esc((r.compose_project ? r.compose_project + '/' : '') + (r.container ? r.container + ' · ' : '') + r.app + ' · ' + t('clickApp'))}">${r.source_type === 'docker' ? DOCKER_ICON : ''}${esc(r.app)}</span>`
: '<span class="app"></span>') : '<span class="app"></span>')
+ `<span class="codes">${codesHTML(r._msg)}</span>` + `<span class="codes">${codesHTML(r._msg)}</span>`
+ `<div class="msg">${msgHTML(r)}</div>` + `<div class="msg">${msgHTML(r)}</div>`
+1
View File
@@ -140,6 +140,7 @@
<div id="table" class="table" hidden> <div id="table" class="table" hidden>
<div id="listHead" class="list-head" role="presentation"> <div id="listHead" class="list-head" role="presentation">
<span data-col="src"><span class="lbl" data-i18n="colSrc">Source</span></span>
<span data-col="rcv"><span class="lbl" data-i18n="colRcv">Received</span><i class="grip" data-i18n-title="colGrip"></i></span> <span data-col="rcv"><span class="lbl" data-i18n="colRcv">Received</span><i class="grip" data-i18n-title="colGrip"></i></span>
<span data-col="mt"><span class="lbl" data-i18n="colMt">Message time</span><i class="grip" data-i18n-title="colGrip"></i></span> <span data-col="mt"><span class="lbl" data-i18n="colMt">Message time</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="sev"><span class="lbl" data-i18n="colSev">Severity</span><i class="grip" data-i18n-title="colGrip"></i></span>
+31 -12
View File
@@ -22,6 +22,10 @@
--hit: rgba(99, 102, 241, .18); --hit: rgba(99, 102, 241, .18);
--shadow: 0 10px 30px rgba(15, 23, 42, .12); --shadow: 0 10px 30px rgba(15, 23, 42, .12);
--chip-l: 38%; /* lightness of project-colored text (labels, Docker apps) */ --chip-l: 38%; /* lightness of project-colored text (labels, Docker apps) */
--src-syslog: #64748b; /* source badges: syslog received over the network */
--src-docker: #1d6fd6; /* Docker containers */
--src-kernel: #b4540a; /* kernel ring buffer (dmesg) */
--src-host: #b0407a; /* /var/log files of the host system */
--src-audit: #7c4dbd; /* audit events (auditd) */ --src-audit: #7c4dbd; /* audit events (auditd) */
--src-ls: #1f9aa3; /* LogStream internal logs */ --src-ls: #1f9aa3; /* LogStream internal logs */
--src-file: #5f8a1f; /* log files (Settings > Sources > Log files) */ --src-file: #5f8a1f; /* log files (Settings > Sources > Log files) */
@@ -74,6 +78,10 @@
--hit: rgba(129, 140, 248, .28); --hit: rgba(129, 140, 248, .28);
--shadow: 0 10px 30px rgba(0, 0, 0, .5); --shadow: 0 10px 30px rgba(0, 0, 0, .5);
--chip-l: 70%; --chip-l: 70%;
--src-syslog: #94a3b8;
--src-docker: #5aa2f5;
--src-kernel: #e7a25a;
--src-host: #e58bbb;
--src-audit: #a983e6; --src-audit: #a983e6;
--src-ls: #3cc3cc; --src-ls: #3cc3cc;
--src-file: #9ccc54; --src-file: #9ccc54;
@@ -110,10 +118,13 @@
--hit: rgba(129, 140, 248, .28); --hit: rgba(129, 140, 248, .28);
--shadow: 0 10px 30px rgba(0, 0, 0, .5); --shadow: 0 10px 30px rgba(0, 0, 0, .5);
--chip-l: 70%; --chip-l: 70%;
--src-syslog: #94a3b8;
--src-docker: #5aa2f5;
--src-kernel: #e7a25a;
--src-host: #e58bbb;
--src-audit: #a983e6; --src-audit: #a983e6;
--src-ls: #3cc3cc; --src-ls: #3cc3cc;
--src-file: #9ccc54; --src-file: #9ccc54;
--src-file: #9ccc54;
--src-tint: 9%; --src-tint: 9%;
--sev-crit: #fb7185; --sev-crit: #fb7185;
--sev-err: #f87171; --sev-err: #f87171;
@@ -344,12 +355,12 @@ body.busy .progress::after {
.list { margin: 6px 20px 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; } .list { margin: 6px 20px 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.list:empty { display: none; } .list:empty { display: none; }
/* Columns: received, message time, severity, host, app, tag codes, message. Widths come from /* Columns: source, received, message time, severity, host, app, tag codes, message. Widths come from
--col-* (set on #table when a column has been resized, see app.js), shared by --col-* (set on #table when a column has been resized, see app.js), shared by
the header and every row through subgrid so that the columns line up. */ the header and every row through subgrid so that the columns line up. */
.table { .table {
display: grid; display: grid;
grid-template-columns: var(--col-rcv, max-content) var(--col-mt, max-content) var(--col-sev, 4.6rem) grid-template-columns: minmax(3.9rem, max-content) var(--col-rcv, max-content) var(--col-mt, max-content) var(--col-sev, 4.6rem)
var(--col-host, minmax(5rem, 9rem)) var(--col-app, minmax(4rem, 8rem)) var(--codes-w) minmax(0, 1fr); var(--col-host, minmax(5rem, 9rem)) var(--col-app, minmax(4rem, 8rem)) var(--codes-w) minmax(0, 1fr);
column-gap: 12px; column-gap: 12px;
margin: 6px 20px 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); margin: 6px 20px 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
@@ -379,7 +390,7 @@ body.col-resizing, body.col-resizing * { cursor: col-resize !important; user-sel
.row { .row {
display: grid; display: grid;
grid-template-columns: max-content max-content 4.6rem minmax(5rem, 9rem) minmax(4rem, 8rem) var(--codes-w) 1fr; grid-template-columns: max-content max-content max-content 4.6rem minmax(5rem, 9rem) minmax(4rem, 8rem) var(--codes-w) 1fr;
gap: 0 12px; align-items: baseline; gap: 0 12px; align-items: baseline;
padding: 5px 14px 5px 11px; padding: 5px 14px 5px 11px;
border-left: 3px solid transparent; border-left: 3px solid transparent;
@@ -424,19 +435,25 @@ body.col-resizing, body.col-resizing * { cursor: col-resize !important; user-sel
.row.sev-crit, .row.sev-err, .row.sev-warning { border-left-color: var(--sev); } .row.sev-crit, .row.sev-err, .row.sev-warning { border-left-color: var(--sev); }
.row.sev-crit, .row.sev-err { background: color-mix(in srgb, var(--sev) 7%, transparent); } .row.sev-crit, .row.sev-err { background: color-mix(in srgb, var(--sev) 7%, transparent); }
.row .host[data-act], .row .app[data-act] { cursor: pointer; } .row .host[data-act], .row .app[data-act] { cursor: pointer; }
/* Audit events and LogStream internal logs: colored edge (over the severity /* Source badge at the start of every row, fixed width, one color per source.
one), light tint, badge before the app name. */ Audit events, log files and LogStream internal logs also get a colored edge
(over the severity one) and a light tint. */
.row.src-syslog { --src: var(--src-syslog); }
.row.src-docker { --src: var(--src-docker); }
.row.src-kernel { --src: var(--src-kernel); }
.row.src-host { --src: var(--src-host); }
.row.src-audit { --src: var(--src-audit); } .row.src-audit { --src: var(--src-audit); }
.row.src-logstream { --src: var(--src-ls); } .row.src-logstream { --src: var(--src-ls); }
.row.src-file { --src: var(--src-file); } .row.src-file { --src: var(--src-file); }
.row.src-audit, .row.src-logstream, .row.src-file { border-left-color: var(--src); background: color-mix(in srgb, var(--src) var(--src-tint), transparent); } .row.src-audit, .row.src-logstream, .row.src-file { border-left-color: var(--src); background: color-mix(in srgb, var(--src) var(--src-tint), transparent); }
.row.src-audit:hover, .row.src-logstream:hover, .row.src-file:hover { background: color-mix(in srgb, var(--src) 13%, var(--row-hover)); } .row.src-audit:hover, .row.src-logstream:hover, .row.src-file:hover { background: color-mix(in srgb, var(--src) 13%, var(--row-hover)); }
.src-badge { .src-badge {
display: inline-block; margin-right: 5px; padding: 0 4px; border-radius: 4px; vertical-align: 1px; justify-self: start; box-sizing: content-box; width: 6ch; padding: 1px 4px; border-radius: 4px; text-align: center;
font: 700 9.5px/1.45 var(--sans); letter-spacing: .04em; font-family: var(--log-font, var(--mono)); font-size: 10px; font-weight: 700; letter-spacing: .02em; line-height: 1.35;
color: var(--src); background: color-mix(in srgb, var(--src) 16%, transparent); color: var(--src); background: color-mix(in srgb, var(--src) 16%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--src) 40%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--src) 40%, transparent);
} }
:root[data-density="compact"] .row .src-badge { padding-top: 0; padding-bottom: 0; line-height: 1.3; }
.row .raw { .row .raw {
margin-top: 2px; font-size: .85em; line-height: 1.35; color: var(--muted); margin-top: 2px; font-size: .85em; line-height: 1.35; color: var(--muted);
white-space: pre-wrap; word-break: break-all; white-space: pre-wrap; word-break: break-all;
@@ -589,10 +606,11 @@ dialog.settings .dlg > header { padding: 14px 16px 14px 22px; border-bottom: 1px
.set-panel .preview-list { margin: 14px 0 10px; pointer-events: none; } .set-panel .preview-list { margin: 14px 0 10px; pointer-events: none; }
.preview-list .row { .preview-list .row {
cursor: default; cursor: default;
grid-template-columns: max-content max-content minmax(0, max-content) minmax(0, 1fr); grid-template-columns: max-content max-content max-content minmax(0, max-content) minmax(0, 1fr);
grid-template-areas: "rcv sev host app" "msg msg msg msg"; grid-template-areas: "src rcv sev host app" "msg msg msg msg msg";
gap: 3px 10px; gap: 3px 10px;
} }
.preview-list .row .src-badge { grid-area: src; }
.preview-list .row time.rcv { grid-area: rcv; } .preview-list .row time.rcv { grid-area: rcv; }
.preview-list .row time.mt { display: none; } .preview-list .row time.mt { display: none; }
.preview-list .row .sev { grid-area: sev; } .preview-list .row .sev { grid-area: sev; }
@@ -827,10 +845,11 @@ input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2
.table .list { display: block; margin: 0; } .table .list { display: block; margin: 0; }
.list-head { display: none; } .list-head { display: none; }
.row, .table .row { .row, .table .row {
grid-template-columns: auto auto 1fr auto; grid-template-columns: auto auto auto 1fr auto;
grid-template-areas: "rcv sev host codes" "msg msg msg msg"; grid-template-areas: "src rcv sev host codes" "msg msg msg msg msg";
gap: 3px 8px; padding: 8px 12px 8px 10px; gap: 3px 8px; padding: 8px 12px 8px 10px;
} }
.row .src-badge { grid-area: src; }
.row time.rcv { grid-area: rcv; } .row time.rcv { grid-area: rcv; }
.row time.mt { display: none; } /* still shown in the details panel */ .row time.mt { display: none; } /* still shown in the details panel */
.row .sev { grid-area: sev; } .row .sev { grid-area: sev; }