feat(logs-page): add structured filters panel with toggle functionality and localization

This commit is contained in:
Supra4E8C
2026-03-07 23:55:20 +08:00
parent a4dee2ea82
commit cf700f45b3
5 changed files with 102 additions and 2 deletions
+4
View File
@@ -1043,6 +1043,10 @@
"show_raw_logs": "Show Raw Logs",
"show_raw_logs_hint": "Show original log text for easier multi-line copy",
"search_placeholder": "Search logs by content or keyword",
"filter_panel_title": "Structured Filters",
"filter_panel_expand": "Expand structured filters",
"filter_panel_collapse": "Collapse structured filters",
"filter_panel_active_count": "{{count}} active",
"filter_method": "Method",
"filter_status": "Status",
"filter_path": "Path",
+4
View File
@@ -1046,6 +1046,10 @@
"show_raw_logs": "Показать исходные журналы",
"show_raw_logs_hint": "Показать текст журнала без обработки для удобного копирования в несколько строк",
"search_placeholder": "Искать по содержимому или ключевым словам",
"filter_panel_title": "Структурные фильтры",
"filter_panel_expand": "Развернуть структурные фильтры",
"filter_panel_collapse": "Свернуть структурные фильтры",
"filter_panel_active_count": "Активно: {{count}}",
"filter_method": "Метод",
"filter_status": "Статус",
"filter_path": "Путь",
+4
View File
@@ -1043,6 +1043,10 @@
"show_raw_logs": "显示原始日志",
"show_raw_logs_hint": "直接显示原始日志文本,方便多行复制",
"search_placeholder": "搜索日志内容或关键字",
"filter_panel_title": "结构化筛选",
"filter_panel_expand": "展开结构化筛选",
"filter_panel_collapse": "收起结构化筛选",
"filter_panel_active_count": "已选 {{count}} 项",
"filter_method": "请求方法",
"filter_status": "状态码",
"filter_path": "路径",
+41
View File
@@ -121,6 +121,33 @@
max-width: 420px;
}
.filterPanelHeader {
display: flex;
align-items: center;
flex: 1 1 100%;
}
.filterPanelToggle {
white-space: nowrap;
}
.filterPanelButtonContent {
display: inline-flex;
align-items: center;
gap: 6px;
}
.filterPanelCount {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: $radius-full;
background: rgba($primary-color, 0.12);
color: var(--primary-color);
font-size: 12px;
line-height: 1.2;
}
.structuredFilters {
display: flex;
flex-direction: column;
@@ -200,6 +227,20 @@
}
@include mobile {
.filterPanelHeader {
width: 100%;
}
.filterPanelToggle {
width: 100%;
}
.filterPanelButtonContent {
width: 100%;
justify-content: space-between;
flex-wrap: wrap;
}
.filterChipGroup {
flex-direction: column;
gap: 6px;
+49 -2
View File
@@ -8,16 +8,20 @@ import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal';
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
import {
IconDownload,
IconChevronDown,
IconChevronUp,
IconCode,
IconDownload,
IconEyeOff,
IconRefreshCw,
IconSearch,
IconSlidersHorizontal,
IconTimer,
IconTrash2,
IconX,
} from '@/components/ui/icons';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
import { logsApi } from '@/services/api/logs';
import { copyToClipboard } from '@/utils/clipboard';
@@ -79,6 +83,10 @@ export function LogsPage() {
const deferredSearchQuery = useDeferredValue(searchQuery);
const [hideManagementLogs, setHideManagementLogs] = useState(true);
const [showRawLogs, setShowRawLogs] = useState(false);
const [structuredFiltersExpanded, setStructuredFiltersExpanded] = useLocalStorage(
'logsPage.structuredFiltersExpanded',
true
);
const [errorLogs, setErrorLogs] = useState<ErrorLogItem[]>([]);
const [loadingErrors, setLoadingErrors] = useState(false);
const [errorLogsError, setErrorLogsError] = useState('');
@@ -305,6 +313,9 @@ export function LogsPage() {
}, [baseLines, hideManagementLogs, trimmedSearchQuery]);
const filters = useLogFilters({ parsedLines: parsedSearchLines });
const structuredFiltersPanelId = 'logs-structured-filters';
const structuredFilterCount =
filters.methodFilters.length + filters.statusFilters.length + filters.pathFilters.length;
const { filteredParsedLines, filteredLines, removedCount } = useMemo(() => {
const filteredParsed = parsedSearchLines.filter((line) => {
@@ -498,7 +509,43 @@ export function LogsPage() {
/>
</div>
<div className={styles.structuredFilters}>
<div className={styles.filterPanelHeader}>
<Button
type="button"
variant="secondary"
size="sm"
className={styles.filterPanelToggle}
onClick={() => setStructuredFiltersExpanded((prev) => !prev)}
aria-expanded={structuredFiltersExpanded}
aria-controls={structuredFiltersPanelId}
title={
structuredFiltersExpanded
? t('logs.filter_panel_collapse')
: t('logs.filter_panel_expand')
}
>
<span className={styles.filterPanelButtonContent}>
<IconSlidersHorizontal size={16} />
<span>{t('logs.filter_panel_title')}</span>
{structuredFilterCount > 0 && (
<span className={styles.filterPanelCount}>
{t('logs.filter_panel_active_count', { count: structuredFilterCount })}
</span>
)}
{structuredFiltersExpanded ? (
<IconChevronUp size={16} />
) : (
<IconChevronDown size={16} />
)}
</span>
</Button>
</div>
<div
id={structuredFiltersPanelId}
className={styles.structuredFilters}
hidden={!structuredFiltersExpanded}
>
<div className={styles.filterChipGroup}>
<span className={styles.filterChipLabel}>{t('logs.filter_method')}</span>
<div className={styles.filterChipList}>