Files
gh-christianlouis-docuelevate/app/views/wizard.py
T
copilot-swe-agent[bot] ecc8100e24 feat(settings): persist storage provider settings to DB, add export endpoint, enrich setup wizard
- dropbox/google_drive/onedrive save-settings: switch to DB as primary,
  .env write as best-effort (no longer fails when .env is absent)
- onedrive/google_drive update-settings: persist changes to DB alongside
  in-memory update; call notify_settings_updated()
- onedrive test_onedrive_token: persist rotated refresh token to DB
- settings_service: add get_settings_for_export() (db / effective modes)
- settings API: add GET /api/settings/export-env (admin-only, downloads .env)
- wizard: enrich settings with current values (DB > ENV > default) and
  value_source badges; pass setup_skipped to template; call
  notify_settings_updated() on save; add /setup/undo-skip route
- setup_wizard.html: pre-populate inputs with current_value; show
  DB/ENV/DEFAULT source badges; skip/undo-skip messaging
- settings.html: replace single Audit Log button with Setup Wizard link,
  Export .env dropdown, and Audit Log button group

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-23 02:41:04 +00:00

187 lines
6.0 KiB
Python

"""
Setup wizard views for initial system configuration.
"""
import logging
import secrets
from fastapi import Depends, Form, Request
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
from app.utils.settings_service import save_setting_to_db
from app.utils.settings_sync import notify_settings_updated
from app.utils.setup_wizard import get_wizard_steps
from app.views.base import APIRouter, get_db, templates
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/setup")
async def setup_wizard(request: Request, step: int = 1, db: Session = Depends(get_db)):
"""
Setup wizard for first-time configuration.
This wizard guides users through configuring essential settings
needed for the system to operate properly.
"""
# Get wizard steps
wizard_steps = get_wizard_steps()
max_step = max(wizard_steps.keys())
# Validate step number
if step < 1:
step = 1
elif step > max_step:
step = max_step
# Get settings for current step
current_settings = wizard_steps.get(step, [])
# Get step category (all settings in a step should have same category)
step_category = (
current_settings[0].get("wizard_category", "Configuration")
if current_settings
else "Configuration"
)
# Enrich settings with current live values
from app.config import settings as app_settings
from app.utils.settings_service import get_setting_from_db
enriched_settings = []
for s in current_settings:
key = s["key"]
db_val = get_setting_from_db(db, key)
env_val = getattr(app_settings, key, None)
# Determine current_value and source
if db_val is not None:
current_value = db_val
value_source = "db"
elif env_val is not None and str(env_val).strip():
current_value = str(env_val)
value_source = "env"
elif s.get("default") is not None:
current_value = s["default"]
value_source = "default"
else:
current_value = ""
value_source = "none"
enriched_settings.append(
{**s, "current_value": current_value, "value_source": value_source}
)
current_settings = enriched_settings
return templates.TemplateResponse(
"setup_wizard.html",
{
"request": request,
"current_step": step,
"max_step": max_step,
"settings": current_settings,
"step_category": step_category,
"progress_percent": int((step / max_step) * 100),
"setup_skipped": bool(get_setting_from_db(db, "_setup_wizard_skipped")),
},
)
@router.post("/setup")
async def setup_wizard_save(
request: Request, step: int = Form(...), db: Session = Depends(get_db)
):
"""
Save settings from the current wizard step.
"""
try:
# Get form data
form_data = await request.form()
# Get settings for current step
wizard_steps = get_wizard_steps()
current_settings = wizard_steps.get(step, [])
# Save each setting from the form
saved_count = 0
for setting in current_settings:
key = setting["key"]
value = form_data.get(key)
# Skip empty values unless it's explicitly allowed
if value and value.strip():
# Auto-generate session_secret if needed
if key == "session_secret" and value == "auto-generate":
value = secrets.token_hex(32)
logger.info("Auto-generated session secret")
# Save to database
if save_setting_to_db(db, key, value):
saved_count += 1
logger.info(f"Setup wizard: Saved {key}")
logger.info(f"Setup wizard step {step}: Saved {saved_count} settings")
if saved_count > 0:
notify_settings_updated()
# Determine next step
max_step = max(wizard_steps.keys())
next_step = step + 1
if next_step > max_step:
# Setup complete, redirect to home
return RedirectResponse(url="/?setup=complete", status_code=303)
else:
# Go to next step
return RedirectResponse(url=f"/setup?step={next_step}", status_code=303)
except Exception as e:
logger.error(f"Error saving wizard settings: {e}")
return RedirectResponse(
url=f"/setup?step={step}&error=save_failed", status_code=303
)
@router.get("/setup/skip")
async def setup_wizard_skip(request: Request):
"""
Skip the setup wizard (for advanced users).
Creates a marker to indicate setup was skipped.
"""
try:
db = next(get_db())
try:
# Save a marker to indicate setup was skipped
save_setting_to_db(db, "_setup_wizard_skipped", "true")
logger.info("Setup wizard skipped by user")
return RedirectResponse(url="/", status_code=303)
finally:
db.close()
except Exception as e:
logger.error(f"Error skipping setup wizard: {e}")
return RedirectResponse(url="/", status_code=303)
@router.get("/setup/undo-skip")
async def setup_wizard_undo_skip(request: Request, db: Session = Depends(get_db)):
"""
Undo a previously skipped setup wizard.
Removes the skip marker from the database so the wizard will be
presented again on next visit to the home page. Redirects to
step 1 of the wizard immediately.
"""
try:
from app.utils.settings_service import delete_setting_from_db
delete_setting_from_db(
db, "_setup_wizard_skipped", changed_by="wizard_undo_skip"
)
logger.info("Setup wizard skip marker removed; redirecting to wizard")
return RedirectResponse(url="/setup?step=1", status_code=303)
except Exception as e:
logger.error(f"Error undoing setup wizard skip: {e}")
return RedirectResponse(url="/settings", status_code=303)