feat: Agent CLI 与终端审计回放全面优化
- Agent CLI: 多轮对话、SSH 长连接、流式推理、Evolver 按用户隔离、历史侧栏 - 终端审计: 回放播放器、命令书签、筛选分页导出、命令输出统计、录制落盘修复 - 修复 session_commands 表迁移、Kill 竞态导致录制丢失、无录制会话回放提示 - 审计历史页只读(移除删除),移除回放弹框录制搜索
This commit is contained in:
1 parent
9a3dd304f7
commit
1acb0c49af
81 files changed
+3916
-1655
No files matched your search
@@ -26,8 +26,9 @@ const DEFAULT_TIME_RANGE: TimeRange = {
|
||||
|
||||
// 引擎默认查询语句 // Logan.Li
|
||||
const DEFAULT_QUERIES: Record<'loki' | 'elasticsearch', string> = {
|
||||
loki: '{app="cex-admin-api"} |= ""',
|
||||
elasticsearch: 'app:cex-admin-api',
|
||||
// 默认查询语句:Loki 用 LogQL / ES 用 Lucene,对齐平台 admin-api 服务 // Logan.Li
|
||||
loki: '{app="admin-api"} |= "INFO"',
|
||||
elasticsearch: 'app:admin-api',
|
||||
};
|
||||
|
||||
// 引擎徽标:左侧小圆点 + 语言标签 // Logan.Li
|
||||
@@ -51,7 +52,7 @@ const ALL_TEMPLATES: Record<'loki' | 'elasticsearch', { label: string; query: st
|
||||
{ label: '高延迟筛选', query: '{app="cex-admin-api"} | json | response_time > 500' },
|
||||
{ label: '异常堆栈', query: '{app="cex-admin-api"} |= "Exception"' },
|
||||
{ label: '日志趋势', query: 'sum(count_over_time({app="cex-admin-api"}[1h]))' },
|
||||
{ label: '正则提取', query: '{app="cex-admin-api"} | regexp "(?P<method>\\w+) (?P<path>\\S+)"' },
|
||||
{ label: '正则提取', query: '{app="cex-admin-api"} | regexp "(?P<method>[A-Za-z0-9_]+) (?P<path>[^ ]+)"' }, // LogQL regexp 用 RE2,不支持 \w \S 等 Perl 简写,须用显式字符类,否则 Loki 返回 400 invalid char escape // Logan.Li
|
||||
{ label: '格式输出', query: '{app="cex-admin-api"} | json | line_format "{{.level}} {{.message}}"' },
|
||||
],
|
||||
elasticsearch: [
|
||||
@@ -151,8 +152,8 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
|
||||
const [dsId, setDsId] = useState(String(sources[0]?.id || ''));
|
||||
// ES 查询允许在面板内临时覆盖索引(默认取自数据源配置)// Logan.Li
|
||||
const [esIndex, setEsIndex] = useState('');
|
||||
// API 单次查询返回条数上限(后端限制 500),Loki 用 limit / ES 用 size // Logan.Li
|
||||
const [queryLimit, setQueryLimit] = useState(200);
|
||||
// API 单次查询返回条数上限(后端限制 1000),Loki 用 limit / ES 用 size // Logan.Li
|
||||
const [queryLimit, setQueryLimit] = useState(1000);
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>(DEFAULT_TIME_RANGE);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [entries, setEntries] = useState<LogEntry[]>([]);
|
||||
@@ -195,7 +196,8 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
|
||||
|
||||
// 执行查询 — 接入真实 API(无 mock)// Logan.Li
|
||||
const handleRun = useCallback(async () => {
|
||||
if (!currentSource) return;
|
||||
// 空查询兜底:后端 Query 字段 required,空值会 400,这里提前拦截 // Logan.Li
|
||||
if (!currentSource || !query.trim()) return;
|
||||
const dsIdNum = currentSource.id;
|
||||
setLoading(true);
|
||||
const t0 = performance.now();
|
||||
@@ -280,7 +282,7 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
|
||||
<span className="text-[11px] font-semibold" style={{ color: badge.dotColor }}>{badge.label}</span>
|
||||
</div>
|
||||
<SyntaxHighlightInput value={query} onChange={setQuery} engine={effEngine} placeholder={meta.queryPlaceholder} />
|
||||
<button onClick={handleRun} disabled={loading || !currentSource} className="h-10 px-4 rounded text-xs font-medium text-white flex items-center gap-1.5 flex-shrink-0 transition-opacity hover:opacity-90" style={{ background: '#22c55e' }}>
|
||||
<button onClick={handleRun} disabled={loading || !currentSource || !query.trim()} className="h-10 px-4 rounded text-xs font-medium text-white flex items-center gap-1.5 flex-shrink-0 transition-opacity hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed" style={{ background: '#22c55e' }}>
|
||||
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Play className="h-4 w-4" />} 查询
|
||||
</button>
|
||||
</div>
|
||||
@@ -310,11 +312,11 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={500}
|
||||
max={1000}
|
||||
value={queryLimit}
|
||||
onChange={e => {
|
||||
const v = parseInt(e.target.value, 10);
|
||||
setQueryLimit(Number.isNaN(v) ? 200 : Math.min(500, Math.max(1, v)));
|
||||
setQueryLimit(Number.isNaN(v) ? 1000 : Math.min(1000, Math.max(1, v)));
|
||||
}}
|
||||
className="w-14 h-6 text-xs text-center rounded outline-none"
|
||||
style={{ background: 'var(--dg-bg-secondary)', color: 'var(--dg-text-primary)', border: '1px solid var(--dg-border)' }}
|
||||
@@ -328,8 +330,8 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
|
||||
{saveMenuOpen && (<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setSaveMenuOpen(false)} />
|
||||
<div className="absolute right-0 top-full mt-1 z-50 rounded-lg border shadow-xl overflow-hidden" style={{ background: 'var(--dg-bg-secondary)', borderColor: 'var(--dg-border)' }}>
|
||||
<button onClick={() => handleSaveMenu('query')} className="w-full text-left px-4 py-2 text-xs flex items-center gap-2 hover:opacity-80" style={{ color: 'var(--dg-text-secondary)' }}><Save className="h-3.5 w-3.5" /> 另存为查询</button>
|
||||
<button onClick={() => handleSaveMenu('alert')} className="w-full text-left px-4 py-2 text-xs flex items-center gap-2 hover:opacity-80" style={{ color: 'var(--dg-text-secondary)', borderTop: '1px solid var(--dg-border)' }}><Bell className="h-3.5 w-3.5" /> 另存为告警</button>
|
||||
<button onClick={() => handleSaveMenu('query')} className="w-full text-left px-4 py-2 text-xs flex items-center gap-2 hover:opacity-80" style={{ color: 'var(--dg-text-secondary)' }}><Save className="h-3.5 w-3.5" /> 查询</button>
|
||||
<button onClick={() => handleSaveMenu('alert')} className="w-full text-left px-4 py-2 text-xs flex items-center gap-2 hover:opacity-80" style={{ color: 'var(--dg-text-secondary)', borderTop: '1px solid var(--dg-border)' }}><Bell className="h-3.5 w-3.5" /> 告警</button>
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user