feat: add toggle for showing raw logs and update log display logic

This commit is contained in:
LTbinglingfeng
2026-01-30 00:01:12 +08:00
parent 94f0038f19
commit 34b6d114d3
4 changed files with 149 additions and 95 deletions

View File

@@ -752,6 +752,8 @@
"loaded_lines": "Loaded: {{count}} lines", "loaded_lines": "Loaded: {{count}} lines",
"filtered_lines": "Filtered: {{count}} lines", "filtered_lines": "Filtered: {{count}} lines",
"hide_management_logs": "Hide {{prefix}} logs", "hide_management_logs": "Hide {{prefix}} logs",
"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", "search_placeholder": "Search logs by content or keyword",
"search_empty_title": "No matching logs found", "search_empty_title": "No matching logs found",
"search_empty_desc": "Try a different keyword or clear the filters.", "search_empty_desc": "Try a different keyword or clear the filters.",

View File

@@ -752,6 +752,8 @@
"loaded_lines": "已载入 {{count}} 行", "loaded_lines": "已载入 {{count}} 行",
"filtered_lines": "已过滤 {{count}} 行", "filtered_lines": "已过滤 {{count}} 行",
"hide_management_logs": "屏蔽 {{prefix}} 日志", "hide_management_logs": "屏蔽 {{prefix}} 日志",
"show_raw_logs": "显示原始日志",
"show_raw_logs_hint": "直接显示原始日志文本,方便多行复制",
"search_placeholder": "搜索日志内容或关键字", "search_placeholder": "搜索日志内容或关键字",
"search_empty_title": "未找到匹配的日志", "search_empty_title": "未找到匹配的日志",
"search_empty_desc": "尝试更换关键字或清空筛选条件。", "search_empty_desc": "尝试更换关键字或清空筛选条件。",

View File

@@ -267,6 +267,30 @@
flex-direction: column; flex-direction: column;
} }
.rawLog {
margin: 0;
padding: 10px 12px;
cursor: text;
user-select: text;
white-space: pre;
color: var(--text-primary);
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
font-size: 12.5px;
line-height: 1.45;
@include tablet {
padding: 8px 10px;
font-size: 12px;
}
@include mobile {
padding: 8px 10px;
font-size: 11.5px;
}
}
.logRow { .logRow {
display: grid; display: grid;
grid-template-columns: 170px 1fr; grid-template-columns: 170px 1fr;

View File

@@ -9,6 +9,7 @@ import { Modal } from '@/components/ui/Modal';
import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
import { import {
IconDownload, IconDownload,
IconCode,
IconEyeOff, IconEyeOff,
IconRefreshCw, IconRefreshCw,
IconSearch, IconSearch,
@@ -383,6 +384,7 @@ export function LogsPage() {
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const deferredSearchQuery = useDeferredValue(searchQuery); const deferredSearchQuery = useDeferredValue(searchQuery);
const [hideManagementLogs, setHideManagementLogs] = useState(true); const [hideManagementLogs, setHideManagementLogs] = useState(true);
const [showRawLogs, setShowRawLogs] = useState(false);
const [errorLogs, setErrorLogs] = useState<ErrorLogItem[]>([]); const [errorLogs, setErrorLogs] = useState<ErrorLogItem[]>([]);
const [loadingErrors, setLoadingErrors] = useState(false); const [loadingErrors, setLoadingErrors] = useState(false);
const [errorLogsError, setErrorLogsError] = useState(''); const [errorLogsError, setErrorLogsError] = useState('');
@@ -632,10 +634,12 @@ export function LogsPage() {
return { filteredLines: working, removedCount: removed }; return { filteredLines: working, removedCount: removed };
}, [baseLines, hideManagementLogs, trimmedSearchQuery]); }, [baseLines, hideManagementLogs, trimmedSearchQuery]);
const parsedVisibleLines = useMemo( const parsedVisibleLines = useMemo(() => {
() => filteredLines.map((line) => parseLogLine(line)), if (showRawLogs) return [];
[filteredLines] return filteredLines.map((line) => parseLogLine(line));
); }, [filteredLines, showRawLogs]);
const rawVisibleText = useMemo(() => filteredLines.join('\n'), [filteredLines]);
const canLoadMore = !isSearching && logState.visibleFrom > 0; const canLoadMore = !isSearching && logState.visibleFrom > 0;
@@ -817,6 +821,22 @@ export function LogsPage() {
} }
/> />
<ToggleSwitch
checked={showRawLogs}
onChange={setShowRawLogs}
label={
<span
className={styles.switchLabel}
title={t('logs.show_raw_logs_hint', {
defaultValue: 'Show original log text for easier multi-line copy',
})}
>
<IconCode size={16} />
{t('logs.show_raw_logs', { defaultValue: 'Show raw logs' })}
</span>
}
/>
<div className={styles.toolbar}> <div className={styles.toolbar}>
<Button <Button
variant="secondary" variant="secondary"
@@ -870,14 +890,14 @@ export function LogsPage() {
{loading ? ( {loading ? (
<div className="hint">{t('logs.loading')}</div> <div className="hint">{t('logs.loading')}</div>
) : logState.buffer.length > 0 && parsedVisibleLines.length > 0 ? ( ) : logState.buffer.length > 0 && filteredLines.length > 0 ? (
<div ref={logViewerRef} className={styles.logPanel} onScroll={handleLogScroll}> <div ref={logViewerRef} className={styles.logPanel} onScroll={handleLogScroll}>
{canLoadMore && ( {canLoadMore && (
<div className={styles.loadMoreBanner}> <div className={styles.loadMoreBanner}>
<span>{t('logs.load_more_hint')}</span> <span>{t('logs.load_more_hint')}</span>
<div className={styles.loadMoreStats}> <div className={styles.loadMoreStats}>
<span> <span>
{t('logs.loaded_lines', { count: parsedVisibleLines.length })} {t('logs.loaded_lines', { count: filteredLines.length })}
</span> </span>
{removedCount > 0 && ( {removedCount > 0 && (
<span className={styles.loadMoreCount}> <span className={styles.loadMoreCount}>
@@ -890,6 +910,11 @@ export function LogsPage() {
</div> </div>
</div> </div>
)} )}
{showRawLogs ? (
<pre className={styles.rawLog} spellCheck={false}>
{rawVisibleText}
</pre>
) : (
<div className={styles.logList}> <div className={styles.logList}>
{parsedVisibleLines.map((line, index) => { {parsedVisibleLines.map((line, index) => {
const rowClassNames = [styles.logRow]; const rowClassNames = [styles.logRow];
@@ -987,6 +1012,7 @@ export function LogsPage() {
); );
})} })}
</div> </div>
)}
</div> </div>
) : logState.buffer.length > 0 ? ( ) : logState.buffer.length > 0 ? (
<EmptyState <EmptyState