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 @@
' + - i18n.t('logs.empty_title') + '
' + - i18n.t('logs.empty_desc') + '
' + - i18n.t('logs.empty_title') + '
' + - i18n.t('logs.empty_desc') + '
' + 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') + '
${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;