chore: merge main into next

This commit is contained in:
peaklabs-dev
2026-10-30 22:16:19 +01:00
6 changed files with 39 additions and 4 deletions
+21
View File
@@ -45,7 +45,28 @@ export function isExpandableLog(content, truncated) {
return truncated || formatLogDetails(content) !== content;
}
const LEVEL_FIELD = /\b(?:level|lvl|severity|loglevel)"?\s*[=:]\s*"?([a-z]+)\b/;
// A level word at the start of the line, after timestamps: `2026-10-30t16:35:49z info msg`, `[debug] msg`.
const LEADING_LEVEL = /^(?:\S*\d\S*\s+){0,3}[[(<]?([a-z]+)[\])>:]?(?:\s|$)/;
const LEVEL_NAMES = {
error: ['error', 'err', 'fatal', 'panic', 'critical', 'crit', 'alert', 'emerg', 'emergency'],
warning: ['warn', 'warning', 'wrn'],
debug: ['debug', 'dbg', 'trace', 'verbose'],
info: ['info', 'inf', 'information', 'notice'],
};
/**
* Prefer the declared level (`level=debug`, `"level":"debug"`, a leading
* `INFO`) over keywords, so `INFO ... 0 error events` or `level=debug err=EOF`
* is not shown as an error.
*/
export function getLogLevel(content) {
for (const pattern of [LEVEL_FIELD, LEADING_LEVEL]) {
const declared = content.match(pattern)?.[1];
for (const [level, names] of Object.entries(LEVEL_NAMES)) {
if (names.includes(declared)) return level;
}
}
if (/\b(error|err|failed|failure|exception|fatal|panic|critical)\b/.test(content)) return 'error';
if (/\b(warn|warning|wrn|caution)\b/.test(content)) return 'warning';
if (/\b(debug|dbg|trace|verbose)\b/.test(content)) return 'debug';
+8
View File
@@ -43,6 +43,14 @@ test('detects log levels by keyword', () => {
assert.equal(getLogLevel('warning: disk'), 'warning');
assert.equal(getLogLevel('trace id=1'), 'debug');
assert.equal(getLogLevel('ready'), 'info');
assert.equal(getLogLevel('time=2026-10-30t16:35:49z level=debug msg="read failed" err=eof'), 'debug');
assert.equal(getLogLevel('{"level":"info","msg":"conn closed","err":"eof"}'), 'info');
assert.equal(getLogLevel('level=error msg="db down"'), 'error');
assert.equal(getLogLevel('{"level":50,"msg":"failed"}'), 'error');
assert.equal(getLogLevel('2026-10-29t03:17:00.849341z info logs: pruned 840 requests, 0 error events'), 'info');
assert.equal(getLogLevel('2026/10/30 16:35:49 [debug] read failed'), 'debug');
assert.equal(getLogLevel('warn: retry failed'), 'warning');
assert.equal(getLogLevel('connection failed'), 'error');
});
test('filters by level and case-insensitive query', () => {
+2 -1
View File
@@ -244,7 +244,8 @@ $appListboxOptions = array_merge(
icon-name="unordered-list" />
@else
@php $maxAppRequests = max(1, (int) collect($topApps)->max('requests')); @endphp
<div x-data="trafficTable()">
{{-- Keyed so the lazy-load morph does not reuse the placeholder skeleton nodes. --}}
<div wire:key="analytics-apps-table" x-data="trafficTable()">
<div class="flex items-center gap-3 px-4 py-2 text-[11px] font-medium text-neutral-500 dark:text-fg-dim">
@include('livewire.traffic._sort-header', ['column' => 'name', 'label' => 'Application / service', 'text' => true, 'class' => 'min-w-0 flex-1'])
<span class="hidden w-16 shrink-0 sm:inline" aria-hidden="true"></span>
@@ -21,7 +21,8 @@
<x-empty size="sm" title="No data"
:description="'No '.strtolower($label).' data for the selected range.'" icon-name="network" />
@else
<div x-data="trafficTable()">
{{-- Keyed so the lazy-load morph does not reuse the placeholder skeleton nodes. --}}
<div wire:key="analytics-{{ $dimension }}-table" x-data="trafficTable()">
<div class="flex items-center gap-3 px-4 py-2 text-[11px] font-medium text-neutral-500 dark:text-fg-dim">
@include('livewire.traffic._sort-header', ['column' => 'name', 'label' => 'Name', 'text' => true, 'class' => 'min-w-0 flex-1'])
<span class="hidden w-16 shrink-0 sm:inline" aria-hidden="true"></span>
@@ -16,7 +16,8 @@
<x-empty size="sm" title="No host data" description="No hostnames were recorded for the selected range."
icon-name="unordered-list" />
@else
<div x-data="trafficTable()">
{{-- Keyed so the lazy-load morph does not reuse the placeholder skeleton nodes. --}}
<div wire:key="{{ $keyPrefix }}-table" x-data="trafficTable()">
<div class="flex items-center gap-3 px-4 py-2 text-[11px] font-medium text-neutral-500 dark:text-fg-dim">
@include('livewire.traffic._sort-header', ['column' => 'name', 'label' => 'Host', 'text' => true, 'class' => 'min-w-0 flex-1'])
<span class="hidden w-16 shrink-0 sm:inline" aria-hidden="true"></span>
@@ -20,7 +20,10 @@
<x-empty size="sm" title="No path data" description="No requests were recorded for the selected range."
icon-name="unordered-list" />
@else
<div x-data="trafficTable()">
{{-- Keyed so the lazy-load morph inserts fresh nodes instead of reusing the
placeholder skeleton; reused nodes keep their old Alpine scope and the
pager and sort buttons stop working. --}}
<div wire:key="{{ $keyPrefix }}-table" x-data="trafficTable()">
<div class="flex items-center gap-3 px-4 py-2 text-[11px] font-medium text-neutral-500 dark:text-fg-dim">
@include('livewire.traffic._sort-header', ['column' => 'name', 'label' => 'Path', 'text' => true, 'class' => 'min-w-0 flex-1'])
<span class="hidden w-16 shrink-0 sm:inline" aria-hidden="true"></span>