Skip to content

Commit ef45681

Browse files
EstrellaXDclaude
andcommitted
feat(ui): add log level filter in log view
Allow filtering logs by level (INFO, WARNING, ERROR, DEBUG) with color-coded filter chips. Multiple levels can be selected simultaneously. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d2bf733 commit ef45681

3 files changed

Lines changed: 192 additions & 3 deletions

File tree

webui/src/i18n/en.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,10 @@
147147
},
148148
"log": {
149149
"bug_repo": "Bug Report",
150+
"clear_filters": "Clear",
150151
"contact_info": "Contact Infomation",
151152
"copy": "Copy",
153+
"filter_level": "Level",
152154
"go": "Go",
153155
"join": "Join",
154156
"reset": "Reset",
@@ -241,13 +243,20 @@
241243
"sun": "Sun"
242244
},
243245
"unknown": "Unknown",
246+
"unknown_title": "Unknown Title",
244247
"today": "Today",
245248
"empty": "No anime",
246249
"refresh": "Refresh schedule",
247250
"no_data": "No schedule data available",
251+
"loading": "Loading...",
252+
"tracked": "Tracked",
253+
"tracked_count": "{count} tracked",
254+
"total_count": "{count} total",
255+
"search_title": "Search and Add Anime",
256+
"no_search_results": "No results found. Try different keywords.",
248257
"empty_state": {
249258
"title": "No Schedule Yet",
250-
"subtitle": "Add anime from RSS to see your weekly schedule"
259+
"subtitle": "Click refresh to fetch this season's broadcast data"
251260
}
252261
},
253262
"setup": {

webui/src/i18n/zh-CN.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,10 @@
147147
},
148148
"log": {
149149
"bug_repo": "Bug 反馈",
150+
"clear_filters": "清除",
150151
"contact_info": "联系方式",
151152
"copy": "复制",
153+
"filter_level": "级别",
152154
"go": "访问",
153155
"join": "加入",
154156
"reset": "重置",
@@ -241,13 +243,20 @@
241243
"sun": ""
242244
},
243245
"unknown": "未知",
246+
"unknown_title": "未知标题",
244247
"today": "今天",
245248
"empty": "今日无番",
246249
"refresh": "刷新放送表",
247250
"no_data": "暂无放送数据",
251+
"loading": "加载中...",
252+
"tracked": "已追踪",
253+
"tracked_count": "已追踪 {count} 部",
254+
"total_count": "共 {count} 部",
255+
"search_title": "搜索并添加番剧",
256+
"no_search_results": "未找到搜索结果,请尝试其他关键词",
248257
"empty_state": {
249258
"title": "暂无放送表",
250-
"subtitle": "从 RSS 添加番剧后即可查看每周放送时间"
259+
"subtitle": "点击刷新按钮获取本季度放送数据"
251260
}
252261
},
253262
"setup": {

webui/src/pages/index/log.vue

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ const { onUpdate, offUpdate, reset, copy, getLog } = useLogStore();
99
const { log } = storeToRefs(useLogStore());
1010
const { version } = useAppInfo();
1111
12+
// Filter states
13+
const selectedLevels = ref<string[]>([]);
14+
15+
// Log levels
16+
const logLevels = ['INFO', 'WARNING', 'ERROR', 'DEBUG'];
17+
1218
const formatLog = computed(() => {
1319
const list = log.value
1420
.trim()
@@ -30,6 +36,31 @@ const formatLog = computed(() => {
3036
});
3137
});
3238
39+
// Filtered logs based on selected levels
40+
const filteredLog = computed(() => {
41+
if (selectedLevels.value.length === 0) {
42+
return formatLog.value;
43+
}
44+
return formatLog.value.filter((entry) =>
45+
selectedLevels.value.includes(entry.type)
46+
);
47+
});
48+
49+
// Toggle level filter
50+
function toggleLevel(level: string) {
51+
const index = selectedLevels.value.indexOf(level);
52+
if (index === -1) {
53+
selectedLevels.value.push(level);
54+
} else {
55+
selectedLevels.value.splice(index, 1);
56+
}
57+
}
58+
59+
// Clear all filters
60+
function clearFilters() {
61+
selectedLevels.value = [];
62+
}
63+
3364
function typeColor(type: string) {
3465
const M: Record<string, string> = {
3566
INFO: 'var(--color-primary)',
@@ -74,9 +105,38 @@ onDeactivated(() => {
74105
<div class="page-log">
75106
<div class="log-layout">
76107
<ab-container :title="$t('log.title')" class="log-main">
108+
<!-- Level Filter Section -->
109+
<div class="log-filters">
110+
<div class="filter-group">
111+
<span class="filter-label">{{ $t('log.filter_level') }}</span>
112+
<div class="filter-chips">
113+
<button
114+
v-for="level in logLevels"
115+
:key="level"
116+
class="filter-chip"
117+
:class="{
118+
active: selectedLevels.includes(level),
119+
[`level-${level.toLowerCase()}`]: true,
120+
}"
121+
@click="toggleLevel(level)"
122+
>
123+
{{ level }}
124+
</button>
125+
</div>
126+
</div>
127+
128+
<button
129+
v-if="selectedLevels.length > 0"
130+
class="clear-filters"
131+
@click="clearFilters"
132+
>
133+
{{ $t('log.clear_filters') }}
134+
</button>
135+
</div>
136+
77137
<div ref="logContainer" class="log-viewer">
78138
<div class="log-content">
79-
<template v-for="i in formatLog" :key="i.index">
139+
<template v-for="i in filteredLog" :key="i.index">
80140
<div
81141
class="log-entry"
82142
:style="{ color: typeColor(i.type) }"
@@ -252,6 +312,117 @@ onDeactivated(() => {
252312
font-size: 13px;
253313
}
254314
315+
.log-filters {
316+
display: flex;
317+
flex-direction: column;
318+
gap: 12px;
319+
margin-bottom: 12px;
320+
padding-bottom: 12px;
321+
border-bottom: 1px solid var(--color-border);
322+
}
323+
324+
.filter-group {
325+
display: flex;
326+
flex-direction: column;
327+
gap: 8px;
328+
329+
@include forDesktop {
330+
flex-direction: row;
331+
align-items: center;
332+
}
333+
}
334+
335+
.filter-label {
336+
font-size: 13px;
337+
font-weight: 500;
338+
color: var(--color-text-muted);
339+
min-width: 60px;
340+
}
341+
342+
.filter-chips {
343+
display: flex;
344+
flex-wrap: wrap;
345+
gap: 6px;
346+
}
347+
348+
.filter-chip {
349+
padding: 4px 12px;
350+
border-radius: var(--radius-sm);
351+
border: 1px solid var(--color-border);
352+
background: transparent;
353+
font-size: 12px;
354+
cursor: pointer;
355+
transition: all var(--transition-fast);
356+
color: var(--color-text);
357+
358+
&:hover {
359+
border-color: var(--color-primary);
360+
}
361+
362+
&.active {
363+
background: var(--color-primary);
364+
border-color: var(--color-primary);
365+
color: white;
366+
}
367+
368+
&.level-info {
369+
&:hover,
370+
&.active {
371+
border-color: var(--color-primary);
372+
}
373+
&.active {
374+
background: var(--color-primary);
375+
}
376+
}
377+
378+
&.level-warning {
379+
&:hover,
380+
&.active {
381+
border-color: var(--color-warning);
382+
}
383+
&.active {
384+
background: var(--color-warning);
385+
}
386+
}
387+
388+
&.level-error {
389+
&:hover,
390+
&.active {
391+
border-color: var(--color-danger);
392+
}
393+
&.active {
394+
background: var(--color-danger);
395+
}
396+
}
397+
398+
&.level-debug {
399+
&:hover,
400+
&.active {
401+
border-color: var(--color-text-muted);
402+
}
403+
&.active {
404+
background: var(--color-text-muted);
405+
}
406+
}
407+
}
408+
409+
.clear-filters {
410+
align-self: flex-start;
411+
padding: 4px 12px;
412+
border-radius: var(--radius-sm);
413+
border: none;
414+
background: var(--color-danger-light);
415+
color: var(--color-danger);
416+
font-size: 12px;
417+
cursor: pointer;
418+
transition: all var(--transition-fast);
419+
420+
&:hover {
421+
background: var(--color-danger);
422+
color: white;
423+
}
424+
}
425+
255426
.log-actions {
256427
display: flex;
257428
justify-content: flex-end;

0 commit comments

Comments
 (0)