feat: 查询页增强 + 权限体系优化

- Redis DB0~DB15 下拉选择,默认DB0
- 查询历史粘贴按钮(一键填入SQL编辑器)
- 去掉导出历史按钮
- SchemaSidebar 按目标数据库筛选
- SQL自动补全 schema 双层注册(db→tables, table→columns)
- 后端 db-schema 接口(INFORMATION_SCHEMA.COLUMNS 一次加载)
- autocompletion 扩展激活
- Redis key 树形结构(冒号分隔层级)
- Redis 查询模板 GET/HGETALL/HGET
- guest 角色彻底移除,改为 auditor
- 登录页优化:去掉演示账号、placeholder 中文提示
This commit is contained in:
logan committed 2026-05-24 18:50:08 +08:00
1 parent aa2e26c827
commit 35a014e571
13 files changed
+345 -111

No files matched your search

+50 -6
View File
@@ -11,7 +11,7 @@ import {
defaultHighlightStyle,
bracketMatching,
} from '@codemirror/language';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
import { closeBrackets, closeBracketsKeymap, autocompletion } from '@codemirror/autocomplete';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
// ---------------------------------------------------------------------------
@@ -26,6 +26,8 @@ export interface SqlEditorProps {
height?: string;
readOnly?: boolean;
onExecute?: () => void;
/** SQL schema: { [dbName]: { [tableName]: string[] (columns) } } or { [tableName]: string[] } */
schema?: Record<string, Record<string, string[]> | string[]>;
}
// ---------------------------------------------------------------------------
@@ -74,7 +76,7 @@ const MONGODB_KEYWORDS = [
'Regex', 'BinData', 'Timestamp', 'MinKey', 'MaxKey', 'Symbol', 'DBRef',
];
function getSQLExtension(language: SqlEditorProps['language']): Extension {
function getSQLExtension(language: SqlEditorProps['language'], schema?: SqlEditorProps['schema']): Extension {
const lang = language ?? 'sql';
if (lang === 'redis') {
@@ -91,6 +93,43 @@ function getSQLExtension(language: SqlEditorProps['language']): Extension {
return sql({ dialect });
}
// MySQL/PG — pass schema for autocomplete (tables + columns)
if (schema && Object.keys(schema).length > 0) {
// Flatten: if first value is an array, it's { table: columns[] }
// if first value is an object, it's { db: { table: columns[] } }
const sampleValue = Object.values(schema)[0];
let tables: Record<string, readonly string[]> = {};
if (Array.isArray(sampleValue)) {
// Direct: { table: [columns] }
tables = Object.fromEntries(
Object.entries(schema).map(([k, v]) => [k, v as string[]])
);
} else {
// Nested: { db: { table: [columns] } }
// Register: db → table names (so "db." shows tables)
// Register: table → columns (so "table." shows columns)
for (const [db, tableMap] of Object.entries(schema)) {
const entries = tableMap as Record<string, string[]>;
const tableNames = Object.keys(entries);
// "shop_db" → shows table names as completions after "."
tables[db] = tableNames;
for (const [table, cols] of Object.entries(entries)) {
// "goods" → shows column names after "."
if (cols.length > 0) {
tables[table] = cols;
}
}
}
}
const dialect = SQLDialect.define({
keywords: 'select from where order by group having limit offset join inner left right full outer cross on and or not in is null like between as asc desc distinct all case when then else end exists union intersect except insert into values update set delete create alter drop table index view primary key foreign references unique check default grant revoke commit rollback begin transaction count sum avg min max show describe use database explain analyze',
schema: tables,
});
return sql({ dialect });
}
return sql();
}
@@ -228,6 +267,7 @@ const SqlEditor: React.FC<SqlEditorProps> = ({
height = '200px',
readOnly = false,
onExecute,
schema,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
@@ -294,11 +334,15 @@ const SqlEditor: React.FC<SqlEditorProps> = ({
history(),
bracketMatching(),
closeBrackets(),
autocompletion({
activateOnTyping: true,
icons: false,
}),
highlightSelectionMatches(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
appTheme,
oneDark,
langCompartmentRef.current.of(getSQLExtension(language)),
langCompartmentRef.current.of(getSQLExtension(language, schema)),
readOnlyCompartmentRef.current.of(
EditorState.readOnly.of(readOnly),
),
@@ -345,14 +389,14 @@ const SqlEditor: React.FC<SqlEditorProps> = ({
}
}, [value]);
// Update language when it changes
// Update language/schema when they change
useEffect(() => {
const view = viewRef.current;
if (!view) return;
view.dispatch({
effects: langCompartmentRef.current.reconfigure(getSQLExtension(language)),
effects: langCompartmentRef.current.reconfigure(getSQLExtension(language, schema)),
});
}, [language]);
}, [language, schema]);
// Update readOnly when it changes
useEffect(() => {