fix(settings): fix route ordering and settings page display of DB values
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+139
-139
@@ -103,6 +103,145 @@ async def get_settings(request: Request, db: DbSession, admin: AdminUser):
|
||||
)
|
||||
|
||||
|
||||
@router.get("/credentials")
|
||||
async def list_credentials(request: Request, db: DbSession, admin: AdminUser):
|
||||
"""
|
||||
List all sensitive credential settings with their configured/unconfigured status.
|
||||
|
||||
Returns a credential audit report indicating which credentials are set and whether
|
||||
each value originates from the database or an environment variable.
|
||||
This endpoint is intended to support credential rotation workflows.
|
||||
Admin only.
|
||||
"""
|
||||
try:
|
||||
db_settings = get_all_settings_from_db(db)
|
||||
credentials = []
|
||||
|
||||
for key, meta in SETTING_METADATA.items():
|
||||
if not meta.get("sensitive", False):
|
||||
continue
|
||||
|
||||
env_value = getattr(settings, key, None)
|
||||
in_db = key in db_settings and db_settings[key]
|
||||
|
||||
if in_db:
|
||||
source = "db"
|
||||
configured = True
|
||||
elif env_value:
|
||||
source = "env"
|
||||
configured = True
|
||||
else:
|
||||
source = None
|
||||
configured = False
|
||||
|
||||
credentials.append(
|
||||
{
|
||||
"key": key,
|
||||
"category": meta.get("category", "Other"),
|
||||
"description": meta.get("description", ""),
|
||||
"configured": configured,
|
||||
"source": source,
|
||||
"restart_required": meta.get("restart_required", False),
|
||||
}
|
||||
)
|
||||
|
||||
configured_count = sum(1 for c in credentials if c["configured"])
|
||||
return {
|
||||
"credentials": credentials,
|
||||
"total": len(credentials),
|
||||
"configured_count": configured_count,
|
||||
"unconfigured_count": len(credentials) - configured_count,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving credential list: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve credentials",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/audit-log")
|
||||
async def list_audit_log(
|
||||
request: Request,
|
||||
db: DbSession,
|
||||
admin: AdminUser,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
):
|
||||
"""
|
||||
Retrieve the settings audit log (most recent first).
|
||||
|
||||
Returns all configuration changes recorded in the audit log.
|
||||
Sensitive values are masked in the response.
|
||||
Admin only.
|
||||
"""
|
||||
try:
|
||||
entries = get_audit_log(db, limit=limit, offset=offset)
|
||||
return {"entries": entries, "limit": limit, "offset": offset}
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving audit log: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve audit log",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/export-env")
|
||||
async def export_env_settings(
|
||||
request: Request,
|
||||
db: DbSession,
|
||||
admin: AdminUser,
|
||||
source: str = "db",
|
||||
):
|
||||
"""
|
||||
Export current settings as a ``.env`` file.
|
||||
|
||||
Query params:
|
||||
- ``source=db`` (default) – only settings explicitly saved to the database.
|
||||
- ``source=effective`` – full runtime configuration (DB > ENV > defaults) for
|
||||
every key defined in SETTING_METADATA.
|
||||
|
||||
Returns a downloadable plain-text file suitable for bootstrapping another
|
||||
installation. All values — including sensitive ones — are included; only
|
||||
admins can access this endpoint.
|
||||
"""
|
||||
from fastapi.responses import Response as FastAPIResponse
|
||||
|
||||
from app.utils.settings_service import get_settings_for_export
|
||||
|
||||
if source not in ("db", "effective"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="source must be 'db' or 'effective'",
|
||||
)
|
||||
|
||||
try:
|
||||
export_data = get_settings_for_export(db, source=source)
|
||||
lines = [
|
||||
"# DocuElevate configuration export",
|
||||
f"# Source: {source}",
|
||||
"# Generated by DocuElevate Settings Export",
|
||||
"# WARNING: This file contains sensitive values. Handle with care.",
|
||||
"",
|
||||
]
|
||||
for env_key, value in export_data.items():
|
||||
lines.append(f"{env_key}={value}")
|
||||
lines.append("") # trailing newline
|
||||
content = "\n".join(lines)
|
||||
|
||||
return FastAPIResponse(
|
||||
content=content,
|
||||
media_type="text/plain",
|
||||
headers={"Content-Disposition": f'attachment; filename="docuelevate-{source}.env"'},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error exporting settings: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to export settings",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{key}", response_model=SettingResponse)
|
||||
async def get_setting(key: str, request: Request, db: DbSession, admin: AdminUser):
|
||||
"""
|
||||
@@ -220,63 +359,6 @@ async def delete_setting(key: str, request: Request, db: DbSession, admin: Admin
|
||||
)
|
||||
|
||||
|
||||
@router.get("/credentials")
|
||||
async def list_credentials(request: Request, db: DbSession, admin: AdminUser):
|
||||
"""
|
||||
List all sensitive credential settings with their configured/unconfigured status.
|
||||
|
||||
Returns a credential audit report indicating which credentials are set and whether
|
||||
each value originates from the database or an environment variable.
|
||||
This endpoint is intended to support credential rotation workflows.
|
||||
Admin only.
|
||||
"""
|
||||
try:
|
||||
db_settings = get_all_settings_from_db(db)
|
||||
credentials = []
|
||||
|
||||
for key, meta in SETTING_METADATA.items():
|
||||
if not meta.get("sensitive", False):
|
||||
continue
|
||||
|
||||
env_value = getattr(settings, key, None)
|
||||
in_db = key in db_settings and db_settings[key]
|
||||
|
||||
if in_db:
|
||||
source = "db"
|
||||
configured = True
|
||||
elif env_value:
|
||||
source = "env"
|
||||
configured = True
|
||||
else:
|
||||
source = None
|
||||
configured = False
|
||||
|
||||
credentials.append(
|
||||
{
|
||||
"key": key,
|
||||
"category": meta.get("category", "Other"),
|
||||
"description": meta.get("description", ""),
|
||||
"configured": configured,
|
||||
"source": source,
|
||||
"restart_required": meta.get("restart_required", False),
|
||||
}
|
||||
)
|
||||
|
||||
configured_count = sum(1 for c in credentials if c["configured"])
|
||||
return {
|
||||
"credentials": credentials,
|
||||
"total": len(credentials),
|
||||
"configured_count": configured_count,
|
||||
"unconfigured_count": len(credentials) - configured_count,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving credential list: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve credentials",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/bulk-update")
|
||||
async def bulk_update_settings(updates: list[SettingUpdate], request: Request, db: DbSession, admin: AdminUser):
|
||||
"""
|
||||
@@ -323,32 +405,6 @@ async def bulk_update_settings(updates: list[SettingUpdate], request: Request, d
|
||||
}
|
||||
|
||||
|
||||
@router.get("/audit-log")
|
||||
async def list_audit_log(
|
||||
request: Request,
|
||||
db: DbSession,
|
||||
admin: AdminUser,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
):
|
||||
"""
|
||||
Retrieve the settings audit log (most recent first).
|
||||
|
||||
Returns all configuration changes recorded in the audit log.
|
||||
Sensitive values are masked in the response.
|
||||
Admin only.
|
||||
"""
|
||||
try:
|
||||
entries = get_audit_log(db, limit=limit, offset=offset)
|
||||
return {"entries": entries, "limit": limit, "offset": offset}
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving audit log: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve audit log",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{key}/history")
|
||||
async def get_key_history(key: str, request: Request, db: DbSession, admin: AdminUser):
|
||||
"""
|
||||
@@ -416,59 +472,3 @@ async def rollback_setting_to_history(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to roll back setting: {key}",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/export-env")
|
||||
async def export_env_settings(
|
||||
request: Request,
|
||||
db: DbSession,
|
||||
admin: AdminUser,
|
||||
source: str = "db",
|
||||
):
|
||||
"""
|
||||
Export current settings as a ``.env`` file.
|
||||
|
||||
Query params:
|
||||
- ``source=db`` (default) – only settings explicitly saved to the database.
|
||||
- ``source=effective`` – full runtime configuration (DB > ENV > defaults) for
|
||||
every key defined in SETTING_METADATA.
|
||||
|
||||
Returns a downloadable plain-text file suitable for bootstrapping another
|
||||
installation. All values — including sensitive ones — are included; only
|
||||
admins can access this endpoint.
|
||||
"""
|
||||
from fastapi.responses import Response as FastAPIResponse
|
||||
|
||||
from app.utils.settings_service import get_settings_for_export
|
||||
|
||||
if source not in ("db", "effective"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="source must be 'db' or 'effective'",
|
||||
)
|
||||
|
||||
try:
|
||||
export_data = get_settings_for_export(db, source=source)
|
||||
lines = [
|
||||
"# DocuElevate configuration export",
|
||||
f"# Source: {source}",
|
||||
"# Generated by DocuElevate Settings Export",
|
||||
"# WARNING: This file contains sensitive values. Handle with care.",
|
||||
"",
|
||||
]
|
||||
for env_key, value in export_data.items():
|
||||
lines.append(f"{env_key}={value}")
|
||||
lines.append("") # trailing newline
|
||||
content = "\n".join(lines)
|
||||
|
||||
return FastAPIResponse(
|
||||
content=content,
|
||||
media_type="text/plain",
|
||||
headers={"Content-Disposition": f'attachment; filename="docuelevate-{source}.env"'},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error exporting settings: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to export settings",
|
||||
)
|
||||
|
||||
@@ -72,25 +72,25 @@ async def settings_page(request: Request, db: Session = Depends(get_db)):
|
||||
for category, keys in categories.items():
|
||||
settings_data[category] = []
|
||||
for key in keys:
|
||||
# Get current value from settings (already has precedence applied)
|
||||
value = getattr(settings, key, None)
|
||||
|
||||
# Determine the source of this setting
|
||||
# Check if it's in the database
|
||||
# Determine the source of this setting and get the effective value
|
||||
# Check if it's in the database (DB takes precedence)
|
||||
if key in db_settings:
|
||||
source = "database"
|
||||
source_label = "DB"
|
||||
source_color = "green"
|
||||
value = db_settings[key]
|
||||
# Check if it's from environment variable
|
||||
elif key.upper() in os.environ or key in os.environ:
|
||||
source = "environment"
|
||||
source_label = "ENV"
|
||||
source_color = "blue"
|
||||
value = getattr(settings, key, None)
|
||||
else:
|
||||
# It's using the default value
|
||||
source = "default"
|
||||
source_label = "DEFAULT"
|
||||
source_color = "gray"
|
||||
value = getattr(settings, key, None)
|
||||
|
||||
# Get metadata
|
||||
metadata = get_setting_metadata(key)
|
||||
|
||||
Reference in New Issue
Block a user