= [
+ { title: t('routing.domain'), dataIndex: 'domain_id', key: 'domain', render: (id: number) => domainName(id) },
+ { title: t('routing.pathPrefix'), dataIndex: 'path_prefix', key: 'path' },
+ { title: t('routing.backend'), dataIndex: 'backend_id', key: 'backend', render: (id: number) => backendLabel(id) },
+ { title: t('routing.priority'), dataIndex: 'priority', key: 'priority' },
+ { title: t('routing.active'), dataIndex: 'active', key: 'active', render: (v: boolean) => v ? t('common.yes') : t('common.no') },
+ {
+ title: t('routing.actions'), key: 'actions',
+ render: (_, row) => (
+
+
+ del.mutate(row.id)}
+ >
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
{t('routing.title')}
+
+
+
{ setEditing(null); setCreating(false) }}
+ onOk={() => { void form.submit() }}
+ confirmLoading={create.isPending || update.isPending}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/management-ui/src/pages/Settings/index.tsx b/management-ui/src/pages/Settings/index.tsx
new file mode 100644
index 0000000..180cc0b
--- /dev/null
+++ b/management-ui/src/pages/Settings/index.tsx
@@ -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
+ }
+
+ return (
+
+ {t('settings.title')}
+ {t('settings.intro')}
+
+
+
+ {health?.version ?? '—'}
+ {health?.status ?? '—'}
+
+
+
+
+
+ {setupStatus?.admin_email ?? '—'}
+ {setupStatus?.fqdn ?? '—'}
+
+ {setupStatus?.completed ? t('common.yes') : t('common.no')}
+
+
+
+
+ )
+}