Compare commits

..

1 Commits

Author SHA1 Message Date
01925fd3f0 Cove: historical run backfill, run detail popup, and docs update
- cove_importer.py: add _backfill_colorbar_runs() to reconstruct up to
  27 days of history from the D09F08 28-day colorbar when a new run is
  created; idempotent via external_id deduplication
- routes_cove.py: add GET /cove/run/<run_id>/detail endpoint returning
  structured Cove account info and per-datasource objects for popups
- routes_run_checks.py: add cove_summary to run payload for cove_api runs
  with readable datasource labels; hide mail section for Cove runs
- routes_jobs.py: add source_type to history_rows dict
- job_detail.html: Cove run rows clickable; JS routes to cove_run_detail;
  Cove summary panel added, mail section hidden for Cove runs
- run_checks.html: Cove summary panel added; JS handles cove_summary
- technical-notes-codex.md: document new route, popup behaviour, and
  historical backfill

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:01:05 +01:00
9 changed files with 8 additions and 96 deletions

View File

@ -163,21 +163,10 @@ def _groups_from_claims(claims: dict) -> set[str]:
return set()
def _captcha_enabled() -> bool:
"""Return True when the login captcha is enabled in SystemSettings."""
try:
s = SystemSettings.query.first()
if s is None:
return True # default on for new installs
return bool(getattr(s, "login_captcha_enabled", True))
except Exception:
return True # fail-safe: keep captcha on if DB unreachable
def captcha_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if request.method == "POST" and _captcha_enabled():
if request.method == "POST":
expected = session.get("captcha_answer")
provided = (request.form.get("captcha") or "").strip()
if not expected or provided != expected:
@ -195,7 +184,6 @@ def captcha_required(func):
return render_template(
"auth/login.html",
captcha_question=question,
captcha_enabled=True,
username=request.form.get("username", ""),
entra_sso_enabled=entra_ready,
)
@ -207,8 +195,6 @@ def captcha_required(func):
@auth_bp.route("/login", methods=["GET", "POST"])
@captcha_required
def login():
captcha_on = _captcha_enabled()
if request.method == "GET":
if not users_exist():
return redirect(url_for("auth.initial_setup"))
@ -225,7 +211,6 @@ def login():
return render_template(
"auth/login.html",
captcha_question=question,
captcha_enabled=captcha_on,
entra_sso_enabled=entra_ready,
)
@ -248,7 +233,6 @@ def login():
return render_template(
"auth/login.html",
captcha_question=question,
captcha_enabled=captcha_on,
username=username,
entra_sso_enabled=entra_ready,
)

View File

@ -741,7 +741,6 @@ def settings():
old_ui_timezone = settings.ui_timezone
old_require_daily_dashboard_visit = settings.require_daily_dashboard_visit
old_is_sandbox_environment = settings.is_sandbox_environment
old_login_captcha_enabled = getattr(settings, "login_captcha_enabled", True)
old_graph_tenant_id = settings.graph_tenant_id
old_graph_client_id = settings.graph_client_id
old_graph_mailbox = settings.graph_mailbox
@ -788,9 +787,6 @@ def settings():
# Checkbox: present in form = checked, absent = unchecked.
settings.is_sandbox_environment = bool(request.form.get("is_sandbox_environment"))
# Login captcha toggle — same form (General tab).
settings.login_captcha_enabled = bool(request.form.get("login_captcha_enabled"))
# Autotask integration
if "autotask_enabled" in request.form:
settings.autotask_enabled = bool(request.form.get("autotask_enabled"))
@ -985,8 +981,6 @@ def settings():
changes_general["require_daily_dashboard_visit"] = {"old": old_require_daily_dashboard_visit, "new": settings.require_daily_dashboard_visit}
if old_is_sandbox_environment != settings.is_sandbox_environment:
changes_general["is_sandbox_environment"] = {"old": old_is_sandbox_environment, "new": settings.is_sandbox_environment}
if old_login_captcha_enabled != settings.login_captcha_enabled:
changes_general["login_captcha_enabled"] = {"old": old_login_captcha_enabled, "new": settings.login_captcha_enabled}
if changes_general:
_log_admin_event(

View File

@ -1447,7 +1447,6 @@ def run_migrations() -> None:
migrate_cc_accounts_repo_unique_key()
migrate_cc_remove_synthetic_missed_runs()
migrate_entra_sso_settings()
migrate_system_settings_login_captcha()
print("[migrations] All migrations completed.")
@ -1555,37 +1554,6 @@ def migrate_system_settings_sandbox_environment() -> None:
print(f"[migrations] Failed to migrate system_settings.is_sandbox_environment: {exc}")
def migrate_system_settings_login_captcha() -> None:
"""Add login_captcha_enabled column to system_settings if missing.
Default TRUE so existing installs keep captcha enabled after the upgrade.
"""
table = "system_settings"
column = "login_captcha_enabled"
try:
engine = db.get_engine()
except Exception as exc:
print(f"[migrations] Could not get engine for login_captcha_enabled migration: {exc}")
return
try:
if _column_exists(table, column):
print("[migrations] system_settings.login_captcha_enabled already exists.")
return
with engine.begin() as conn:
conn.execute(
text(
f'ALTER TABLE "{table}" ADD COLUMN {column} BOOLEAN NOT NULL DEFAULT TRUE'
)
)
print("[migrations] migrate_system_settings_login_captcha completed.")
except Exception as exc:
print(f"[migrations] Failed to migrate system_settings.login_captcha_enabled: {exc}")
def migrate_performance_indexes() -> None:
"""Add performance indexes for frequently queried foreign key columns.

View File

@ -123,9 +123,6 @@ class SystemSettings(db.Model):
# this is not a production environment.
is_sandbox_environment = db.Column(db.Boolean, nullable=False, default=False)
# Login page captcha (simple math question). Default True for new installs.
login_captcha_enabled = db.Column(db.Boolean, nullable=False, default=True)
# Cove Data Protection integration settings
cove_enabled = db.Column(db.Boolean, nullable=False, default=False)
cove_api_url = db.Column(db.String(255), nullable=True) # default: https://api.backup.management/jsonapi

View File

@ -348,13 +348,10 @@ body.bc-body {
============================================================ */
.bc-main-auth .bc-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 32px 16px;
max-width: 480px;
margin: 0 auto;
width: 100%;
}
/* ============================================================

View File

@ -8,13 +8,13 @@
top: 30px;
left: -60px;
width: 250px;
background-color: rgba(220, 53, 69, 0.45);
color: rgba(255, 255, 255, 0.9);
background-color: #dc3545;
color: white;
text-align: center;
transform: rotate(-45deg);
z-index: 9999;
pointer-events: none; /* Banner itself is not clickable, elements behind it are */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.sandbox-banner-text {

View File

@ -1,6 +1,7 @@
{% extends "layout/base.html" %}
{% block content %}
<div>
<div class="row justify-content-center">
<div class="col-md-4">
<h2 class="mb-3">Login</h2>
<form method="post">
<div class="mb-3">
@ -24,7 +25,6 @@
required
/>
</div>
{% if captcha_enabled %}
<div class="mb-3">
<label class="form-label">Captcha: {{ captcha_question }}</label>
<input
@ -34,7 +34,6 @@
required
/>
</div>
{% endif %}
<button type="submit" class="btn btn-primary w-100">Login</button>
<div class="mt-3 text-center">
<a class="btn btn-link" href="{{ url_for('auth.password_reset_request') }}">Forgot password?</a>
@ -46,5 +45,6 @@
Sign in with Microsoft
</a>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -161,17 +161,6 @@
</div>
</div>
<div class="card mb-3">
<div class="card-header">Security</div>
<div class="card-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="login_captcha_enabled" name="login_captcha_enabled" {% if settings.login_captcha_enabled %}checked{% endif %} />
<label class="form-check-label" for="login_captcha_enabled">Enable login captcha</label>
</div>
<div class="form-text">When enabled, users must solve a simple math question before logging in. Enabled by default.</div>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary">Save settings</button>
</div>

View File

@ -2,23 +2,6 @@
This file documents all changes made to this project via Claude Code.
## [2026-03-20] (4)
### Added
- Settings → General: "Security" card with login captcha toggle (`login_captcha_enabled`):
- When disabled, the math captcha is hidden on the login page and not validated
- Default `TRUE` for new installs and existing installs after migration
- Migration `migrate_system_settings_login_captcha()` adds the column with `DEFAULT TRUE`
- Audit-logged when changed (same pattern as other General settings)
### Fixed
- Login page layout broken when flash messages were present (e.g. "You have been logged out"):
- `bc-main-auth .bc-content` now uses `max-width: 480px; margin: 0 auto` as a centered column instead of `align-items: center` on a flex container (which caused Bootstrap `col-md-4` percentage widths to collapse)
- `login.html`: replaced `row justify-content-center / col-md-4` with a plain `<div>` — the parent CSS column handles centering
### Changed
- Sandbox banner: semi-transparent (`rgba(220,53,69,0.45)`) instead of solid red
## [2026-03-20] (3)
### Added