feat(ui): (a) Backends + Routing-Rules + Settings pages + Sidebar
CRUD-Pages analog Domains: * Backends: AntD Table + Modal-Form mit name, scheme, address, port, health_check_path, active. TanStack-Query gegen /api/v1/backends. * RoutingRules: Table mit Domain-Name- und Backend-Label-Resolution, Modal mit Select-Pickern für Domain + Backend, Path-Prefix, Priority, Active. Drei parallele Queries (rules, domains, backends) liefern die Listen. * Settings: read-only Descriptions-Cards mit /system/health und /setup/status. Editable Werte folgen später. Sidebar erweitert um Backends, Routing-Rules, Settings (mit AntD- Icons). i18n de/en für alle drei neuen Seiten. bun run build + npx tsc -b strict (0 errors). Live-Smoke gegen API: SPA-Routes /backends, /routing-rules, /settings antworten 200 mit index.html (NoRoute fallback wirkt). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
management-ui/src/pages/Settings/index.tsx
Normal file
66
management-ui/src/pages/Settings/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Card, Descriptions, Spin, Typography } from 'antd'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import apiClient, { isEnvelope } from '../../api/client'
|
||||
|
||||
interface SetupStatus {
|
||||
completed: boolean
|
||||
admin_email: string
|
||||
fqdn: string
|
||||
}
|
||||
|
||||
interface SystemHealth {
|
||||
status: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: setupStatus, isLoading: loadingSetup } = useQuery({
|
||||
queryKey: ['setup', 'status'],
|
||||
queryFn: async () => {
|
||||
const r = await apiClient.get('/setup/status')
|
||||
if (isEnvelope(r.data)) return r.data.data as SetupStatus
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
const { data: health, isLoading: loadingHealth } = useQuery({
|
||||
queryKey: ['system', 'health'],
|
||||
queryFn: async () => {
|
||||
const r = await apiClient.get('/system/health')
|
||||
if (isEnvelope(r.data)) return r.data.data as SystemHealth
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
if (loadingSetup || loadingHealth) {
|
||||
return <Spin />
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography.Title level={3}>{t('settings.title')}</Typography.Title>
|
||||
<Typography.Paragraph type="secondary">{t('settings.intro')}</Typography.Paragraph>
|
||||
|
||||
<Card title={t('settings.systemInfo')} style={{ marginBottom: 16 }}>
|
||||
<Descriptions column={1}>
|
||||
<Descriptions.Item label={t('settings.version')}>{health?.version ?? '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label={t('settings.status')}>{health?.status ?? '—'}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
<Card title={t('settings.setupInfo')}>
|
||||
<Descriptions column={1}>
|
||||
<Descriptions.Item label={t('settings.adminEmail')}>{setupStatus?.admin_email ?? '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label={t('settings.fqdn')}>{setupStatus?.fqdn ?? '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label={t('settings.setupCompleted')}>
|
||||
{setupStatus?.completed ? t('common.yes') : t('common.no')}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user