import type { components } from '@pipu/api-client'; import { unwrap } from '@pipu/core'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSession } from '../../app/session'; import { useErrorText } from '../../shared/lib/errors'; import { Button, Dialog, Field, FormError, PageSkeleton } from '../../shared/ui/ui'; import { JobPanel } from './JobPanel'; import s from './management.module.css'; export function ConfigPage() { const { t } = useTranslation('admin'); const session = useSession(); const errorText = useErrorText(); const qc = useQueryClient(); const [draft, setDraft] = useState>({}); const [baseRevision, setBaseRevision] = useState(null); const [search, setSearch] = useState(''); const [group, setGroup] = useState('all'); const [confirm, setConfirm] = useState(false); const [saved, setSaved] = useState(false); const dirty = Object.keys(draft).length > 0; const query = useQuery({ queryKey: ['server-config'], queryFn: async () => unwrap(await session.client.GET('/api/v1/admin/server/config')), retry: false }); const status = useQuery({ queryKey: ['server-updates', 'config'], queryFn: async () => unwrap(await session.client.GET('/api/v1/admin/server/updates')), refetchInterval: 3000, retry: false }); const running = status.data?.job?.state === 'running'; useEffect(() => { if (!dirty) return; const prevent = (e: BeforeUnloadEvent) => { e.preventDefault(); }; window.addEventListener('beforeunload', prevent); return () => window.removeEventListener('beforeunload', prevent); }, [dirty]); useEffect(() => { if (status.data?.job?.state === 'succeeded') void qc.invalidateQueries({ queryKey: ['server-config'] }); }, [status.data?.job?.id, status.data?.job?.state, qc]); const save = useMutation({ mutationFn: async () => unwrap(await session.client.PATCH('/api/v1/admin/server/config', { body: { revision: baseRevision ?? query.data!.revision, values: draft } })), onSuccess: (data) => { qc.setQueryData(['server-config'], data); setDraft({}); setBaseRevision(null); setSaved(true); }, }); const apply = useMutation({ mutationFn: async () => unwrap(await session.client.POST('/api/v1/admin/server/config/apply')), onSuccess: (job) => { setConfirm(false); qc.setQueryData(['server-updates', 'config'], (old) => old ? { ...old, job } : old); void qc.invalidateQueries({ queryKey: ['server-updates'] }); }, }); const change = (key: string, value: string | null) => { const entry = query.data!.entries.find((v) => v.key === key)!; const unchanged = value === null ? entry.local === null : entry.secret ? value === '' : value === (entry.local ?? ''); const next = { ...draft }; if (unchanged) delete next[key]; else next[key] = value; setBaseRevision(Object.keys(next).length ? baseRevision ?? query.data!.revision : null); setDraft(next); setSaved(false); save.reset(); }; if (query.isPending) return ; const entries = query.data?.entries.filter((e) => (group === 'all' || e.group === group) && `${e.key} ${e.group}`.toLowerCase().includes(search.toLowerCase())) ?? []; return
{t('management.server')}

{t('management.config')}

{t('management.config_intro')}

{query.error && {errorText(query.error)}} {query.data && <>
{t('management.priority')}ENV → config.local → {t('management.defaults')}

{t('management.priority_hint')}

{t('management.config_files_hint')}

{entries.map((entry) => { const changed = Object.hasOwn(draft, entry.key); const value = changed ? draft[entry.key] ?? '' : entry.secret ? '' : entry.local ?? ''; return
{entry.key}{t(`management.groups.${entry.group}`, { defaultValue: entry.group })}{t(`management.value_source.${entry.source}`)}
{t('management.effective')}: {entry.effective || '—'}ENV: {entry.env ?? '—'}
{entry.readonly ?

{t('management.readonly')}

:
change(entry.key, v)} mono />
}
; })}
{!entries.length &&

{t('management.no_results')}

} {saved && !dirty &&

{t('management.saved')}

} {dirty &&
{t('management.unsaved', { count: Object.keys(draft).length })}
} {!dirty && query.data.restart_required && !running &&
{t('management.pending_restart')}
} {save.error && {errorText(save.error)}} {apply.error && {errorText(apply.error)}} } {confirm && !apply.isPending && setConfirm(false)}>

{t('management.apply_warning')}

{apply.error && {errorText(apply.error)}}
}
; }