feat(ui): add log filter based on log level (#8784)

This commit is contained in:
Andras Bacsai 2026-03-12 13:39:18 +01:00 committed by GitHub
commit 2b65eaa2b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,6 +8,7 @@
rafId: null,
scrollDebounce: null,
colorLogs: localStorage.getItem('coolify-color-logs') === 'true',
logFilters: JSON.parse(localStorage.getItem('coolify-log-filters')) || {error: true, warning: true, debug: true, info: true},
searchQuery: '',
matchCount: 0,
containerName: '{{ $container ?? "logs" }}',
@ -70,6 +71,17 @@
}
}, 150);
},
getLogLevel(content) {
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';
return 'info';
},
toggleLogFilter(level) {
this.logFilters[level] = !this.logFilters[level];
localStorage.setItem('coolify-log-filters', JSON.stringify(this.logFilters));
this.applySearch();
},
toggleColorLogs() {
this.colorLogs = !this.colorLogs;
localStorage.setItem('coolify-color-logs', this.colorLogs);
@ -81,17 +93,11 @@
const lines = logs.querySelectorAll('[data-log-line]');
lines.forEach(line => {
const content = (line.dataset.logContent || '').toLowerCase();
const level = this.getLogLevel(content);
line.dataset.logLevel = level;
line.classList.remove('log-error', 'log-warning', 'log-debug', 'log-info');
if (!this.colorLogs) return;
if (/\b(error|err|failed|failure|exception|fatal|panic|critical)\b/.test(content)) {
line.classList.add('log-error');
} else if (/\b(warn|warning|wrn|caution)\b/.test(content)) {
line.classList.add('log-warning');
} else if (/\b(debug|dbg|trace|verbose)\b/.test(content)) {
line.classList.add('log-debug');
} else if (/\b(info|inf|notice)\b/.test(content)) {
line.classList.add('log-info');
}
line.classList.add('log-' + level);
});
},
hasActiveLogSelection() {
@ -118,7 +124,10 @@
lines.forEach(line => {
const content = (line.dataset.logContent || '').toLowerCase();
const textSpan = line.querySelector('[data-line-text]');
const matches = !query || content.includes(query);
const level = line.dataset.logLevel || this.getLogLevel(content);
const passesFilter = this.logFilters[level] !== false;
const matchesSearch = !query || content.includes(query);
const matches = passesFilter && matchesSearch;
line.classList.toggle('hidden', !matches);
if (matches && query) count++;
@ -389,6 +398,52 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-
d="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42" />
</svg>
</button>
<div x-data="{ filterOpen: false }" class="relative">
<button x-on:click="filterOpen = !filterOpen" title="Filter Log Levels"
:class="Object.values(logFilters).some(v => !v) ? '!text-warning' : ''"
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
</svg>
</button>
<div x-show="filterOpen" x-on:click.away="filterOpen = false"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute right-0 z-50 mt-2 w-max origin-top-right rounded-md bg-white dark:bg-coolgray-200 shadow-lg ring-1 ring-neutral-200 dark:ring-coolgray-300 focus:outline-none">
<div class="py-1">
<label class="flex items-center gap-2 px-4 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-coolgray-300 cursor-pointer select-none">
<input type="checkbox" :checked="logFilters.error" x-on:change="toggleLogFilter('error')"
class="rounded border-gray-300 dark:border-gray-600 text-warning focus:ring-warning dark:bg-coolgray-300" />
<span class="w-2.5 h-2.5 rounded-full bg-red-500"></span>
Error
</label>
<label class="flex items-center gap-2 px-4 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-coolgray-300 cursor-pointer select-none">
<input type="checkbox" :checked="logFilters.warning" x-on:change="toggleLogFilter('warning')"
class="rounded border-gray-300 dark:border-gray-600 text-warning focus:ring-warning dark:bg-coolgray-300" />
<span class="w-2.5 h-2.5 rounded-full bg-yellow-500"></span>
Warning
</label>
<label class="flex items-center gap-2 px-4 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-coolgray-300 cursor-pointer select-none">
<input type="checkbox" :checked="logFilters.debug" x-on:change="toggleLogFilter('debug')"
class="rounded border-gray-300 dark:border-gray-600 text-warning focus:ring-warning dark:bg-coolgray-300" />
<span class="w-2.5 h-2.5 rounded-full bg-purple-500"></span>
Debug
</label>
<label class="flex items-center gap-2 px-4 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-coolgray-300 cursor-pointer select-none">
<input type="checkbox" :checked="logFilters.info" x-on:change="toggleLogFilter('info')"
class="rounded border-gray-300 dark:border-gray-600 text-warning focus:ring-warning dark:bg-coolgray-300" />
<span class="w-2.5 h-2.5 rounded-full bg-blue-500"></span>
Info
</label>
</div>
</div>
</div>
<button title="Follow Logs" :class="alwaysScroll ? '!text-warning' : ''"
x-on:click="toggleScroll"
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">