diff --git a/app.js b/app.js index cb46852..f185a93 100644 --- a/app.js +++ b/app.js @@ -77,7 +77,9 @@ class CLIProxyManager { this.logsRefreshTimer = null; // 当前展示的日志行 + this.allLogLines = []; this.displayedLogLines = []; + this.logSearchQuery = ''; this.maxDisplayLogLines = MAX_LOG_LINES; this.logFetchLimit = LOG_FETCH_LIMIT; @@ -333,6 +335,7 @@ class CLIProxyManager { const downloadLogs = document.getElementById('download-logs'); const clearLogs = document.getElementById('clear-logs'); const logsAutoRefreshToggle = document.getElementById('logs-auto-refresh-toggle'); + const logsSearchInput = document.getElementById('logs-search-input'); if (refreshLogs) { refreshLogs.addEventListener('click', () => this.refreshLogs()); @@ -349,6 +352,14 @@ class CLIProxyManager { if (logsAutoRefreshToggle) { logsAutoRefreshToggle.addEventListener('change', (e) => this.toggleLogsAutoRefresh(e.target.checked)); } + if (logsSearchInput) { + const debouncedLogSearch = this.debounce((value) => { + this.updateLogSearchQuery(value); + }, 200); + logsSearchInput.addEventListener('input', (e) => { + debouncedLogSearch(e?.target?.value ?? ''); + }); + } // API 密钥管理 const addApiKey = document.getElementById('add-api-key'); diff --git a/i18n.js b/i18n.js index 5f8be4f..9b74d3d 100644 --- a/i18n.js +++ b/i18n.js @@ -529,6 +529,9 @@ const i18n = { 'logs.auto_refresh': '自动刷新', 'logs.auto_refresh_enabled': '自动刷新已开启', 'logs.auto_refresh_disabled': '自动刷新已关闭', + 'logs.search_placeholder': '搜索日志内容或关键字', + 'logs.search_empty_title': '未找到匹配的日志', + 'logs.search_empty_desc': '尝试更换关键字或清空搜索条件。', 'logs.lines': '行', 'logs.removed': '已删除', 'logs.upgrade_required_title': '需要升级 CLI Proxy API', @@ -1161,6 +1164,9 @@ const i18n = { 'logs.auto_refresh': 'Auto Refresh', 'logs.auto_refresh_enabled': 'Auto refresh enabled', 'logs.auto_refresh_disabled': 'Auto refresh disabled', + 'logs.search_placeholder': 'Search logs by content or keyword', + 'logs.search_empty_title': 'No matching logs found', + 'logs.search_empty_desc': 'Try a different keyword or clear the search filter.', 'logs.lines': 'lines', 'logs.removed': 'Removed', 'logs.upgrade_required_title': 'Please Upgrade CLI Proxy API', diff --git a/index.html b/index.html index 3963679..4ce403a 100644 --- a/index.html +++ b/index.html @@ -839,27 +839,33 @@

日志查看

-
-

日志内容

+
+
+

日志内容

+ +
- 自动刷新 -
- - - -
+ + + +
diff --git a/src/modules/logs.js b/src/modules/logs.js index 2c55a11..8d027be 100644 --- a/src/modules/logs.js +++ b/src/modules/logs.js @@ -50,20 +50,19 @@ export const logsModule = { } else if (!incremental && response.lines.length > 0) { this.renderLogs(response.lines, response['line-count'] || response.lines.length, true); } else if (!incremental) { - logsContent.innerHTML = '

' + - i18n.t('logs.empty_title') + '

' + - i18n.t('logs.empty_desc') + '

'; this.latestLogTimestamp = null; + this.renderLogs([], 0, false); } } else if (!incremental) { - logsContent.innerHTML = '

' + - i18n.t('logs.empty_title') + '

' + - i18n.t('logs.empty_desc') + '

'; this.latestLogTimestamp = null; + this.renderLogs([], 0, false); } } catch (error) { console.error('加载日志失败:', error); if (!incremental) { + this.allLogLines = []; + this.displayedLogLines = []; + this.latestLogTimestamp = null; const is404 = error.message && (error.message.includes('404') || error.message.includes('Not Found')); if (is404) { @@ -82,7 +81,17 @@ export const logsModule = { const logsContent = document.getElementById('logs-content'); if (!logsContent) return; - if (!lines || lines.length === 0) { + const sourceLines = Array.isArray(lines) ? lines : []; + const filteredLines = sourceLines.filter(line => !line.includes('/v0/management/')); + let displayedLines = filteredLines; + if (filteredLines.length > this.maxDisplayLogLines) { + const linesToRemove = filteredLines.length - this.maxDisplayLogLines; + displayedLines = filteredLines.slice(linesToRemove); + } + + this.allLogLines = displayedLines.slice(); + + if (displayedLines.length === 0) { this.displayedLogLines = []; logsContent.innerHTML = '

' + i18n.t('logs.empty_title') + '

' + @@ -90,14 +99,15 @@ export const logsModule = { return; } - const filteredLines = lines.filter(line => !line.includes('/v0/management/')); - let displayedLines = filteredLines; - if (filteredLines.length > this.maxDisplayLogLines) { - const linesToRemove = filteredLines.length - this.maxDisplayLogLines; - displayedLines = filteredLines.slice(linesToRemove); - } + const visibleLines = this.filterLogLinesBySearch(displayedLines); + this.displayedLogLines = visibleLines.slice(); - this.displayedLogLines = displayedLines.slice(); + if (visibleLines.length === 0) { + logsContent.innerHTML = '

' + + i18n.t('logs.search_empty_title') + '

' + + i18n.t('logs.search_empty_desc') + '

'; + return; + } const displayedLineCount = this.displayedLogLines.length; logsContent.innerHTML = ` @@ -107,7 +117,7 @@ export const logsModule = {
${this.buildLogsHtml(this.displayedLogLines)}
`; - if (scrollToBottom) { + if (scrollToBottom && !this.logSearchQuery) { const logsTextElement = logsContent.querySelector('.logs-text'); if (logsTextElement) { logsTextElement.scrollTop = logsTextElement.scrollHeight; @@ -138,9 +148,21 @@ export const logsModule = { const isAtBottom = logsTextElement.scrollHeight - logsTextElement.scrollTop - logsTextElement.clientHeight < 50; - this.displayedLogLines = this.displayedLogLines.concat(filteredNewLines); - if (this.displayedLogLines.length > this.maxDisplayLogLines) { - this.displayedLogLines = this.displayedLogLines.slice(this.displayedLogLines.length - this.maxDisplayLogLines); + const baseLines = Array.isArray(this.allLogLines) && this.allLogLines.length > 0 + ? this.allLogLines + : (Array.isArray(this.displayedLogLines) ? this.displayedLogLines : []); + + this.allLogLines = baseLines.concat(filteredNewLines); + if (this.allLogLines.length > this.maxDisplayLogLines) { + this.allLogLines = this.allLogLines.slice(this.allLogLines.length - this.maxDisplayLogLines); + } + + const visibleLines = this.filterLogLinesBySearch(this.allLogLines); + this.displayedLogLines = visibleLines.slice(); + + if (visibleLines.length === 0) { + this.renderLogs(this.allLogLines, this.allLogLines.length, false); + return; } logsTextElement.innerHTML = this.buildLogsHtml(this.displayedLogLines); @@ -150,11 +172,44 @@ export const logsModule = { logsInfoElement.innerHTML = ` ${displayedLines} ${i18n.t('logs.lines')}`; } - if (isAtBottom) { + if (isAtBottom && !this.logSearchQuery) { logsTextElement.scrollTop = logsTextElement.scrollHeight; } }, + filterLogLinesBySearch(lines) { + const keyword = (this.logSearchQuery || '').toLowerCase(); + if (!keyword) { + return Array.isArray(lines) ? lines.slice() : []; + } + if (!Array.isArray(lines) || lines.length === 0) { + return []; + } + return lines.filter(line => (line || '').toLowerCase().includes(keyword)); + }, + + updateLogSearchQuery(value = '') { + const normalized = (value || '').trim(); + if (this.logSearchQuery === normalized) { + return; + } + this.logSearchQuery = normalized; + this.applyLogSearchFilter(); + }, + + applyLogSearchFilter() { + const logsContent = document.getElementById('logs-content'); + if (!logsContent) return; + if (logsContent.querySelector('.upgrade-notice') || logsContent.querySelector('.error-state')) { + return; + } + const baseLines = Array.isArray(this.allLogLines) ? this.allLogLines : []; + if (baseLines.length === 0 && logsContent.querySelector('.loading-placeholder')) { + return; + } + this.renderLogs(baseLines, baseLines.length, false); + }, + buildLogsHtml(lines) { if (!lines || lines.length === 0) { return ''; diff --git a/styles.css b/styles.css index 553eaed..d91949e 100644 --- a/styles.css +++ b/styles.css @@ -4331,6 +4331,60 @@ input:checked+.slider:before { line-height: 1.6; } +/* 日志页面头部布局 */ +#logs .card-header.logs-header { + flex-wrap: wrap; + gap: 12px; +} + +.logs-header-main { + display: flex; + align-items: center; + gap: 12px; + flex: 1; + min-width: 280px; +} + +.logs-header-main h3 { + margin: 0; +} + +.logs-search { + display: flex; + align-items: center; + border: 1px solid var(--border-primary); + border-radius: 10px; + padding: 0 12px; + background: var(--bg-secondary); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.03); + min-width: 240px; + max-width: 420px; + flex: 1; +} + +[data-theme="dark"] .logs-search { + background: var(--bg-tertiary); + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.04); +} + +.logs-search i { + color: var(--text-tertiary); + margin-right: 8px; +} + +.logs-search input { + border: none; + background: transparent; + color: var(--text-primary); + width: 100%; + padding: 10px 0; + font-size: 0.95rem; +} + +.logs-search input:focus { + outline: none; +} + /* 日志页面头部操作区域 */ #logs .card-header .header-actions { display: flex; @@ -4362,6 +4416,21 @@ input:checked+.slider:before { font-size: 12px; } + #logs .card-header.logs-header { + align-items: flex-start; + } + + .logs-header-main { + width: 100%; + flex-direction: column; + align-items: flex-start; + } + + .logs-search { + width: 100%; + max-width: none; + } + #logs .card-header .header-actions { width: 100%; justify-content: flex-start;