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:
parent
ae66457415
commit
abf8b89d7c
@ -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:
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
32
docs/changelog-claude.md
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user