From 6b4b3924a18b03d8e29aab76b9ffbf96d5194735 Mon Sep 17 00:00:00 2001 From: Shroominic Date: Tue, 2 Dec 2025 15:23:00 +0800 Subject: [PATCH] feat(ui): make admin settings dynamic based on backend response This update changes the admin settings page to dynamically render settings fields based on the JSON response from the backend, rather than hardcoding them. This ensures that new settings added to the backend are automatically available in the UI without code changes. - Specific handling for known fields like name, description, urls, keys, mints, and relays remains to provide a polished UX. - All other fields are rendered dynamically based on their type (boolean, number, string, array). - Sensitive fields (keys, passwords) are automatically masked. - Specific internal/unused fields are ignored. --- ui/components/settings/admin-settings.tsx | 196 +++++++++++++++++++++- 1 file changed, 194 insertions(+), 2 deletions(-) diff --git a/ui/components/settings/admin-settings.tsx b/ui/components/settings/admin-settings.tsx index c81d0b75..dd8468db 100644 --- a/ui/components/settings/admin-settings.tsx +++ b/ui/components/settings/admin-settings.tsx @@ -18,6 +18,7 @@ import { Textarea } from '@/components/ui/textarea'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { AlertCircle, Save, RefreshCw, Eye, EyeOff } from 'lucide-react'; import { toast } from 'sonner'; +import { Switch } from '@/components/ui/switch'; interface SettingsData { name?: string; @@ -28,9 +29,32 @@ interface SettingsData { http_url?: string; onion_url?: string; cashu_mints?: string[]; + relays?: string[]; [key: string]: unknown; } +const HANDLED_KEYS = [ + 'name', + 'description', + 'http_url', + 'onion_url', + 'npub', + 'nsec', + 'cashu_mints', + 'relays', + 'admin_password', + 'id', + 'updated_at', +]; + +const IGNORED_KEYS = [ + 'upstream_base_url', + 'upstream_api_key', + 'upstream_provider_fee', + 'exchange_fee', + 'models_path', +]; + interface PasswordData { current_password: string; new_password: string; @@ -44,6 +68,7 @@ export function AdminSettings() { const [error, setError] = useState(''); const [showSecrets, setShowSecrets] = useState(false); const [newMint, setNewMint] = useState(''); + const [newRelay, setNewRelay] = useState(''); const [passwordData, setPasswordData] = useState({ current_password: '', new_password: '', @@ -129,7 +154,7 @@ export function AdminSettings() { } }; - const handleInputChange = (field: string, value: string | boolean) => { + const handleInputChange = (field: string, value: unknown) => { setSettings((prev) => ({ ...prev, [field]: value, @@ -153,6 +178,23 @@ export function AdminSettings() { })); }; + const addRelay = () => { + if (newRelay.trim()) { + setSettings((prev) => ({ + ...prev, + relays: [...(prev.relays || []), newRelay.trim()], + })); + setNewRelay(''); + } + }; + + const removeRelay = (index: number) => { + setSettings((prev) => ({ + ...prev, + relays: prev.relays?.filter((_, i) => i !== index) || [], + })); + }; + const renderSecretField = ( field: string, label: string, @@ -162,7 +204,7 @@ export function AdminSettings() { const displayValue = showSecrets ? value : value ? '••••••••' : ''; return ( -
+
{ + const label = key + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + + if (typeof value === 'boolean') { + return ( +
+ + handleInputChange(key, checked)} + /> +
+ ); + } + + if (typeof value === 'number') { + return ( +
+ + { + const val = e.target.value === '' ? 0 : Number(e.target.value); + handleInputChange(key, val); + }} + /> +
+ ); + } + + if (Array.isArray(value)) { + const strValue = value.join(', '); + return ( +
+ +