maestro/ui/src/components/settings/ServerTlsForm.tsx
oss-sync 3b1645cc91
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (d31b280)
2026-06-11 11:28:40 +00:00

120 lines
4.1 KiB
TypeScript

import { useTranslation } from 'react-i18next';
import { HelpText } from './HelpText';
import { FieldLabel, FieldInput } from './formUtils';
import { StringArrayEditor } from './StringArrayEditor';
import type { SectionFormProps } from './types';
/**
* Server TLS settings — binds to the `server.tls` config object.
*
* Editable fields: enabled, certFile, keyFile, httpRedirect,
* httpRedirectPort, redirectHost, selfSignedHosts.
*
* Fields intentionally omitted from the UI (left to config.yaml):
* minVersion, selfSignedDir. The onChange path mechanism in ConfigFormInner
* uses setNestedValue which does a shallow-merge, so unedited fields are
* preserved on save automatically.
*
* TODO(server-tls-info): cert source/expiry/fingerprint panel + regenerate
* button need a GET /api/server/tls-info endpoint (future).
*/
export function ServerTlsForm({ config, onChange }: SectionFormProps) {
const { t } = useTranslation('settings');
// Navigate to the server.tls sub-object; fall back to empty object if absent.
const tls = (config?.server?.tls) ?? {};
return (
<div className="space-y-5">
<h2 className="text-base font-semibold text-slate-800 dark:text-slate-100">
{t('serverTls.title')}
</h2>
{/* Restart-required banner — always visible */}
<div className="px-3 py-2.5 rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-500/10 dark:border-amber-500/30 text-xs text-amber-800 dark:text-amber-300">
{t('serverTls.restartBanner')}
</div>
{/* Enable HTTPS */}
<div>
<label className="inline-flex items-center gap-2 text-[13px] text-slate-700 dark:text-slate-200">
<input
type="checkbox"
checked={tls.enabled === true}
onChange={e => onChange('server.tls.enabled', e.target.checked)}
/>
<span>{t('serverTls.enabled')}</span>
</label>
<HelpText>{t('serverTls.enabledHelp')}</HelpText>
</div>
{/* Certificate file */}
<div>
<FieldLabel>{t('serverTls.certFile')}</FieldLabel>
<FieldInput
value={tls.certFile ?? ''}
onChange={v => onChange('server.tls.certFile', v || undefined)}
placeholder="/etc/ssl/certs/server.pem"
/>
</div>
{/* Private key file */}
<div>
<FieldLabel>{t('serverTls.keyFile')}</FieldLabel>
<FieldInput
value={tls.keyFile ?? ''}
onChange={v => onChange('server.tls.keyFile', v || undefined)}
placeholder="/etc/ssl/private/server.key"
/>
<HelpText>{t('serverTls.certHelp')}</HelpText>
</div>
{/* HTTP → HTTPS redirect */}
<div>
<label className="inline-flex items-center gap-2 text-[13px] text-slate-700 dark:text-slate-200">
<input
type="checkbox"
checked={tls.httpRedirect === true}
onChange={e => onChange('server.tls.httpRedirect', e.target.checked)}
/>
<span>{t('serverTls.httpRedirect')}</span>
</label>
</div>
{/* HTTP redirect port */}
<div>
<FieldLabel>{t('serverTls.httpRedirectPort')}</FieldLabel>
<FieldInput
type="number"
value={tls.httpRedirectPort != null ? String(tls.httpRedirectPort) : ''}
onChange={v => {
const n = parseInt(v, 10);
onChange('server.tls.httpRedirectPort', isNaN(n) ? undefined : n);
}}
placeholder="9080"
/>
</div>
{/* Redirect host (optional) */}
<div>
<FieldLabel>{t('serverTls.redirectHost')}</FieldLabel>
<FieldInput
value={tls.redirectHost ?? ''}
onChange={v => onChange('server.tls.redirectHost', v || undefined)}
placeholder="example.com"
/>
</div>
{/* Additional SAN hostnames for self-signed cert */}
<div>
<FieldLabel>{t('serverTls.selfSignedHosts')}</FieldLabel>
<StringArrayEditor
value={tls.selfSignedHosts ?? []}
onChange={v => onChange('server.tls.selfSignedHosts', v)}
placeholder="example.com / 10.0.0.10"
/>
</div>
</div>
);
}