refactor: 数据源菜单合一+布局优化
- 4个数据源菜单(MySQL/PG/Redis/Mongo)合并为1个「数据源」 - InstancePage 改为统一入口,顶部Tab切换类型过滤 - 添加数据源改为弹框Modal(默认MySQL) - 去掉左下角用户区,退出登录移到右上角header - 侧边栏收窄220px,整体布局更紧凑 - 后端202 tests passing - 前端构建零错误
This commit is contained in:
1 parent
bab55d3927
commit
5cbd0e3441
23 files changed
+2881
-94
No files matched your search
@@ -0,0 +1,62 @@
|
||||
"""Backup management API."""
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import require_role
|
||||
from app.models.user import User
|
||||
from app.models.backup import Backup
|
||||
from app.schemas.backup import BackupCreate, BackupResponse
|
||||
|
||||
router = APIRouter(prefix="/backups", tags=["备份管理"])
|
||||
|
||||
|
||||
@router.get("", response_model=List[BackupResponse])
|
||||
async def list_backups(
|
||||
instance_id: Optional[int] = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
stmt = select(Backup).order_by(Backup.id.desc())
|
||||
if instance_id:
|
||||
stmt = stmt.where(Backup.instance_id == instance_id)
|
||||
result = await db.execute(stmt)
|
||||
backups = result.scalars().all()
|
||||
return backups
|
||||
|
||||
|
||||
@router.post("", response_model=BackupResponse, status_code=201)
|
||||
async def create_backup(
|
||||
data: BackupCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
backup = Backup(
|
||||
title=data.title,
|
||||
instance_id=data.instance_id,
|
||||
database=data.database,
|
||||
filename=data.database + "_" + str(data.instance_id) + ".sql",
|
||||
status="pending",
|
||||
created_by=current_user.id,
|
||||
remark=data.remark,
|
||||
)
|
||||
db.add(backup)
|
||||
await db.commit()
|
||||
await db.refresh(backup)
|
||||
return backup
|
||||
|
||||
|
||||
@router.get("/{backup_id}", response_model=BackupResponse)
|
||||
async def get_backup(
|
||||
backup_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
result = await db.execute(select(Backup).where(Backup.id == backup_id))
|
||||
backup = result.scalar_one_or_none()
|
||||
if not backup:
|
||||
raise HTTPException(status_code=404, detail="备份记录不存在")
|
||||
return backup
|
||||
@@ -0,0 +1,70 @@
|
||||
"""数据字典 API 端点。"""
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import require_role
|
||||
from app.models.user import User
|
||||
from app.services import dictionary_service
|
||||
|
||||
router = APIRouter(tags=["数据字典"])
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}/databases")
|
||||
async def list_databases(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba", "developer")),
|
||||
):
|
||||
"""获取实例的数据库列表。"""
|
||||
databases = await dictionary_service.list_databases(db, instance_id)
|
||||
if databases is None:
|
||||
raise HTTPException(status_code=404, detail="数据源不存在")
|
||||
return {"databases": databases}
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}/tables")
|
||||
async def list_tables(
|
||||
instance_id: int,
|
||||
database: str = Query(..., description="数据库名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba", "developer")),
|
||||
):
|
||||
"""获取指定数据库的表列表。"""
|
||||
tables = await dictionary_service.list_tables(db, instance_id, database)
|
||||
if tables is None:
|
||||
raise HTTPException(status_code=404, detail="数据源不存在")
|
||||
return {"tables": tables}
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}/tables/{table_name}")
|
||||
async def get_table_detail(
|
||||
instance_id: int,
|
||||
table_name: str,
|
||||
database: str = Query(..., description="数据库名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba", "developer")),
|
||||
):
|
||||
"""获取表详情: 字段/索引/DDL/行数。"""
|
||||
detail = await dictionary_service.get_table_detail(
|
||||
db, instance_id, database, table_name
|
||||
)
|
||||
if not detail:
|
||||
raise HTTPException(status_code=404, detail="数据源不存在")
|
||||
return detail
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}/export")
|
||||
async def export_dictionary(
|
||||
instance_id: int,
|
||||
database: str = Query(..., description="数据库名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba", "developer")),
|
||||
):
|
||||
"""导出整个数据库的表结构信息。"""
|
||||
result = await dictionary_service.export_dictionary(
|
||||
db, instance_id, database
|
||||
)
|
||||
return {"tables": result}
|
||||
@@ -0,0 +1,331 @@
|
||||
"""MySQL instance detail management API — databases, tables, users, slow queries, processlist, status, variables."""
|
||||
import asyncio
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import require_role
|
||||
from app.models.user import User
|
||||
from app.models.instance import Instance
|
||||
from app.core.crypto import decrypt_password
|
||||
|
||||
router = APIRouter(prefix="/instances", tags=["实例详情"])
|
||||
|
||||
|
||||
def _get_instance_or_404(db: AsyncSession, instance_id: int):
|
||||
"""Helper to get instance or raise 404. Returns coroutine."""
|
||||
return db.execute(select(Instance).where(Instance.id == instance_id))
|
||||
|
||||
|
||||
async def _get_decrypted_instance(db: AsyncSession, instance_id: int) -> Instance:
|
||||
result = await _get_instance_or_404(db, instance_id)
|
||||
instance = result.scalar_one_or_none()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="数据源不存在")
|
||||
instance.password = decrypt_password(instance.password)
|
||||
return instance
|
||||
|
||||
|
||||
async def _mysql_query(instance: Instance, sql: str, database: Optional[str] = None) -> list:
|
||||
"""Execute a MySQL query and return rows as list of dicts."""
|
||||
import aiomysql
|
||||
conn = await aiomysql.connect(
|
||||
host=instance.host, port=instance.port,
|
||||
user=instance.username, password=instance.password,
|
||||
db=database or instance.database,
|
||||
)
|
||||
try:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cur:
|
||||
await cur.execute(sql)
|
||||
rows = await cur.fetchall()
|
||||
return rows
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
async def _mysql_execute(instance: Instance, sql: str, database: Optional[str] = None):
|
||||
"""Execute a MySQL statement (no result set needed)."""
|
||||
import aiomysql
|
||||
conn = await aiomysql.connect(
|
||||
host=instance.host, port=instance.port,
|
||||
user=instance.username, password=instance.password,
|
||||
db=database or instance.database,
|
||||
)
|
||||
try:
|
||||
async with conn.cursor() as cur:
|
||||
await cur.execute(sql)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ---- Databases ----
|
||||
|
||||
@router.get("/{instance_id}/detail/databases")
|
||||
async def list_databases(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(instance, "SHOW DATABASES")
|
||||
databases = [
|
||||
row["Database"] for row in rows
|
||||
if row["Database"] not in ("information_schema", "performance_schema", "sys", "mysql")
|
||||
]
|
||||
return databases
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询数据库列表失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Tables ----
|
||||
|
||||
@router.get("/{instance_id}/detail/tables")
|
||||
async def list_tables(
|
||||
instance_id: int,
|
||||
database: str = Query(..., description="数据库名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(instance, "SHOW TABLE STATUS", database=database)
|
||||
tables = []
|
||||
for row in rows:
|
||||
tables.append({
|
||||
"name": row.get("Name", ""),
|
||||
"engine": row.get("Engine", ""),
|
||||
"rows": row.get("Rows", 0),
|
||||
"data_length": row.get("Data_length", 0),
|
||||
"index_length": row.get("Index_length", 0),
|
||||
"comment": row.get("Comment", ""),
|
||||
"collation": row.get("Collation", ""),
|
||||
"create_time": str(row.get("Create_time", "")),
|
||||
"update_time": str(row.get("Update_time", "")),
|
||||
})
|
||||
return tables
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询表列表失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Table Detail ----
|
||||
|
||||
@router.get("/{instance_id}/detail/table-detail")
|
||||
async def table_detail(
|
||||
instance_id: int,
|
||||
database: str = Query(..., description="数据库名"),
|
||||
table: str = Query(..., description="表名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
# SHOW CREATE TABLE
|
||||
ddl_rows = await _mysql_query(
|
||||
instance,
|
||||
"SHOW CREATE TABLE " + table,
|
||||
database=database,
|
||||
)
|
||||
ddl = ddl_rows[0].get("Create Table", "") if ddl_rows else ""
|
||||
|
||||
# SHOW COLUMNS
|
||||
columns = await _mysql_query(
|
||||
instance,
|
||||
"SHOW COLUMNS FROM " + table,
|
||||
database=database,
|
||||
)
|
||||
|
||||
# SHOW INDEX
|
||||
indexes = await _mysql_query(
|
||||
instance,
|
||||
"SHOW INDEX FROM " + table,
|
||||
database=database,
|
||||
)
|
||||
|
||||
return {
|
||||
"ddl": ddl,
|
||||
"columns": columns,
|
||||
"indexes": indexes,
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询表详情失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Users ----
|
||||
|
||||
@router.get("/{instance_id}/detail/users")
|
||||
async def list_users(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(instance, "SELECT user, host FROM mysql.user")
|
||||
users = [{"user": row.get("user", ""), "host": row.get("host", "")} for row in rows]
|
||||
return users
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询用户列表失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Grants ----
|
||||
|
||||
@router.get("/{instance_id}/detail/grants")
|
||||
async def show_grants(
|
||||
instance_id: int,
|
||||
user: str = Query(..., description="用户名"),
|
||||
host: str = Query(..., description="主机"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
quoted_user = user.replace("'", "\\'")
|
||||
quoted_host = host.replace("'", "\\'")
|
||||
sql = "SHOW GRANTS FOR '" + quoted_user + "'@'" + quoted_host + "'"
|
||||
rows = await _mysql_query(instance, sql)
|
||||
grants = []
|
||||
for row in rows:
|
||||
# The key varies by MySQL version, grab first value
|
||||
grants.append(list(row.values())[0])
|
||||
return {"user": user, "host": host, "grants": grants}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询权限失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Slow Queries ----
|
||||
|
||||
@router.get("/{instance_id}/detail/slow-queries")
|
||||
async def slow_queries(
|
||||
instance_id: int,
|
||||
database: Optional[str] = Query(None, description="数据库名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
# Try querying mysql.slow_log table first
|
||||
try:
|
||||
sql = (
|
||||
"SELECT start_time, user_host, query_time, lock_time, "
|
||||
"rows_sent, rows_examined, sql_text "
|
||||
"FROM mysql.slow_log ORDER BY start_time DESC LIMIT 100"
|
||||
)
|
||||
rows = await _mysql_query(instance, sql, database=database)
|
||||
return rows
|
||||
except Exception:
|
||||
# Fallback: show slow query variables
|
||||
rows = await _mysql_query(
|
||||
instance,
|
||||
"SHOW VARIABLES LIKE 'slow%'",
|
||||
database=database,
|
||||
)
|
||||
return rows
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询慢查询失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Processlist ----
|
||||
|
||||
@router.get("/{instance_id}/detail/processlist")
|
||||
async def show_processlist(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(instance, "SHOW FULL PROCESSLIST")
|
||||
return rows
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询进程列表失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Kill Process ----
|
||||
|
||||
@router.post("/{instance_id}/detail/processlist/{pid}/kill")
|
||||
async def kill_process(
|
||||
instance_id: int,
|
||||
pid: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
await _mysql_execute(instance, "KILL CONNECTION " + str(pid))
|
||||
return {"message": "进程已终止", "pid": pid}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="终止进程失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Status ----
|
||||
|
||||
@router.get("/{instance_id}/detail/status")
|
||||
async def show_status(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(instance, "SHOW GLOBAL STATUS")
|
||||
# Return key metrics
|
||||
key_metrics = {}
|
||||
important_keys = {
|
||||
"Threads_connected", "Threads_running", "Threads_cached",
|
||||
"Queries", "Questions", "Slow_queries",
|
||||
"Connections", "Aborted_connects",
|
||||
"Bytes_received", "Bytes_sent",
|
||||
"Table_open_cache_hits", "Table_open_cache_misses",
|
||||
"Innodb_row_lock_waits", "Innodb_row_lock_time",
|
||||
"Uptime",
|
||||
}
|
||||
for row in rows:
|
||||
var_name = row.get("Variable_name", "")
|
||||
if var_name in important_keys:
|
||||
key_metrics[var_name] = row.get("Value", "")
|
||||
return {
|
||||
"key_metrics": key_metrics,
|
||||
"all_status": rows,
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询状态失败: " + str(e))
|
||||
|
||||
|
||||
# ---- Variables ----
|
||||
|
||||
@router.get("/{instance_id}/detail/variables")
|
||||
async def show_variables(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
instance = await _get_decrypted_instance(db, instance_id)
|
||||
if instance.db_type != "mysql":
|
||||
raise HTTPException(status_code=400, detail="仅支持MySQL实例")
|
||||
try:
|
||||
rows = await _mysql_query(instance, "SHOW VARIABLES")
|
||||
return rows
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="查询变量失败: " + str(e))
|
||||
@@ -0,0 +1,240 @@
|
||||
"""Redis management API endpoints."""
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user, require_role
|
||||
from app.models.user import User
|
||||
from app.models.instance import Instance
|
||||
from app.services import redis_connector
|
||||
|
||||
router = APIRouter(tags=["Redis管理"])
|
||||
|
||||
|
||||
async def _get_redis_instance(instance_id: int, db: AsyncSession) -> Instance:
|
||||
"""Fetch and validate a Redis instance."""
|
||||
result = await db.execute(select(Instance).where(Instance.id == instance_id))
|
||||
instance = result.scalar_one_or_none()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail="数据源不存在")
|
||||
if instance.db_type != "redis":
|
||||
raise HTTPException(status_code=400, detail="该实例不是 Redis 类型")
|
||||
return instance
|
||||
|
||||
|
||||
# ── Request schemas ──────────────────────────────────────────────
|
||||
|
||||
class RedisKeyCreate(BaseModel):
|
||||
db: int = 0
|
||||
key: str
|
||||
value: object
|
||||
type: str = "string"
|
||||
ttl: Optional[int] = None
|
||||
|
||||
|
||||
class RedisCommandRequest(BaseModel):
|
||||
db: int = 0
|
||||
command: str
|
||||
|
||||
|
||||
class RedisExpireRequest(BaseModel):
|
||||
db: int = 0
|
||||
seconds: int
|
||||
|
||||
|
||||
# ── Endpoints ────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/redis/{instance_id}/info")
|
||||
async def get_redis_info(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Redis 信息概览."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
info = await redis_connector.RedisConnector.get_info(instance)
|
||||
return info
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取 Redis 信息失败: " + str(e))
|
||||
|
||||
|
||||
@router.get("/redis/{instance_id}/dbsize")
|
||||
async def get_redis_dbsize(
|
||||
instance_id: int,
|
||||
db_num: int = Query(0, alias="db"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""DBSIZE."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
size = await redis_connector.RedisConnector.get_dbsize(instance, db_num)
|
||||
return {"db": db_num, "size": size}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取 DBSIZE 失败: " + str(e))
|
||||
|
||||
|
||||
@router.get("/redis/{instance_id}/keys")
|
||||
async def get_redis_keys(
|
||||
instance_id: int,
|
||||
db_num: int = Query(0, alias="db"),
|
||||
pattern: str = Query("*"),
|
||||
type: Optional[str] = Query(None, alias="type"),
|
||||
cursor: int = Query(0),
|
||||
count: int = Query(50),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""键列表."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
next_cursor, keys = await redis_connector.RedisConnector.scan_keys(
|
||||
instance, db=db_num, pattern=pattern,
|
||||
type_filter=type, cursor=cursor, count=count,
|
||||
)
|
||||
return {"cursor": next_cursor, "keys": keys}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取键列表失败: " + str(e))
|
||||
|
||||
|
||||
@router.get("/redis/{instance_id}/keys/{key:path}")
|
||||
async def get_redis_key_detail(
|
||||
instance_id: int,
|
||||
key: str,
|
||||
db_num: int = Query(0, alias="db"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""键详情."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
detail = await redis_connector.RedisConnector.get_key_value(
|
||||
instance, db_num, key
|
||||
)
|
||||
return detail
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取键详情失败: " + str(e))
|
||||
|
||||
|
||||
@router.post("/redis/{instance_id}/keys")
|
||||
async def create_redis_key(
|
||||
instance_id: int,
|
||||
data: RedisKeyCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""创建/更新键."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
result = await redis_connector.RedisConnector.set_key_value(
|
||||
instance, data.db, data.key, data.value, data.type, data.ttl
|
||||
)
|
||||
return {"message": "操作成功", "result": result}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="创建键失败: " + str(e))
|
||||
|
||||
|
||||
@router.delete("/redis/{instance_id}/keys/{key:path}")
|
||||
async def delete_redis_key(
|
||||
instance_id: int,
|
||||
key: str,
|
||||
db_num: int = Query(0, alias="db"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""删除键."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
deleted = await redis_connector.RedisConnector.del_key(instance, db_num, key)
|
||||
return {"message": "删除成功", "deleted": deleted}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="删除键失败: " + str(e))
|
||||
|
||||
|
||||
@router.put("/redis/{instance_id}/keys/{key:path}/expire")
|
||||
async def set_redis_key_expire(
|
||||
instance_id: int,
|
||||
key: str,
|
||||
data: RedisExpireRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""设置 TTL."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
ok = await redis_connector.RedisConnector.set_ttl(
|
||||
instance, data.db, key, data.seconds
|
||||
)
|
||||
return {"message": "设置成功" if ok else "设置失败"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="设置 TTL 失败: " + str(e))
|
||||
|
||||
|
||||
@router.delete("/redis/{instance_id}/flushdb")
|
||||
async def flush_redis_db(
|
||||
instance_id: int,
|
||||
db_num: int = Query(0, alias="db"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""清空DB (admin only!)."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
result = await redis_connector.RedisConnector.flush_db(instance, db_num)
|
||||
return {"message": "清空成功", "result": result}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="清空 DB 失败: " + str(e))
|
||||
|
||||
|
||||
@router.post("/redis/{instance_id}/command")
|
||||
async def execute_redis_command(
|
||||
instance_id: int,
|
||||
data: RedisCommandRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""执行命令."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
output = await redis_connector.RedisConnector.execute_command(
|
||||
instance, data.db, data.command
|
||||
)
|
||||
return {"output": output}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="命令执行失败: " + str(e))
|
||||
|
||||
|
||||
@router.get("/redis/{instance_id}/slowlog")
|
||||
async def get_redis_slowlog(
|
||||
instance_id: int,
|
||||
count: int = Query(10),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""慢日志."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
logs = await redis_connector.RedisConnector.get_slowlog(instance, count)
|
||||
return logs
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取慢日志失败: " + str(e))
|
||||
|
||||
|
||||
@router.get("/redis/{instance_id}/clients")
|
||||
async def get_redis_clients(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""客户端列表."""
|
||||
instance = await _get_redis_instance(instance_id, db)
|
||||
try:
|
||||
clients = await redis_connector.RedisConnector.get_clients(instance)
|
||||
return clients
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="获取客户端列表失败: " + str(e))
|
||||
Reference in new issue
Block a user