init
This commit is contained in:
1 parent
c7fccf99c7
commit
8471f368eb
33 files changed
+444
-2095
No files matched your search
@@ -23,7 +23,7 @@ async def list_audit_rules(
|
||||
db_type: Optional[str] = None,
|
||||
is_enabled: Optional[bool] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
current_user: User = Depends(require_role("admin", "dba", "auditor")),
|
||||
):
|
||||
stmt = select(AuditRule).order_by(AuditRule.id)
|
||||
if rule_type:
|
||||
@@ -62,7 +62,7 @@ async def create_audit_rule(
|
||||
@router.get("/stats/summary")
|
||||
async def rule_stats(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
current_user: User = Depends(require_role("admin", "dba", "auditor")),
|
||||
):
|
||||
total_stmt = select(func.count(AuditRule.id))
|
||||
total = (await db.execute(total_stmt)).scalar()
|
||||
@@ -130,7 +130,7 @@ async def seed_audit_rules(
|
||||
async def get_audit_rule(
|
||||
rule_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
current_user: User = Depends(require_role("admin", "dba", "auditor")),
|
||||
):
|
||||
result = await db.execute(select(AuditRule).where(AuditRule.id == rule_id))
|
||||
rule = result.scalar_one_or_none()
|
||||
|
||||
@@ -49,7 +49,6 @@ DB_TYPE_INFO: Dict[str, Dict[str, Any]] = {
|
||||
"查看索引": "SHOW INDEX FROM table_name",
|
||||
"查看连接": "SHOW PROCESSLIST",
|
||||
"查看状态": "SHOW STATUS LIKE 'Threads%'",
|
||||
"查看慢查询": "SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 20",
|
||||
},
|
||||
"limitations": [
|
||||
"不支持存储过程调用",
|
||||
|
||||
@@ -217,42 +217,6 @@ async def show_grants(
|
||||
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)
|
||||
# P0: 权限检查
|
||||
await check_instance_access(current_user, instance, db)
|
||||
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")
|
||||
|
||||
@@ -100,12 +100,11 @@ async def test_ldap_connection(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""测试 LDAP 连接 — 使用 mock 模拟"""
|
||||
"""测试 LDAP 连接"""
|
||||
ldap = await _get_existing(db)
|
||||
if not ldap:
|
||||
raise HTTPException(status_code=404, detail="LDAP 配置不存在,请先创建")
|
||||
|
||||
# Use mock for testing — python-ldap may not be installed
|
||||
try:
|
||||
import ldap as _ldap # noqa
|
||||
# Real LDAP test path
|
||||
@@ -131,12 +130,9 @@ async def test_ldap_connection(
|
||||
return {"success": True, "message": f"LDAP 连接成功,用户 {data.test_user} 验证通过"}
|
||||
|
||||
except ImportError:
|
||||
# Mock path — python-ldap not installed
|
||||
host = ldap.ldap_host
|
||||
port = ldap.ldap_port
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"[Mock] LDAP 连接模拟成功 ({host}:{port}),用户 {data.test_user} 验证通过",
|
||||
"success": False,
|
||||
"message": "python-ldap 未安装,无法测试 LDAP 连接。请执行: pip install python-ldap",
|
||||
}
|
||||
except Exception as e:
|
||||
return {"success": False, "message": f"LDAP 连接失败: {str(e)}"}
|
||||
@@ -124,7 +124,9 @@ async def test_notification(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""测试发送通知 — 目前返回模拟结果"""
|
||||
"""测试发送通知 — 实际发送测试消息到配置的渠道。"""
|
||||
from app.services.notification_sender import NotificationSender
|
||||
|
||||
result = await db.execute(
|
||||
select(NotificationChannel).where(NotificationChannel.id == channel_id)
|
||||
)
|
||||
@@ -133,24 +135,19 @@ async def test_notification(
|
||||
raise HTTPException(status_code=404, detail="通知渠道不存在")
|
||||
|
||||
config = json.loads(channel.config) if channel.config else {}
|
||||
channel_type = channel.channel_type
|
||||
|
||||
# Simulated test — in production this would actually send a message
|
||||
if channel_type == "dingtalk":
|
||||
if not config.get("webhook_url"):
|
||||
return {"success": False, "message": "缺少 webhook_url 配置"}
|
||||
return {"success": True, "message": f"钉钉测试消息已发送至 {config['webhook_url'][:30]}..."}
|
||||
|
||||
elif channel_type == "webhook_feishu":
|
||||
if not config.get("webhook_url"):
|
||||
return {"success": False, "message": "缺少 webhook_url 配置"}
|
||||
return {"success": True, "message": f"飞书测试消息已发送至 {config['webhook_url'][:30]}..."}
|
||||
|
||||
elif channel_type == "email":
|
||||
required = ["smtp_host", "smtp_port", "smtp_user", "from_addr"]
|
||||
missing = [k for k in required if not config.get(k)]
|
||||
if missing:
|
||||
return {"success": False, "message": f"缺少必要配置: {', '.join(missing)}"}
|
||||
return {"success": True, "message": f"邮件测试已发送: {config['smtp_user']} -> test@example.com"}
|
||||
|
||||
return {"success": False, "message": f"不支持的通知类型: {channel_type}"}
|
||||
sender = NotificationSender()
|
||||
test_data = {
|
||||
"title": "测试通知",
|
||||
"applicant": current_user.display_name or current_user.username,
|
||||
"db_type": "mysql",
|
||||
"target_database": "test_db",
|
||||
"sql_preview": "SELECT 1",
|
||||
"priority": "normal",
|
||||
"approver": "系统",
|
||||
"ticket_id": "TEST",
|
||||
"result": "成功",
|
||||
"duration": "10",
|
||||
"affected_rows": "0",
|
||||
}
|
||||
result = await sender.send(channel.channel_type, config, "ticket_created", test_data)
|
||||
return result
|
||||
@@ -1,103 +0,0 @@
|
||||
"""慢日志 API。"""
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
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.core.crypto import decrypt_password
|
||||
from app.models.user import User
|
||||
from app.models.instance import Instance
|
||||
from app.services.slow_log_collector import SlowLogCollector
|
||||
|
||||
router = APIRouter(prefix="/slow-logs", tags=["慢日志分析"])
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}")
|
||||
async def list_slow_logs(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""获取指定实例的慢日志。"""
|
||||
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="数据源不存在")
|
||||
|
||||
collector = SlowLogCollector()
|
||||
logs = collector.get_slow_logs(instance_id)
|
||||
|
||||
return {
|
||||
"instance_id": instance_id,
|
||||
"total": len(logs),
|
||||
"logs": [log.to_dict() for log in logs],
|
||||
}
|
||||
|
||||
|
||||
@router.post("/instances/{instance_id}/collect")
|
||||
async def collect_slow_logs(
|
||||
instance_id: int,
|
||||
database: str = "",
|
||||
limit: int = 50,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""手动收集慢日志。"""
|
||||
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 not database:
|
||||
database = instance.database or ""
|
||||
|
||||
collector = SlowLogCollector()
|
||||
instance.password = decrypt_password(instance.password)
|
||||
|
||||
try:
|
||||
if instance.db_type == "mysql":
|
||||
logs = await collector.collect_mysql_slow_logs(instance, database, limit)
|
||||
elif instance.db_type in ("postgres", "postgresql"):
|
||||
logs = await collector.collect_postgres_slow_logs(instance, database, limit)
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail=f"不支持的类型: {instance.db_type}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"收集失败: {str(e)}")
|
||||
|
||||
await collector.save_slow_logs(instance_id, logs)
|
||||
report = collector.get_report(instance_id)
|
||||
|
||||
return report.to_dict()
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}/report")
|
||||
async def slow_log_report(
|
||||
instance_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin", "dba")),
|
||||
):
|
||||
"""获取慢日志分析报告。"""
|
||||
collector = SlowLogCollector()
|
||||
report = collector.get_report(instance_id)
|
||||
return report.to_dict()
|
||||
|
||||
|
||||
@router.get("/dashboard/top")
|
||||
async def dashboard_top_queries(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""仪表盘慢查询 TOP 10。"""
|
||||
collector = SlowLogCollector()
|
||||
all_logs = []
|
||||
for instance_id, logs in collector._logs.items():
|
||||
all_logs.extend(logs)
|
||||
|
||||
# 全局 TOP 10
|
||||
top = sorted(all_logs, key=lambda x: x.avg_time_ms, reverse=True)[:10]
|
||||
return {
|
||||
"total_instances": len(collector._logs),
|
||||
"top_queries": [q.to_dict() for q in top],
|
||||
}
|
||||
@@ -5,7 +5,7 @@ from app.api.v1.endpoints import (
|
||||
announcement, notification, ldap_config,
|
||||
instance_detail, backup, redis, dictionary,
|
||||
instance_group, masking_rule, rollback,
|
||||
workflow, impact, schema_drift, slow_log, pool,
|
||||
workflow, impact, schema_drift, pool,
|
||||
mongo, query_history, sql_assistant, db_types,
|
||||
settings,
|
||||
)
|
||||
@@ -34,7 +34,6 @@ api_router.include_router(rollback.router)
|
||||
api_router.include_router(workflow.router)
|
||||
api_router.include_router(impact.router)
|
||||
api_router.include_router(schema_drift.router)
|
||||
api_router.include_router(slow_log.router)
|
||||
api_router.include_router(pool.router)
|
||||
api_router.include_router(query_history.router)
|
||||
api_router.include_router(sql_assistant.router)
|
||||
|
||||
Reference in new issue
Block a user