feat: add logging routes and update health page to display system logs
CI / lint-test-build (push) Failing after 12s
CI / lint-test-build (push) Failing after 12s
This commit is contained in:
@@ -1493,3 +1493,49 @@ async def dashboard_api_pairings_sync(request: Request) -> HTMLResponse:
|
||||
name="partials/pairings_table.html",
|
||||
context={"request": request, "pairings": pairings},
|
||||
)
|
||||
|
||||
|
||||
# ── Log routes ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/dashboard/fragment/logs", response_class=HTMLResponse)
|
||||
async def dashboard_logs_fragment(
|
||||
request: Request,
|
||||
level: str | None = None,
|
||||
page: int = 1,
|
||||
per_page: int = 50,
|
||||
) -> HTMLResponse:
|
||||
"""HTMX fragment: log table for health page."""
|
||||
repo = LogRepository(request.app.state.store)
|
||||
offset = (page - 1) * per_page
|
||||
records = await repo.query(level=level, limit=per_page, offset=offset)
|
||||
total = await repo.count_filtered(level=level)
|
||||
total_pages = max(1, (total + per_page - 1) // per_page)
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="partials/log_table.html",
|
||||
context={
|
||||
"request": request,
|
||||
"records": records,
|
||||
"page": page,
|
||||
"total_pages": total_pages,
|
||||
"total": total,
|
||||
"current_level": level or "all",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/dashboard/api/logging/aggregate", response_class=JSONResponse)
|
||||
async def dashboard_logging_aggregate(request: Request) -> JSONResponse:
|
||||
from arbitrade.logging.maintenance import run_log_aggregation
|
||||
|
||||
await run_log_aggregation(request.app.state.store)
|
||||
return JSONResponse({"status": "ok"})
|
||||
|
||||
|
||||
@router.post("/dashboard/api/logging/archive", response_class=JSONResponse)
|
||||
async def dashboard_logging_archive(request: Request) -> JSONResponse:
|
||||
from arbitrade.logging.maintenance import run_log_archive
|
||||
|
||||
count = await run_log_archive(request.app.state.store)
|
||||
return JSONResponse({"status": "ok", "archived": count})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "_base.html" %} {% block title %}Arbitrade Health Check{% endblock %}
|
||||
{% block header %} {% with page_title="Arbitrade Health Check",
|
||||
page_subtitle="Live system state." %} {% include "_header.html" %} {% endwith %}
|
||||
{% endblock %} {% block main_class %}shell{% endblock %} {% block content %}
|
||||
<section class="card">
|
||||
page_subtitle="Live system state and logs." %} {% include "_header.html" %} {%
|
||||
endwith %} {% endblock %} {% block main_class %}shell{% endblock %} {% block
|
||||
content %}
|
||||
|
||||
<section class="card" style="margin-bottom: 24px">
|
||||
<h1>Arbitrade Bootstrap Complete</h1>
|
||||
<p><span class="badge">Status: {{ status }}</span></p>
|
||||
<p>UTC: {{ time }}</p>
|
||||
@@ -18,4 +20,43 @@ page_subtitle="Live system state." %} {% include "_header.html" %} {% endwith %}
|
||||
</p>
|
||||
<pre id="health-json">{"status":"ok","service":"arbitrade"}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>System Logs</h2>
|
||||
<div class="toolbar" style="display: flex; gap: 8px; margin-bottom: 12px">
|
||||
<form
|
||||
hx-post="/dashboard/api/logging/aggregate"
|
||||
hx-target="#aggregate-result"
|
||||
hx-swap="innerHTML"
|
||||
style="display: inline"
|
||||
>
|
||||
<button type="submit" class="button secondary" style="font-size: 0.85rem">
|
||||
Aggregate Now
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
hx-post="/dashboard/api/logging/archive"
|
||||
hx-target="#archive-result"
|
||||
hx-swap="innerHTML"
|
||||
style="display: inline"
|
||||
>
|
||||
<button type="submit" class="button secondary" style="font-size: 0.85rem">
|
||||
Archive Old Logs
|
||||
</button>
|
||||
</form>
|
||||
<span id="aggregate-result" style="font-size: 0.85rem; opacity: 0.6"></span>
|
||||
<span id="archive-result" style="font-size: 0.85rem; opacity: 0.6"></span>
|
||||
</div>
|
||||
<div
|
||||
id="log-table-container"
|
||||
hx-get="/dashboard/fragment/logs"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div style="text-align: center; padding: 20px; opacity: 0.5">
|
||||
Loading logs...
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %} {% block scripts %}{% endblock %}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<div id="log-table-container">
|
||||
<div class="toolbar" style="display: flex; gap: 8px; margin-bottom: 12px">
|
||||
<select
|
||||
name="level"
|
||||
hx-get="/dashboard/fragment/logs"
|
||||
hx-target="#log-table-container"
|
||||
hx-trigger="change"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<option value="" {{ 'selected' if current_level == 'all' else '' }}>All</option>
|
||||
<option value="INFO" {{ 'selected' if current_level == 'INFO' else '' }}>INFO</option>
|
||||
<option value="WARNING" {{ 'selected' if current_level == 'WARNING' else '' }}>WARNING</option>
|
||||
<option value="ERROR" {{ 'selected' if current_level == 'ERROR' else '' }}>ERROR</option>
|
||||
<option value="CRITICAL" {{ 'selected' if current_level == 'CRITICAL' else '' }}>CRITICAL</option>
|
||||
</select>
|
||||
<span style="opacity: 0.6; font-size: 0.85rem">{{ total }} entries</span>
|
||||
</div>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.82rem">
|
||||
<thead>
|
||||
<tr style="border-bottom: 1px solid rgba(255,255,255,0.1); text-align: left">
|
||||
<th style="padding: 6px 8px">Time</th>
|
||||
<th style="padding: 6px 8px">Level</th>
|
||||
<th style="padding: 6px 8px">Logger</th>
|
||||
<th style="padding: 6px 8px">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in records %}
|
||||
<tr style="border-bottom: 1px solid rgba(255,255,255,0.04)">
|
||||
<td style="padding: 4px 8px; white-space: nowrap">
|
||||
{{ r.recorded_at.strftime('%H:%M:%S') if r.recorded_at else '—' }}
|
||||
</td>
|
||||
<td style="padding: 4px 8px">
|
||||
<span class="badge level-{{ r.level.lower() }}">{{ r.level }}</span>
|
||||
</td>
|
||||
<td style="padding: 4px 8px; opacity: 0.7; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
|
||||
{{ r.logger }}
|
||||
</td>
|
||||
<td style="padding: 4px 8px; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
|
||||
{{ r.message }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not records %}
|
||||
<tr>
|
||||
<td colspan="4" style="padding: 20px; text-align: center; opacity: 0.5">No log entries found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="toolbar" style="display: flex; gap: 8px; justify-content: center; margin-top: 12px">
|
||||
{% if page > 1 %}
|
||||
<button
|
||||
class="button secondary"
|
||||
hx-get="/dashboard/fragment/logs?level={{ current_level }}&page={{ page - 1 }}"
|
||||
hx-target="#log-table-container"
|
||||
hx-swap="outerHTML"
|
||||
>Previous</button>
|
||||
{% endif %}
|
||||
<span style="opacity: 0.6; font-size: 0.85rem; padding: 0 8px">Page {{ page }} / {{ total_pages }}</span>
|
||||
{% if page < total_pages %}
|
||||
<button
|
||||
class="button secondary"
|
||||
hx-get="/dashboard/fragment/logs?level={{ current_level }}&page={{ page + 1 }}"
|
||||
hx-target="#log-table-container"
|
||||
hx-swap="outerHTML"
|
||||
>Next</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user