120 lines
4.1 KiB
TypeScript
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>
|
|
);
|
|
}
|