- Event ORM model with status/level enums, domain categories, review tracking - Alembic migration e7f3a9b1c5d0 for events table - Event crawler service with placeholder data (12 sample events) - GET /api/v1/events with status/domain/search filtering and overdue_days - EventArchivePage with filter toolbar (status, domain, search) and adaptive card grid - Status badges, level badges, domain tags, overdue markers on event cards - Loading/error/empty states, hover-to-honey card interaction Co-Authored-By: Claude <noreply@anthropic.com>
163 lines
5.9 KiB
Python
163 lines
5.9 KiB
Python
"""Event Crawler — 爬取 hm.zhiweidata.ai/events 数据存入 events 表。
|
|
|
|
TODO: 替换为真实爬虫逻辑。当前返回占位数据。
|
|
"""
|
|
import logging
|
|
from datetime import datetime, date, timedelta, timezone
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import async_session
|
|
from app.models.event import Event, EventStatus, EventLevel
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PLACEHOLDER_EVENTS = [
|
|
{
|
|
"title": "taiwan Gateway 频繁重启",
|
|
"level": EventLevel.A,
|
|
"domain": "gateway",
|
|
"status": EventStatus.ACTIVE,
|
|
"occurred_date": date.today() - timedelta(days=1),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(hours=6),
|
|
"no_new_info_count": 0,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(hours=6),
|
|
},
|
|
{
|
|
"title": "guangxi 爬虫数据延迟",
|
|
"level": EventLevel.B,
|
|
"domain": "crawler",
|
|
"status": EventStatus.ACTIVE,
|
|
"occurred_date": date.today() - timedelta(days=2),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=1),
|
|
"no_new_info_count": 1,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(hours=12),
|
|
},
|
|
{
|
|
"title": "飞书 Webhook 推送异常",
|
|
"level": EventLevel.B,
|
|
"domain": "feishu",
|
|
"status": EventStatus.FADING,
|
|
"occurred_date": date.today() - timedelta(days=5),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=2),
|
|
"no_new_info_count": 3,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(days=1),
|
|
},
|
|
{
|
|
"title": "ops-engineer 日报生成失败",
|
|
"level": EventLevel.C,
|
|
"domain": "reporter",
|
|
"status": EventStatus.FADING,
|
|
"occurred_date": date.today() - timedelta(days=7),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=3),
|
|
"no_new_info_count": 4,
|
|
"next_review": datetime.now(timezone.utc) - timedelta(days=1),
|
|
},
|
|
{
|
|
"title": "hebo 数据库连接超时",
|
|
"level": EventLevel.A,
|
|
"domain": "database",
|
|
"status": EventStatus.DISSIPATING,
|
|
"occurred_date": date.today() - timedelta(days=14),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=7),
|
|
"no_new_info_count": 6,
|
|
"next_review": datetime.now(timezone.utc) - timedelta(days=3),
|
|
},
|
|
{
|
|
"title": "密钥轮换提醒 (taiwan)",
|
|
"level": EventLevel.C,
|
|
"domain": "security",
|
|
"status": EventStatus.DISSIPATING,
|
|
"occurred_date": date.today() - timedelta(days=20),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=10),
|
|
"no_new_info_count": 5,
|
|
"next_review": datetime.now(timezone.utc) - timedelta(days=5),
|
|
},
|
|
{
|
|
"title": "Gateway 配置同步冲突",
|
|
"level": EventLevel.B,
|
|
"domain": "gateway",
|
|
"status": EventStatus.CLOSED,
|
|
"occurred_date": date.today() - timedelta(days=30),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=25),
|
|
"no_new_info_count": 8,
|
|
"next_review": None,
|
|
},
|
|
{
|
|
"title": "网络延迟告警 (overseas node)",
|
|
"level": EventLevel.B,
|
|
"domain": "network",
|
|
"status": EventStatus.ACTIVE,
|
|
"occurred_date": date.today() - timedelta(days=3),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(hours=12),
|
|
"no_new_info_count": 2,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(hours=18),
|
|
},
|
|
{
|
|
"title": "存储空间不足告警",
|
|
"level": EventLevel.C,
|
|
"domain": "storage",
|
|
"status": EventStatus.ACTIVE,
|
|
"occurred_date": date.today() - timedelta(days=4),
|
|
"last_reviewed": None,
|
|
"no_new_info_count": 0,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(days=1),
|
|
},
|
|
{
|
|
"title": "Monitor 心跳超时",
|
|
"level": EventLevel.A,
|
|
"domain": "monitor",
|
|
"status": EventStatus.ACTIVE,
|
|
"occurred_date": date.today() - timedelta(hours=8),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(hours=4),
|
|
"no_new_info_count": 0,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(hours=2),
|
|
},
|
|
{
|
|
"title": "Scheduler 任务积压",
|
|
"level": EventLevel.B,
|
|
"domain": "scheduler",
|
|
"status": EventStatus.FADING,
|
|
"occurred_date": date.today() - timedelta(days=6),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(days=2),
|
|
"no_new_info_count": 3,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(hours=10),
|
|
},
|
|
{
|
|
"title": "API 响应延迟 (p95 > 5s)",
|
|
"level": EventLevel.A,
|
|
"domain": "api",
|
|
"status": EventStatus.ACTIVE,
|
|
"occurred_date": date.today() - timedelta(hours=12),
|
|
"last_reviewed": datetime.now(timezone.utc) - timedelta(hours=3),
|
|
"no_new_info_count": 0,
|
|
"next_review": datetime.now(timezone.utc) + timedelta(hours=4),
|
|
},
|
|
]
|
|
|
|
|
|
async def seed_placeholder_events() -> list[Event]:
|
|
"""向数据库写入占位事件数据(幂等:清空后重写)。"""
|
|
async with async_session() as db:
|
|
existing = await db.execute(select(Event).limit(1))
|
|
if existing.scalar_one_or_none():
|
|
logger.info("events 表已有数据,跳过占位种子")
|
|
return []
|
|
|
|
events = []
|
|
for data in PLACEHOLDER_EVENTS:
|
|
event = Event(**data)
|
|
db.add(event)
|
|
events.append(event)
|
|
await db.commit()
|
|
logger.info("写入 %d 条占位事件", len(events))
|
|
return events
|
|
|
|
|
|
async def run_crawl():
|
|
"""定时爬取入口(TODO: 真实 HTTP 爬取 hm.zhiweidata.ai/events)。"""
|
|
logger.info("Event crawler 执行开始")
|
|
await seed_placeholder_events()
|
|
logger.info("Event crawler 执行结束")
|