Add setting to enable/disable daily dashboard redirect

Added a new setting in Settings > General > Navigation that allows
administrators to control whether users are redirected to the dashboard
on their first page view each day.

Changes:
- Added require_daily_dashboard_visit column to SystemSettings model
- Added migration for the new column (default: FALSE)
- Modified before_request hook to check the setting before redirecting
- Added Navigation card with toggle in Settings General page
- Restored changelog-claude.md with performance and feature updates

Default is OFF - users can navigate directly to any page without
being forced to visit the dashboard first.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ivo Oskamp 2026-02-04 22:03:11 +01:00
parent ae66457415
commit abf8b89d7c
6 changed files with 141 additions and 0 deletions

View File

@ -69,6 +69,9 @@ def create_app():
This ensures that when a user opens the site for the first time each day, This ensures that when a user opens the site for the first time each day,
they land on the dashboard regardless of the bookmarked/deeplinked URL. they land on the dashboard regardless of the bookmarked/deeplinked URL.
This behavior is controlled by the system setting `require_daily_dashboard_visit`.
When disabled (the default), users can navigate directly to any page.
""" """
# Only for normal page loads. # Only for normal page loads.
@ -99,6 +102,18 @@ def create_app():
session["daily_dashboard_seen"] = _get_today_ui_date() session["daily_dashboard_seen"] = _get_today_ui_date()
return None return None
# Check if the feature is enabled in system settings.
try:
from .models import SystemSettings
settings = SystemSettings.query.first()
if not settings or not getattr(settings, "require_daily_dashboard_visit", False):
# Feature is disabled; skip redirect.
return None
except Exception:
# On any error (e.g. column doesn't exist yet), skip redirect.
return None
today = _get_today_ui_date() today = _get_today_ui_date()
seen = (session.get("daily_dashboard_seen") or "").strip() seen = (session.get("daily_dashboard_seen") or "").strip()
if seen != today: if seen != today:

View File

@ -430,6 +430,10 @@ def settings():
if "ui_timezone" in request.form: if "ui_timezone" in request.form:
settings.ui_timezone = (request.form.get("ui_timezone") or "").strip() or "Europe/Amsterdam" settings.ui_timezone = (request.form.get("ui_timezone") or "").strip() or "Europe/Amsterdam"
# Navigation setting is in the same form (General tab), so process it here.
# Checkbox: present in form = checked, absent = unchecked.
settings.require_daily_dashboard_visit = bool(request.form.get("require_daily_dashboard_visit"))
# Daily Jobs # Daily Jobs
if "daily_jobs_start_date" in request.form: if "daily_jobs_start_date" in request.form:
daily_jobs_start_date_str = (request.form.get("daily_jobs_start_date") or "").strip() daily_jobs_start_date_str = (request.form.get("daily_jobs_start_date") or "").strip()

View File

@ -801,6 +801,8 @@ def run_migrations() -> None:
migrate_news_tables() migrate_news_tables()
migrate_reporting_tables() migrate_reporting_tables()
migrate_reporting_report_config() migrate_reporting_report_config()
migrate_performance_indexes()
migrate_system_settings_require_daily_dashboard_visit()
print("[migrations] All migrations completed.") print("[migrations] All migrations completed.")
@ -844,6 +846,78 @@ def migrate_jobs_archiving() -> None:
print("[migrations] migrate_jobs_archiving completed.") print("[migrations] migrate_jobs_archiving completed.")
def migrate_system_settings_require_daily_dashboard_visit() -> None:
"""Add require_daily_dashboard_visit column to system_settings if missing.
When enabled, authenticated users are redirected to the dashboard on
their first page view each day.
"""
table = "system_settings"
column = "require_daily_dashboard_visit"
try:
engine = db.get_engine()
except Exception as exc:
print(f"[migrations] Could not get engine for system_settings require_daily_dashboard_visit migration: {exc}")
return
try:
if _column_exists(table, column):
print("[migrations] system_settings.require_daily_dashboard_visit already exists.")
return
with engine.begin() as conn:
conn.execute(
text(
f'ALTER TABLE "{table}" ADD COLUMN {column} BOOLEAN NOT NULL DEFAULT FALSE'
)
)
print("[migrations] migrate_system_settings_require_daily_dashboard_visit completed.")
except Exception as exc:
print(f"[migrations] Failed to migrate system_settings.require_daily_dashboard_visit: {exc}")
def migrate_performance_indexes() -> None:
"""Add performance indexes for frequently queried foreign key columns.
These indexes significantly improve query performance on slow storage,
especially for Daily Jobs and Run Checks pages.
This migration is idempotent.
"""
try:
engine = db.get_engine()
except Exception as exc:
print(f"[migrations] Could not get engine for performance indexes migration: {exc}")
return
with engine.begin() as conn:
# JobRun indexes
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_run_job_id ON job_runs (job_id)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_run_job_id_run_at ON job_runs (job_id, run_at)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_run_job_id_reviewed_at ON job_runs (job_id, reviewed_at)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_run_mail_message_id ON job_runs (mail_message_id)'))
# MailMessage indexes
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_mail_message_job_id ON mail_messages (job_id)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_mail_message_location ON mail_messages (location)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_mail_message_job_id_location ON mail_messages (job_id, location)'))
# MailObject indexes
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_mail_object_mail_message_id ON mail_objects (mail_message_id)'))
# TicketScope indexes
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_ticket_scope_ticket_id ON ticket_scopes (ticket_id)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_ticket_scope_job_id ON ticket_scopes (job_id)'))
# RemarkScope indexes
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_remark_scope_remark_id ON remark_scopes (remark_id)'))
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_remark_scope_job_id ON remark_scopes (job_id)'))
print("[migrations] migrate_performance_indexes completed.")
def migrate_reporting_report_config() -> None: def migrate_reporting_report_config() -> None:
"""Add report_definitions.report_config column if missing. """Add report_definitions.report_config column if missing.

View File

@ -107,6 +107,11 @@ class SystemSettings(db.Model):
# UI display timezone (IANA name). Used for rendering times in the web interface. # UI display timezone (IANA name). Used for rendering times in the web interface.
ui_timezone = db.Column(db.String(64), nullable=False, default="Europe/Amsterdam") ui_timezone = db.Column(db.String(64), nullable=False, default="Europe/Amsterdam")
# Navigation behavior: require visiting dashboard first each day.
# When enabled, authenticated users are redirected to the dashboard on
# their first page view each day before they can navigate elsewhere.
require_daily_dashboard_visit = db.Column(db.Boolean, nullable=False, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column( updated_at = db.Column(
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False

View File

@ -136,6 +136,17 @@
</div> </div>
</div> </div>
<div class="card mb-3">
<div class="card-header">Navigation</div>
<div class="card-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="require_daily_dashboard_visit" name="require_daily_dashboard_visit" {% if settings.require_daily_dashboard_visit %}checked{% endif %} />
<label class="form-check-label" for="require_daily_dashboard_visit">Require dashboard visit on first page view each day</label>
</div>
<div class="form-text">When enabled, users are redirected to the dashboard on their first page view each day, regardless of which page they try to access.</div>
</div>
</div>
<div class="d-flex justify-content-end mt-3"> <div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary">Save settings</button> <button type="submit" class="btn btn-primary">Save settings</button>
</div> </div>

32
docs/changelog-claude.md Normal file
View File

@ -0,0 +1,32 @@
# Changelog - Claude Code
This file documents all changes made to this project via Claude Code.
## [2026-02-04]
### Added
- `docs/changelog-claude.md` - Changelog file for tracking changes made via Claude Code
- Setting to enable/disable daily dashboard redirect requirement (Settings > General > Navigation)
- New `require_daily_dashboard_visit` column in `SystemSettings` model
- Migration in `migrations.py` to add the column
- Toggle in Settings General page under new "Navigation" card
- Default value is OFF (disabled) - users can navigate directly to any page
### Changed
- Converted changelog to English (all project documentation must be in English)
- Documented branch naming convention and build workflow in Claude memory
- Filled README.md with comprehensive project documentation based on source code analysis
### Performance
- Added database indexes migration (`migrations.py`) for frequently queried foreign key columns:
- `JobRun`: indexes on `job_id`, `job_id+run_at`, `job_id+reviewed_at`, `mail_message_id`
- `MailMessage`: indexes on `job_id`, `location`, `job_id+location`
- `MailObject`: index on `mail_message_id`
- `TicketScope`: indexes on `ticket_id`, `job_id`
- `RemarkScope`: indexes on `remark_id`, `job_id`
- Fixed N+1 query in `_recompute_override_flags_for_runs()` - batch loads all jobs instead of per-run queries
- Optimized Daily Jobs page with batch queries:
- Batch load all today's runs for all jobs in single query
- Batch infer weekly schedules for all jobs (was per-job query)
- Batch infer monthly schedules for jobs without weekly schedule
- Batch load ticket/remark indicators for all jobs