Certificates section react work

This commit is contained in:
Jamie Curnow
2025-10-26 00:28:39 +10:00
parent 5b7013b8d5
commit bb6c9c8daf
24 changed files with 596 additions and 121 deletions

View File

@@ -58,7 +58,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id =
options?.unshift({
value: 0,
label: intl.formatMessage({ id: "access-list.public" }),
subLabel: "No basic auth required",
subLabel: intl.formatMessage({ id: "access-list.public.subtitle" }),
icon: <IconLockOpen2 size={14} className="text-red" />,
});

View File

@@ -1,8 +1,10 @@
import { IconAlertTriangle } from "@tabler/icons-react";
import { Field, useFormikContext } from "formik";
import { useState } from "react";
import Select, { type ActionMeta } from "react-select";
import type { DNSProvider } from "src/api/backend";
import { useDnsProviders } from "src/hooks";
import { T } from "src/locale";
import styles from "./DNSProviderFields.module.css";
interface DNSProviderOption {
@@ -10,7 +12,11 @@ interface DNSProviderOption {
readonly label: string;
readonly credentials: string;
}
export function DNSProviderFields() {
interface Props {
showBoundaryBox?: boolean;
}
export function DNSProviderFields({ showBoundaryBox = false }: Props) {
const { values, setFieldValue } = useFormikContext();
const { data: dnsProviders, isLoading } = useDnsProviders();
const [dnsProviderId, setDnsProviderId] = useState<string | null>(null);
@@ -31,17 +37,17 @@ export function DNSProviderFields() {
})) || [];
return (
<div className={styles.dnsChallengeWarning}>
<p className="text-info">
This section requires some knowledge about Certbot and DNS plugins. Please consult the respective
plugins documentation.
<div className={showBoundaryBox ? styles.dnsChallengeWarning : undefined}>
<p className="text-warning">
<IconAlertTriangle size={16} className="me-1" />
<T id="certificates.dns.warning" />
</p>
<Field name="meta.dnsProvider">
{({ field }: any) => (
<div className="row">
<label htmlFor="dnsProvider" className="form-label">
DNS Provider
<T id="certificates.dns.provider" />
</label>
<Select
className="react-select-container"
@@ -66,7 +72,7 @@ export function DNSProviderFields() {
{({ field }: any) => (
<div className="mt-3">
<label htmlFor="dnsProviderCredentials" className="form-label">
Credentials File Content
<T id="certificates.dns.credentials" />
</label>
<textarea
id="dnsProviderCredentials"
@@ -78,13 +84,12 @@ export function DNSProviderFields() {
/>
<div>
<small className="text-muted">
This plugin requires a configuration file containing an API token or other
credentials to your provider
<T id="certificates.dns.credentials-note" />
</small>
</div>
<div>
<small className="text-danger">
This data will be stored as plaintext in the database and in a file!
<T id="certificates.dns.credentials-warning" />
</small>
</div>
</div>
@@ -94,20 +99,18 @@ export function DNSProviderFields() {
{({ field }: any) => (
<div className="mt-3">
<label htmlFor="propagationSeconds" className="form-label">
Propagation Seconds
<T id="certificates.dns.propagation-seconds" />
</label>
<input
id="propagationSeconds"
type="number"
x
className="form-control"
min={0}
max={600}
{...field}
/>
<small className="text-muted">
Leave empty to use the plugins default value. Number of seconds to wait for DNS
propagation.
<T id="certificates.dns.propagation-seconds-note" />
</small>
</div>
)}

View File

@@ -18,14 +18,16 @@ interface Props {
dnsProviderWildcardSupported?: boolean;
name?: string;
label?: string;
onChange?: (domains: string[]) => void;
}
export function DomainNamesField({
name = "domainNames",
label = "domain-names",
id = "domainNames",
maxDomains,
isWildcardPermitted = true,
dnsProviderWildcardSupported = true,
isWildcardPermitted = false,
dnsProviderWildcardSupported = false,
onChange,
}: Props) {
const { setFieldValue } = useFormikContext();
@@ -34,6 +36,7 @@ export function DomainNamesField({
return i.value;
});
setFieldValue(name, doms);
onChange?.(doms);
};
const helperTexts: ReactNode[] = [];

View File

@@ -11,7 +11,7 @@ interface Props {
initialValues: ProxyLocation[];
name?: string;
}
export function LocationsFields({ initialValues, name = "items" }: Props) {
export function LocationsFields({ initialValues, name = "locations" }: Props) {
const [values, setValues] = useState<ProxyLocation[]>(initialValues || []);
const { setFieldValue } = useFormikContext();
const [advVisible, setAdvVisible] = useState<number[]>([]);

View File

@@ -30,6 +30,7 @@ export function NginxConfigField({
fontFamily: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
borderRadius: "0.3rem",
minHeight: "200px",
backgroundColor: "var(--tblr-bg-surface-dark)",
}}
{...field}
/>

View File

@@ -3,7 +3,7 @@ import { Field, useFormikContext } from "formik";
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
import type { Certificate } from "src/api/backend";
import { useCertificates } from "src/hooks";
import { DateTimeFormat, T } from "src/locale";
import { DateTimeFormat, intl, T } from "src/locale";
interface CertOption {
readonly value: number | "new";
@@ -75,9 +75,7 @@ export function SSLCertificateField({
data?.map((cert: Certificate) => ({
value: cert.id,
label: cert.niceName,
subLabel: `${cert.provider === "letsencrypt" ? "Let's Encrypt" : cert.provider} &mdash; Expires: ${
cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A"
}`,
subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} &mdash; ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" })}`,
icon: <IconShield size={14} className="text-pink" />,
})) || [];
@@ -85,8 +83,8 @@ export function SSLCertificateField({
if (allowNew) {
options?.unshift({
value: "new",
label: "Request a new Certificate",
subLabel: "with Let's Encrypt",
label: intl.formatMessage({ id: "certificates.request.title" }),
subLabel: intl.formatMessage({ id: "certificates.request.subtitle" }),
icon: <IconShield size={14} className="text-lime" />,
});
}
@@ -95,8 +93,10 @@ export function SSLCertificateField({
if (!required) {
options?.unshift({
value: 0,
label: "None",
subLabel: forHttp ? "This host will not use HTTPS" : "No certificate assigned",
label: intl.formatMessage({ id: "certificate.none.title" }),
subLabel: forHttp
? intl.formatMessage({ id: "certificate.none.subtitle.for-http" })
: intl.formatMessage({ id: "certificate.none.subtitle" }),
icon: <IconShield size={14} className="text-red" />,
});
}

View File

@@ -136,8 +136,8 @@ export function SSLOptionsFields({ forHttp = true, forceDNSForNew, requireDomain
</label>
)}
</Field>
{requireDomainNames ? <DomainNamesField /> : null}
{dnsChallenge ? <DNSProviderFields /> : null}
{requireDomainNames ? <DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> : null}
{dnsChallenge ? <DNSProviderFields showBoundaryBox /> : null}
</>
) : null}
</div>

View File

@@ -64,7 +64,7 @@ export function EventFormatter({ row }: Props) {
<div className="flex-fill">
<div className="font-weight-medium">
{getIcon(row)}
<T id={`event.${row.action}-${row.objectType}`} />
<T id={`object.event.${row.action}`} tData={{ object: row.objectType }} />
&mdash; <span className="badge">{getEventValue(row)}</span>
</div>
<div className="text-secondary mt-1">{DateTimeFormat(row.createdOn)}</div>