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>
This commit is contained in:
+66
-4
@@ -10,6 +10,7 @@ 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
|
||||
|
||||
@@ -18,7 +19,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/setup")
|
||||
async def setup_wizard(request: Request, step: int = 1):
|
||||
async def setup_wizard(request: Request, step: int = 1, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Setup wizard for first-time configuration.
|
||||
|
||||
@@ -39,7 +40,38 @@ async def setup_wizard(request: Request, step: int = 1):
|
||||
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"
|
||||
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",
|
||||
@@ -50,12 +82,15 @@ async def setup_wizard(request: Request, step: int = 1):
|
||||
"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)):
|
||||
async def setup_wizard_save(
|
||||
request: Request, step: int = Form(...), db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Save settings from the current wizard step.
|
||||
"""
|
||||
@@ -87,6 +122,9 @@ async def setup_wizard_save(request: Request, step: int = Form(...), db: Session
|
||||
|
||||
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
|
||||
@@ -100,7 +138,9 @@ async def setup_wizard_save(request: Request, step: int = Form(...), db: Session
|
||||
|
||||
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)
|
||||
return RedirectResponse(
|
||||
url=f"/setup?step={step}&error=save_failed", status_code=303
|
||||
)
|
||||
|
||||
|
||||
@router.get("/setup/skip")
|
||||
@@ -122,3 +162,25 @@ async def setup_wizard_skip(request: Request):
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user