from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import Optional from app.core.database import get_db from app.core.security import get_current_user from app.services.alert_service import AlertService from app.schemas._tz_util import serialize_dt_fields, serialize_dt_list, to_business_iso router = APIRouter( prefix="/alerts", tags=["告警管理"], dependencies=[Depends(get_current_user)], ) @router.get("", summary="获取告警列表") def get_alerts( status: Optional[str] = None, severity: Optional[str] = None, alert_type: Optional[str] = None, skip: int = 0, limit: int = 100, db: Session = Depends(get_db) ): """获取告警列表""" from app.models.alert import Alert query = db.query(Alert) if status: query = query.filter(Alert.status == status) if severity: query = query.filter(Alert.severity == severity) if alert_type: query = query.filter(Alert.alert_type == alert_type) total = query.count() items = query.order_by(Alert.created_at.desc()).offset(skip).limit(limit).all() return {"total": total, "items": serialize_dt_list(items)} @router.get("/{alert_id}", summary="获取告警详情") def get_alert(alert_id: int, db: Session = Depends(get_db)): from app.models.alert import Alert alert = db.query(Alert).filter(Alert.id == alert_id).first() if not alert: raise HTTPException(status_code=404, detail="告警不存在") return serialize_dt_fields(alert) @router.post("/{alert_id}/acknowledge", summary="确认告警") def acknowledge_alert(alert_id: int, acknowledged_by: str = "system", db: Session = Depends(get_db)): """确认告警""" alert = AlertService.acknowledge_alert(db, alert_id, acknowledged_by) if not alert: raise HTTPException(status_code=404, detail="告警不存在") return {"message": "告警已确认", "alert": alert} @router.post("/{alert_id}/resolve", summary="解决告警") def resolve_alert(alert_id: int, resolved_by: str = "system", notes: str = "", db: Session = Depends(get_db)): """标记告警为已解决""" alert = AlertService.resolve_alert(db, alert_id, resolved_by, notes) if not alert: raise HTTPException(status_code=404, detail="告警不存在") return {"message": "告警已解决", "alert": alert} @router.post("/{alert_id}/ignore", summary="忽略告警") def ignore_alert(alert_id: int, db: Session = Depends(get_db)): """忽略告警""" alert = AlertService.ignore_alert(db, alert_id) if not alert: raise HTTPException(status_code=404, detail="告警不存在") return {"message": "告警已忽略", "alert": alert} @router.post("/detect/run", summary="运行所有检测") def run_detection(db: Session = Depends(get_db)): """立即运行所有告警检测""" results = AlertService.run_all_detections(db) return {"message": "检测完成", "results": results} @router.get("/statistics/summary", summary="告警统计摘要") def get_alert_statistics(db: Session = Depends(get_db)): """获取告警统计信息""" stats = AlertService.get_statistics(db) return stats # ========== MAC 白名单管理 ========== @router.get("/whitelist/macs", summary="获取 MAC 白名单") def get_mac_whitelist( skip: int = 0, limit: int = 100, db: Session = Depends(get_db) ): """获取 MAC 地址白名单""" total, items = AlertService.get_mac_whitelist(db, skip=skip, limit=limit) return {"total": total, "items": items} @router.post("/whitelist/macs", summary="添加 MAC 到白名单") def add_mac_to_whitelist( mac_address: str, description: Optional[str] = None, owner: Optional[str] = None, db: Session = Depends(get_db) ): """添加 MAC 地址到白名单""" whitelist_mac = AlertService.add_mac_to_whitelist( db, mac_address=mac_address, description=description, owner=owner ) return {"message": "MAC 已添加到白名单", "item": whitelist_mac} @router.delete("/whitelist/macs/{mac_id}", summary="从白名单移除 MAC") def remove_mac_from_whitelist(mac_id: int, db: Session = Depends(get_db)): """从白名单移除 MAC 地址""" success = AlertService.remove_mac_from_whitelist(db, mac_id) if not success: raise HTTPException(status_code=404, detail="MAC 不存在") return {"message": "MAC 已从白名单移除"} # ========== 单独检测接口 ========== @router.post("/detect/ip-conflicts", summary="检测 IP 冲突") def detect_ip_conflicts(db: Session = Depends(get_db)): """仅检测 IP 冲突""" conflicts = AlertService.detect_ip_conflicts(db) return { "count": len(conflicts), "conflicts": conflicts } @router.post("/detect/unauthorized", summary="检测未授权接入") def detect_unauthorized_access(db: Session = Depends(get_db)): """仅检测未授权接入""" unauthorized = AlertService.detect_unauthorized_access(db) return { "count": len(unauthorized), "devices": unauthorized } @router.post("/detect/subnet-exhaustion", summary="检测网段耗尽") def detect_subnet_exhaustion(db: Session = Depends(get_db)): """仅检测网段耗尽""" exhaustion = AlertService.detect_subnet_exhaustion(db) return { "count": len(exhaustion), "subnets": exhaustion } @router.post("/detect/new-devices", summary="检测新设备") def detect_new_devices(db: Session = Depends(get_db)): """仅检测新发现的设备""" new_devices = AlertService.detect_new_devices(db) return { "count": len(new_devices), "devices": new_devices } @router.post("/detect/offline-devices", summary="检测离线设备") def detect_offline_devices(db: Session = Depends(get_db)): """仅检测离线设备""" offline_devices = AlertService.detect_device_offline(db) return { "count": len(offline_devices), "devices": offline_devices } # ========== 飞书告警通知 ========== @router.get("/notify/feishu/status", summary="获取飞书通知配置状态") def get_feishu_status(): """查看飞书通知是否已配置(不返回密钥等敏感信息)""" from app.services.feishu_service import get_feishu_service svc = get_feishu_service() return { "enabled": svc.enabled, "configured": bool(svc.webhook_url), "webhook_set": bool(svc.webhook_url), "secret_set": bool(svc.secret), "min_severity": svc.min_severity, "alert_types": svc.alert_types, "cani_push": svc.is_configured(), } @router.post("/notify/feishu/test", summary="发送飞书测试告警") def test_feishu_notification(): """发送一条测试告警到飞书,验证 webhook 配置是否正确""" from app.services.feishu_service import get_feishu_service svc = get_feishu_service() if not svc.is_configured(): raise HTTPException( status_code=400, detail="飞书通知未启用或未配置 webhook。请在 backend/.env 设置 FEISHU_ENABLED=true 和 FEISHU_WEBHOOK_URL" ) ok = svc.send_alert({ "alert_type": "new_device_detected", "severity": "warning", "title": "飞书通知测试", "message": "这是一条测试告警,用于验证飞书机器人 webhook 配置是否正常。", "ip_address_str": "172.16.0.1", "mac_address": "00:11:22:33:44:55", }) if ok: return {"message": "飞书测试通知发送成功", "success": True} raise HTTPException(status_code=500, detail="飞书测试通知发送失败,请检查 webhook 地址和日志")