feat: 工具配置增强、站点拨测与日志中心多项改进
- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖 - 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败 - Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS - 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率 - 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示 - 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
This commit is contained in:
1 parent
cde7bbe540
commit
7e1c8f583c
84 files changed
+7729
-1507
No files matched your search
@@ -0,0 +1,114 @@
|
||||
// 查询输入自动补全上下文检测 // Logan.Li
|
||||
|
||||
import { COMMON_ES_FIELDS, ES_KEYWORD_SUGGESTIONS, LOKI_PIPE_SUGGESTIONS } from './query-syntax';
|
||||
|
||||
export interface AutocompleteContext {
|
||||
prefix: string;
|
||||
replaceStart: number;
|
||||
replaceEnd: number;
|
||||
kind: 'label' | 'value' | 'pipe' | 'field' | 'keyword';
|
||||
labelName?: string;
|
||||
}
|
||||
|
||||
const LOKI_SELECTOR_RE = /\{([^}]*)\}/g;
|
||||
|
||||
export function detectAutocompleteContext(
|
||||
query: string,
|
||||
cursor: number,
|
||||
engine: 'loki' | 'elasticsearch',
|
||||
): AutocompleteContext | null {
|
||||
const before = query.slice(0, cursor);
|
||||
if (engine === 'loki') {
|
||||
if (before.endsWith('|') || before.endsWith('| ')) {
|
||||
return { prefix: '', replaceStart: cursor, replaceEnd: cursor, kind: 'pipe' };
|
||||
}
|
||||
const selectorMatch = [...query.matchAll(LOKI_SELECTOR_RE)].find((m) => {
|
||||
const start = m.index ?? 0;
|
||||
const end = start + m[0].length;
|
||||
return cursor >= start && cursor <= end;
|
||||
});
|
||||
if (!selectorMatch) return null;
|
||||
const inner = selectorMatch[1];
|
||||
const innerStart = (selectorMatch.index ?? 0) + 1;
|
||||
const rel = cursor - innerStart;
|
||||
const innerBefore = inner.slice(0, rel);
|
||||
const valueMatch = innerBefore.match(/([a-zA-Z_][\w.-]*)\s*(=~|!~|=|!=)\s*"?([^"]*)$/);
|
||||
if (valueMatch) {
|
||||
const labelName = valueMatch[1];
|
||||
const valStart = innerStart + innerBefore.length - (valueMatch[3]?.length || 0);
|
||||
return {
|
||||
prefix: valueMatch[3] || '',
|
||||
replaceStart: valStart,
|
||||
replaceEnd: cursor,
|
||||
kind: 'value',
|
||||
labelName,
|
||||
};
|
||||
}
|
||||
const labelMatch = innerBefore.match(/(?:^|,)\s*([a-zA-Z_][\w.-]*)?$/);
|
||||
if (labelMatch) {
|
||||
const partial = labelMatch[1] || '';
|
||||
return {
|
||||
prefix: partial,
|
||||
replaceStart: cursor - partial.length,
|
||||
replaceEnd: cursor,
|
||||
kind: 'label',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const fieldMatch = before.match(/(?:^|\s)([a-zA-Z_@][\w.-]*)$/);
|
||||
if (fieldMatch) {
|
||||
const partial = fieldMatch[1];
|
||||
return {
|
||||
prefix: partial,
|
||||
replaceStart: cursor - partial.length,
|
||||
replaceEnd: cursor,
|
||||
kind: 'field',
|
||||
};
|
||||
}
|
||||
if (/\b(AND|OR|NOT)\s*$/i.test(before) || before.endsWith(' ')) {
|
||||
return { prefix: '', replaceStart: cursor, replaceEnd: cursor, kind: 'keyword' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function filterSuggestions(
|
||||
ctx: AutocompleteContext,
|
||||
labels: string[],
|
||||
labelValues: string[],
|
||||
mappingFields: string[],
|
||||
): string[] {
|
||||
const p = ctx.prefix.toLowerCase();
|
||||
const match = (items: string[]) => items.filter((s) => s.toLowerCase().startsWith(p)).slice(0, 20);
|
||||
|
||||
switch (ctx.kind) {
|
||||
case 'label':
|
||||
return match(labels);
|
||||
case 'value':
|
||||
return match(labelValues);
|
||||
case 'pipe':
|
||||
return LOKI_PIPE_SUGGESTIONS;
|
||||
case 'field':
|
||||
return match([...new Set([...mappingFields, ...COMMON_ES_FIELDS])]);
|
||||
case 'keyword':
|
||||
return ES_KEYWORD_SUGGESTIONS;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function applySuggestion(query: string, ctx: AutocompleteContext, suggestion: string): { text: string; cursor: number } {
|
||||
const left = query.slice(0, ctx.replaceStart);
|
||||
const right = query.slice(ctx.replaceEnd);
|
||||
let insert = suggestion;
|
||||
if (ctx.kind === 'value') {
|
||||
insert = suggestion.includes(' ') ? `"${suggestion}"` : suggestion;
|
||||
} else if (ctx.kind === 'label') {
|
||||
insert = `${suggestion}=`;
|
||||
} else if (ctx.kind === 'field') {
|
||||
insert = `${suggestion}:`;
|
||||
}
|
||||
const text = left + insert + right;
|
||||
return { text, cursor: left.length + insert.length };
|
||||
}
|
||||
Reference in new issue
Block a user