Compare commits
4 Commits
feature/da
...
fix/run-ro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fa28e8e47 | ||
| 1c4ad0bd9d | |||
|
|
52c5cc1af1 | ||
| c97ced52b4 |
@@ -29,7 +29,7 @@ def dashboard(request: Request, db: Session = Depends(get_db), settings: Setting
|
|||||||
counts = stats_payload(db)
|
counts = stats_payload(db)
|
||||||
counts["runs"] = db.scalar(select(func.count()).select_from(CrawlRun)) or 0
|
counts["runs"] = db.scalar(select(func.count()).select_from(CrawlRun)) or 0
|
||||||
counts["errors"] = db.scalar(select(func.count()).select_from(CrawlError)) or 0
|
counts["errors"] = db.scalar(select(func.count()).select_from(CrawlError)) or 0
|
||||||
run_models = db.scalars(select(CrawlRun).order_by(desc(CrawlRun.started_at)).limit(10)).all()
|
run_models = db.scalars(select(CrawlRun).order_by(desc(CrawlRun.started_at)).limit(5)).all()
|
||||||
runs = [run_payload(run) for run in run_models]
|
runs = [run_payload(run) for run in run_models]
|
||||||
return _render(request, "dashboard.html", {"counts": counts, "runs": runs, "latest_run": runs[0] if runs else None})
|
return _render(request, "dashboard.html", {"counts": counts, "runs": runs, "latest_run": runs[0] if runs else None})
|
||||||
|
|
||||||
|
|||||||
@@ -59,10 +59,23 @@
|
|||||||
applyColumns(columns);
|
applyColumns(columns);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupClickableRows() {
|
||||||
|
const openRow = (row) => {
|
||||||
|
window.location.href = row.dataset.rowHref;
|
||||||
|
};
|
||||||
|
|
||||||
document.querySelectorAll("[data-row-href]").forEach((row) => {
|
document.querySelectorAll("[data-row-href]").forEach((row) => {
|
||||||
row.addEventListener("click", (event) => {
|
row.addEventListener("click", (event) => {
|
||||||
if (event.target.closest("a, button, input, select, label")) return;
|
if (event.target.closest("a, button, input, select, label")) return;
|
||||||
window.location.href = row.dataset.rowHref;
|
openRow(row);
|
||||||
|
});
|
||||||
|
row.addEventListener("keydown", (event) => {
|
||||||
|
if (!["Enter", " "].includes(event.key)) return;
|
||||||
|
if (event.target.closest("a, button, input, select, label")) return;
|
||||||
|
event.preventDefault();
|
||||||
|
openRow(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -107,5 +120,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupColumns();
|
setupColumns();
|
||||||
|
setupClickableRows();
|
||||||
setupProgress();
|
setupProgress();
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<thead><tr><th class="table__head">ID</th><th class="table__head">Статус</th><th class="table__head">Обработано</th><th class="table__head">Ошибки</th><th class="table__head">Старт</th></tr></thead>
|
<thead><tr><th class="table__head">ID</th><th class="table__head">Статус</th><th class="table__head">Обработано</th><th class="table__head">Ошибки</th><th class="table__head">Старт</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for run in runs %}
|
{% for run in runs %}
|
||||||
<tr><td class="table__cell">{{ run.id }}</td><td class="table__cell">{{ run.status_display }}</td><td class="table__cell">{{ run.parsed_count }}</td><td class="table__cell">{{ run.error_count }}</td><td class="table__cell">{{ run.started_display }}</td></tr>
|
<tr class="table__row" data-row-href="/admin/runs/{{ run.id }}" role="link" tabindex="0"><td class="table__cell">{{ run.id }}</td><td class="table__cell">{{ run.status_display }}</td><td class="table__cell">{{ run.parsed_count }}</td><td class="table__cell">{{ run.error_count }}</td><td class="table__cell">{{ run.started_display }}</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<thead><tr><th class="table__head">ID</th><th class="table__head">Статус</th><th class="table__head">Найдено</th><th class="table__head">Обработано</th><th class="table__head">Новые</th><th class="table__head">Ошибки</th><th class="table__head">Уволены</th><th class="table__head">Старт</th></tr></thead>
|
<thead><tr><th class="table__head">ID</th><th class="table__head">Статус</th><th class="table__head">Найдено</th><th class="table__head">Обработано</th><th class="table__head">Новые</th><th class="table__head">Ошибки</th><th class="table__head">Уволены</th><th class="table__head">Старт</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for run in runs %}
|
{% for run in runs %}
|
||||||
<tr class="table__row" data-row-href="/admin/runs/{{ run.id }}"><td class="table__cell"><a class="admin__link" href="/admin/runs/{{ run.id }}">{{ run.id }}</a></td><td class="table__cell">{{ run.status_display }}</td><td class="table__cell">{{ run.found_count }}</td><td class="table__cell">{{ run.parsed_count }}</td><td class="table__cell">{{ run.new_count }}</td><td class="table__cell">{{ run.error_count }}</td><td class="table__cell">{{ run.dismissed_count }}</td><td class="table__cell">{{ run.started_display }}</td></tr>
|
<tr class="table__row" data-row-href="/admin/runs/{{ run.id }}" role="link" tabindex="0"><td class="table__cell">{{ run.id }}</td><td class="table__cell">{{ run.status_display }}</td><td class="table__cell">{{ run.found_count }}</td><td class="table__cell">{{ run.parsed_count }}</td><td class="table__cell">{{ run.new_count }}</td><td class="table__cell">{{ run.error_count }}</td><td class="table__cell">{{ run.dismissed_count }}</td><td class="table__cell">{{ run.started_display }}</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
APP_VERSION = "0.4.1"
|
APP_VERSION = "0.4.3"
|
||||||
FRONTEND_VERSION = "0.4.1"
|
FRONTEND_VERSION = "0.4.3"
|
||||||
BACKEND_VERSION = "0.4.1"
|
BACKEND_VERSION = "0.4.3"
|
||||||
|
|||||||
@@ -35,11 +35,20 @@ def test_admin_employees_route_redirects_to_directory():
|
|||||||
assert 'RedirectResponse("/admin/directory", status_code=303)' in source
|
assert 'RedirectResponse("/admin/directory", status_code=303)' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_limits_latest_runs_to_five():
|
||||||
|
source = Path("app/admin.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "order_by(desc(CrawlRun.started_at)).limit(5)" in source
|
||||||
|
assert "order_by(desc(CrawlRun.started_at)).limit(10)" not in source
|
||||||
|
|
||||||
|
|
||||||
def test_runs_template_links_to_run_detail():
|
def test_runs_template_links_to_run_detail():
|
||||||
template = Path("app/templates/runs.html").read_text(encoding="utf-8")
|
template = Path("app/templates/runs.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert 'href="/admin/runs/{{ run.id }}"' in template
|
|
||||||
assert 'data-row-href="/admin/runs/{{ run.id }}"' in template
|
assert 'data-row-href="/admin/runs/{{ run.id }}"' in template
|
||||||
|
assert 'role="link"' in template
|
||||||
|
assert 'tabindex="0"' in template
|
||||||
|
assert '<a class="admin__link" href="/admin/runs/{{ run.id }}">' not in template
|
||||||
|
|
||||||
|
|
||||||
def test_run_detail_template_extends_base_and_shows_change_groups():
|
def test_run_detail_template_extends_base_and_shows_change_groups():
|
||||||
@@ -61,3 +70,20 @@ def test_dashboard_metric_cards_link_to_admin_targets():
|
|||||||
assert '/admin/runs/{{ latest_run.id }}#new-employees' in template
|
assert '/admin/runs/{{ latest_run.id }}#new-employees' in template
|
||||||
assert 'href="/admin/directory?status=dismissed"' in template
|
assert 'href="/admin/directory?status=dismissed"' in template
|
||||||
assert 'href="/admin/runs"' in template
|
assert 'href="/admin/runs"' in template
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_latest_run_rows_link_to_run_detail():
|
||||||
|
template = Path("app/templates/dashboard.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert 'data-row-href="/admin/runs/{{ run.id }}"' in template
|
||||||
|
assert 'role="link"' in template
|
||||||
|
assert 'tabindex="0"' in template
|
||||||
|
assert '<a class="admin__link" href="/admin/runs/{{ run.id }}">' not in template
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_js_supports_keyboard_activation_for_clickable_rows():
|
||||||
|
source = Path("app/static/admin.js").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert 'addEventListener("keydown"' in source
|
||||||
|
assert '"Enter"' in source
|
||||||
|
assert '" "' in source
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def test_health_returns_versions():
|
|||||||
response = client.get("/api/health")
|
response = client.get("/api/health")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["backend_version"] == "0.4.1"
|
assert response.json()["backend_version"] == "0.4.3"
|
||||||
|
|
||||||
|
|
||||||
def test_mcp_requires_token_and_lists_tools():
|
def test_mcp_requires_token_and_lists_tools():
|
||||||
|
|||||||
Reference in New Issue
Block a user