feat: grouped nav tree with badges, dashboard events section with filtered chart
Navigation: - Restructure flat nav into grouped tree: Notification (Trackers, Configs, Templates), Commands (same), Bots (Telegram), Settings (Common, Users) - Collapsible groups with expand/collapse state persisted in localStorage - Auto-expand group containing the active page - Counter badges on groups (sum of children) and individual items - New /api/status/counts endpoint for nav badge data - Mobile bottom nav uses flattened key pages Dashboard: - Rename "Recent Events" to "Events" - Move chart under Events section (after filters, before event list) - Filters (event type, provider, search) now affect both the event list AND the chart simultaneously - Add event_type, provider_id, search filter params to /api/status/chart
This commit is contained in:
@@ -8,7 +8,19 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ..auth.dependencies import get_current_user
|
||||
from ..database.engine import get_session
|
||||
from ..database.models import NotificationTarget, NotificationTracker, ServiceProvider, EventLog, User
|
||||
from ..database.models import (
|
||||
CommandConfig,
|
||||
CommandTemplateConfig,
|
||||
CommandTracker,
|
||||
EventLog,
|
||||
NotificationTarget,
|
||||
NotificationTracker,
|
||||
ServiceProvider,
|
||||
TelegramBot,
|
||||
TemplateConfig,
|
||||
TrackingConfig,
|
||||
User,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/status", tags=["status"])
|
||||
|
||||
@@ -93,13 +105,52 @@ async def get_status(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/counts")
|
||||
async def get_nav_counts(
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
"""Return entity counts for sidebar navigation badges."""
|
||||
counts = {}
|
||||
for model, key in [
|
||||
(ServiceProvider, "providers"),
|
||||
(NotificationTracker, "notification_trackers"),
|
||||
(TrackingConfig, "tracking_configs"),
|
||||
(TemplateConfig, "template_configs"),
|
||||
(NotificationTarget, "targets"),
|
||||
(TelegramBot, "telegram_bots"),
|
||||
(CommandTracker, "command_trackers"),
|
||||
(CommandConfig, "command_configs"),
|
||||
(CommandTemplateConfig, "command_template_configs"),
|
||||
]:
|
||||
count = (await session.exec(
|
||||
select(func.count()).select_from(model).where(model.user_id == user.id)
|
||||
)).one()
|
||||
counts[key] = count
|
||||
|
||||
# System-owned templates (user_id=0) count as well
|
||||
for model, key in [
|
||||
(TemplateConfig, "template_configs"),
|
||||
(CommandTemplateConfig, "command_template_configs"),
|
||||
]:
|
||||
system_count = (await session.exec(
|
||||
select(func.count()).select_from(model).where(model.user_id == 0)
|
||||
)).one()
|
||||
counts[key] += system_count
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
@router.get("/chart")
|
||||
async def get_event_chart(
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
days: int = Query(14, ge=1, le=90),
|
||||
event_type: str | None = Query(None),
|
||||
provider_id: int | None = Query(None),
|
||||
search: str | None = Query(None),
|
||||
):
|
||||
"""Return daily event counts by type for the last N days."""
|
||||
"""Return daily event counts by type for the last N days, with optional filters."""
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
|
||||
|
||||
day_col = func.date(EventLog.created_at)
|
||||
@@ -112,10 +163,21 @@ async def get_event_chart(
|
||||
)
|
||||
.join(NotificationTracker, EventLog.tracker_id == NotificationTracker.id)
|
||||
.where(NotificationTracker.user_id == user.id, EventLog.created_at >= cutoff)
|
||||
.group_by(day_col, EventLog.event_type)
|
||||
.order_by(day_col)
|
||||
)
|
||||
|
||||
if event_type:
|
||||
query = query.where(EventLog.event_type == event_type)
|
||||
if provider_id is not None:
|
||||
query = query.where(EventLog.provider_id == provider_id)
|
||||
if search:
|
||||
query = query.where(
|
||||
EventLog.collection_name.contains(search)
|
||||
| EventLog.tracker_name.contains(search)
|
||||
| EventLog.provider_name.contains(search)
|
||||
)
|
||||
|
||||
query = query.group_by(day_col, EventLog.event_type).order_by(day_col)
|
||||
|
||||
rows = (await session.exec(query)).all()
|
||||
|
||||
# Build a dict: { "2026-03-15": { "assets_added": 18, ... }, ... }
|
||||
|
||||
Reference in New Issue
Block a user