Implemented a comprehensive settings management system that allows admin users to configure all environment variables and application settings through the UI, with encryption for secrets, complete audit logging, and validation.
- application_setting.py - ApplicationSetting model with fields for key, value, type, category, encryption flags, validation schema
- setting_audit_log.py - SettingAuditLog model for tracking all setting changes with before/after values
- init.py - Updated to export new models
- application_setting.py - Pydantic schemas for CRUD operations, responses (with secret masking), bulk updates, export/import
- init.py - Updated to export new schemas
- settings_service.py - SettingsService with encryption, validation, audit logging, bulk operations, export/import
- runtime_settings.py - RuntimeSettings singleton for in-memory caching and type-safe access to settings
- settings.py - RESTful API endpoints for all settings operations (admin-only access)
- router.py - Updated to include settings routes
- 20260101_000200_add_settings.py - Alembic migration creating tables and seeding default settings
- SETTINGS_MANAGEMENT.md - Comprehensive usage guide, API reference, security considerations
- settings.ts - TypeScript interfaces for ApplicationSetting, SettingCategory, SettingAuditLog
- index.ts - Updated to export settings types
- settings-api.ts - API client functions for all settings operations
- settings-store.ts - Zustand store for settings state management
- SettingCard.tsx - Individual setting card with edit/reset functionality
- CategorySelector.tsx - Category navigation tabs
- SettingSearch.tsx - Search component for filtering settings
- ExportImportButtons.tsx - Export/Import action buttons
- BulkEditor.tsx - Bulk edit dialog for multiple settings
- AuditLogTimeline.tsx - Timeline visualization of setting changes
- textarea.tsx - Textarea UI component (required for BulkEditor)
- management/page.tsx - Main settings management dashboard
- category/[category]/page.tsx - Category detail page for editing settings
- audit/page.tsx - Audit log page with filtering and export
- page.tsx - Updated main settings page with admin link to management
# List all settings
curl http://localhost:8000/api/v1/settings \
-H "Authorization: Bearer <admin_token>"
# Get settings by category
curl http://localhost:8000/api/v1/settings/category/llm \
-H "Authorization: Bearer <admin_token>"
# Update a setting
curl -X PUT http://localhost:8000/api/v1/settings/llm.temperature \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{"value": "0.8", "change_reason": "More creative responses"}'
# Bulk update
curl -X PUT http://localhost:8000/api/v1/settings/bulk \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{
"settings": {"app.debug": "false", "app.log_level": "WARNING"},
"change_reason": "Production deployment"
}'
# Export settings
curl -X POST http://localhost:8000/api/v1/settings/export \
-H "Authorization: Bearer <admin_token>" \
-o settings-backup.json
# Import settings
curl -X POST "http://localhost:8000/api/v1/settings/import?overwrite=false" \
-H "Authorization: Bearer <admin_token>" \
-F "file=@settings-backup.json"
# Get audit log
curl http://localhost:8000/api/v1/settings/llm.temperature/audit?limit=50 \
-H "Authorization: Bearer <admin_token>"// Import settings API
import {
getSettings,
updateSetting,
bulkUpdateSettings,
exportSettings,
importSettings,
getAuditLog,
} from '@/lib/settings-api'
// Or use the store
import { useSettingsStore } from '@/stores/settings-store'
const {
settings,
categories,
fetchSettings,
updateSetting,
bulkUpdateSettings,
exportSettings,
importSettings,
fetchAuditLog,
} = useSettingsStore()
// Load settings
await fetchSettings('llm')
// Update setting
await updateSetting('llm.temperature', '0.8', 'More creative responses')
// Bulk update
await bulkUpdateSettings({
'app.debug': 'false',
'app.log_level': 'WARNING',
}, 'Production deployment')
// Export
const blob = await exportSettings()
// Import
await importSettings(file, false)
// Get audit log
const logs = await fetchAuditLog('llm.temperature', 50)from app.core.runtime_settings import get_runtime_settings
settings = get_runtime_settings()
# Get typed values
temperature = settings.get_float("llm.temperature", 0.7)
debug = settings.get_bool("app.debug", False)
max_tokens = settings.get_int("llm.max_tokens", 4096)
cors_origins = settings.get_json("security.cors_origins", [])
# Refresh cache
await settings.refresh()- All secret values encrypted using Fernet encryption
- Encryption key required via
ENCRYPTION_KEYenvironment variable - Generate key:
cryptography.fernet.Fernet.generate_key() - Secrets masked in API responses:
sk_••••••••••••••••••••• - Secrets only decrypted when explicitly requested
- Admin-only access: Only
adminandsuperadminroles can access settings - Role verification: Every endpoint checks user role
- Audit trail: All changes logged with user ID and timestamp
- Server-side validation for all inputs
- Type validation (integer, float, boolean, json)
- Schema validation using JSON Schema
- Range validation (min/max)
- Enum validation for predefined values
- Immutable audit log in
setting_audit_logstable - Records: old value, new value, user, timestamp, reason
- Export capability for compliance
- No delete/update operations on audit logs
- Change reasons recommended for audit trail
- Test in development before production
- Export before bulk changes (backup)
- Review audit logs regularly
- Rotate
ENCRYPTION_KEYperiodically
Update services to use runtime settings:
# Before
from app.core.settings import get_settings
settings = get_settings()
temp = settings.llm_temperature
# After
from app.core.runtime_settings import get_runtime_settings
runtime = get_runtime_settings()
temp = runtime.get_float("llm.temperature", 0.7)# Use settings from DB instead of env vars
pool_size = runtime_settings.get_int("database.pool_size", 10)
pool_timeout = runtime_settings.get_int("database.pool_timeout", 30)
echo_sql = runtime_settings.get_bool("database.echo_sql", False)# Use dynamic settings
temperature = runtime_settings.get_float("llm.temperature", 0.7)
max_tokens = runtime_settings.get_int("llm.max_tokens", 4096)
api_key = runtime_settings.get_string("llm.openai_api_key")# Use dynamic security settings
jwt_expiry = runtime_settings.get_int("security.jwt_expiry_minutes", 30)
max_attempts = runtime_settings.get_int("security.max_login_attempts", 5)
cors_origins = runtime_settings.get_json("security.cors_origins", [])# Emit events on settings change
await websocket_manager.broadcast({
"type": "settings_updated",
"setting_key": key,
"updated_by": user_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
})Run migration to create tables and seed defaults:
cd backend
alembic upgrade headThis creates:
application_settingstable with indexessetting_audit_logstable with foreign keys- Seeds 23 default settings across 6 categories
- llm.default_provider, llm.openai_api_key, llm.openai_base_url
- llm.anthropic_api_key, llm.groq_api_key
- llm.temperature, llm.max_tokens
- database.pool_size, database.pool_timeout, database.echo_sql
- redis.db, redis.ttl
- security.jwt_expiry_minutes, security.max_login_attempts
- security.session_timeout_minutes, security.cors_origins
- app.debug, app.log_level, app.workers, app.max_upload_size_mb
- ui.theme, ui.page_size, ui.enable_websocket
-
ENCRYPTION_KEY Required: Must set before using secret settings
# Generate and set python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" export ENCRYPTION_KEY="<generated_key>"
-
Admin Access Only: Regular users will get 403 Forbidden
-
Restart May Be Required: Some settings (workers, database pool) may require application restart
-
Validation Errors: Check validation schema if updates fail
-
Audit Log Growth: Consider periodic export/archival for compliance
- PostgreSQL database is configured and accessible
- Redis is available for caching (optional)
- User authentication and RBAC system is in place
- Admin/superadmin roles exist in the system
- Alembic migrations are configured and working
- Frontend has Next.js with App Router configured
- Zustand is used for state management
- shadcn/ui components are available
- backend/app/models/init.py - Added exports for new models
- backend/app/schemas/init.py - Added exports for new schemas
- backend/app/services/init.py - Added exports for settings service
- backend/app/api/v1/router.py - Added settings routes
- frontend/src/types/index.ts - Added settings type exports
- frontend/src/app/dashboard/settings/page.tsx - Added admin link to management page
- Run migration:
alembic upgrade head - Generate encryption key and set
ENCRYPTION_KEYenvironment variable - Restart backend to load runtime settings
- Test settings UI at
/dashboard/settings/management - Update existing services to use runtime settings
- Add WebSocket events for real-time updates
- Consider adding approval workflow for critical settings