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:
1 parent
aa2e26c827
commit
35a014e571
13 files changed
+345
-111
No files matched your search
@@ -22,6 +22,9 @@ async def login(req: LoginRequest, db: AsyncSession = Depends(get_db)):
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户已禁用")
|
||||
|
||||
if user.role == "guest":
|
||||
raise HTTPException(status_code=403, detail="访客账户已禁用,请联系管理员")
|
||||
|
||||
user.last_login = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ async def _get_decrypted_instance(db: AsyncSession, instance_id: int) -> Instanc
|
||||
return instance
|
||||
|
||||
|
||||
async def _mysql_query(instance: Instance, sql: str, database: Optional[str] = None) -> list:
|
||||
async def _mysql_query(instance: Instance, sql: str, database: Optional[str] = None, params: Optional[list] = None) -> list:
|
||||
"""Execute a MySQL query and return rows as list of dicts."""
|
||||
import aiomysql
|
||||
conn = await aiomysql.connect(
|
||||
@@ -40,7 +40,7 @@ async def _mysql_query(instance: Instance, sql: str, database: Optional[str] = N
|
||||
)
|
||||
try:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cur:
|
||||
await cur.execute(sql)
|
||||
await cur.execute(sql, params)
|
||||
rows = await cur.fetchall()
|
||||
return rows
|
||||
finally:
|
||||
@@ -120,6 +120,38 @@ async def list_tables(
|
||||
raise HTTPException(status_code=500, detail="查询表列表失败: " + str(e))
|
||||
|
||||
|
||||
# ---- DB Schema (for autocomplete) ----
|
||||
|
||||
@router.get("/{instance_id}/detail/db-schema")
|
||||
async def get_db_schema(
|
||||
instance_id: int,
|
||||
database: str = Query(..., description="数据库名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba", "developer")),
|
||||
):
|
||||
"""一次性返回整个库的 {table: [columns]} 结构,用于前端 SQL 自动补全"""
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
await check_instance_access(current_user, instance, db)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(
|
||||
instance,
|
||||
"SELECT TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS "
|
||||
"WHERE TABLE_SCHEMA = %s ORDER BY TABLE_NAME, ORDINAL_POSITION",
|
||||
database=database,
|
||||
params=[database],
|
||||
)
|
||||
schema: dict[str, list[str]] = {}
|
||||
for row in rows:
|
||||
tbl = row["TABLE_NAME"]
|
||||
col = row["COLUMN_NAME"]
|
||||
schema.setdefault(tbl, []).append(col)
|
||||
return schema
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取库结构失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Table Detail ----
|
||||
|
||||
@router.get("/{instance_id}/detail/table-detail")
|
||||
|
||||
Reference in new issue
Block a user