agnes-the-ai-analyst/app/web/templates/admin_scheduler_runs.html
ZdenekSrotyr e86dd5edc5 fix(anthropic): strict json_schema (additionalProperties=false) + add /admin/scheduler-runs UI
E2E test on a real BQ deploy showed every verification-extraction call
fails with HTTP 400 invalid_request_error: "output_config.format.schema:
For 'object' type, 'additionalProperties' must be explicitly set to false".
The Anthropic structured-output API now requires the field on every object
node in the json_schema. Fix: connectors/llm/anthropic_provider.py wraps
the caller-supplied schema through a recursive _strict_json_schema()
walker that adds the field where missing (preserving any explicit
override), then passes the strict variant to the API. Six unit tests in
TestStrictJsonSchema pin the recursion across nested objects, array items,
and the no-mutation invariant.

Adds /admin/scheduler-runs — a read-only admin page that surfaces the
last 200 audit-log entries from scheduler-driven actions. New
AuditRepository.query_actions(actions, limit) helper, new admin nav
entry. Failed scheduler ticks (HTTP 401, network errors) don't reach
the audit_log; the page calls that out with a hint to set
SCHEDULER_API_TOKEN if no rows show up.
2026-05-05 08:00:57 +02:00

86 lines
3.5 KiB
HTML

{% extends "base.html" %}
{% block title %}Scheduler runs — {{ config.INSTANCE_NAME }}{% endblock %}
{% block content %}
<style>
.container:has(.sched-page) { max-width: none; padding: 24px 16px; }
.sched-page { max-width: 1400px; margin: 0 auto; padding: 0; }
.sched-title { margin: 0 0 8px 0; font-size: 22px; font-weight: 600; }
.sched-help { color: var(--text-secondary, #6b7280); font-size: 13px; margin-bottom: 20px; }
.sched-help code { background: var(--border-light, #f3f4f6); padding: 1px 6px; border-radius: 4px; font-size: 12px; }
.sched-table-wrap {
background: var(--surface, #fff);
border: 1px solid var(--border, #e5e7eb);
border-radius: 12px;
overflow-x: auto;
}
.sched-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.sched-table thead th {
text-align: left; padding: 12px 16px;
background: var(--border-light, #f9fafb);
border-bottom: 1px solid var(--border, #e5e7eb);
font-weight: 600; color: var(--text-secondary, #6b7280);
font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px;
white-space: nowrap;
}
.sched-table tbody td {
padding: 10px 16px;
border-bottom: 1px solid var(--border-light, #f3f4f6);
vertical-align: top;
}
.sched-table tbody tr:last-child td { border-bottom: none; }
.sched-table tbody tr:hover { background: var(--border-light, #fafafa); }
.sched-table .ts { white-space: nowrap; color: var(--text-secondary, #6b7280); font-variant-numeric: tabular-nums; }
.sched-table .action { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.sched-table .duration { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-secondary, #6b7280); }
.sched-table .params {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
color: var(--text-secondary, #6b7280); max-width: 600px;
word-break: break-word; white-space: pre-wrap;
}
.empty {
padding: 40px 16px; text-align: center;
color: var(--text-secondary, #6b7280); font-size: 13px;
}
</style>
<div class="sched-page">
<h1 class="sched-title">Scheduler runs</h1>
<p class="sched-help">
Last 200 audited scheduler-driven admin actions, newest first.
Tracked actions: {% for a in actions %}<code>{{ a }}</code>{% if not loop.last %} {% endif %}{% endfor %}.
Failed ticks (HTTP 401, network errors) live only in the scheduler container's
stdout — <code>docker logs agnes-scheduler-1</code>. Set <code>SCHEDULER_API_TOKEN</code>
in <code>.env</code> if you see no rows here.
</p>
<div class="sched-table-wrap">
{% if rows %}
<table class="sched-table">
<thead>
<tr>
<th>When</th>
<th>Action</th>
<th>Resource</th>
<th>Duration</th>
<th>Result / params</th>
</tr>
</thead>
<tbody>
{% for r in rows %}
<tr>
<td class="ts">{{ r.timestamp.strftime("%Y-%m-%d %H:%M:%S") if r.timestamp else "" }}</td>
<td class="action">{{ r.action }}</td>
<td>{{ r.resource or "" }}</td>
<td class="duration">{% if r.duration_ms is not none %}{{ r.duration_ms }} ms{% endif %}</td>
<td class="params">{{ r.params or r.result or "" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty">No scheduler runs in audit_log yet. The scheduler may not be authenticated — check <code>SCHEDULER_API_TOKEN</code>.</div>
{% endif %}
</div>
</div>
{% endblock %}