diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 4cbbda51..87379e65 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -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", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 4861fe6a..261b12c1 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -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": "Путь", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index bab2763f..ca186657 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -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": "路径", diff --git a/src/pages/LogsPage.module.scss b/src/pages/LogsPage.module.scss index 69851357..f7c1baab 100644 --- a/src/pages/LogsPage.module.scss +++ b/src/pages/LogsPage.module.scss @@ -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; diff --git a/src/pages/LogsPage.tsx b/src/pages/LogsPage.tsx index b38cc204..0601a523 100644 --- a/src/pages/LogsPage.tsx +++ b/src/pages/LogsPage.tsx @@ -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([]); 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() { /> -
+
+ +
+ +