mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 07:43:33 +00:00 
			
		
		
		
	Certificates section react work
This commit is contained in:
		| @@ -4,7 +4,6 @@ import type { Certificate } from "./models"; | ||||
| export async function createCertificate(item: Certificate): Promise<Certificate> { | ||||
| 	return await api.post({ | ||||
| 		url: "/nginx/certificates", | ||||
| 		// todo: only use whitelist of fields for this data | ||||
| 		data: item, | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import * as api from "./base"; | ||||
|  | ||||
| export async function testHttpCertificate(domains: string[]): Promise<Record<string, string>> { | ||||
| 	return await api.get({ | ||||
| 	return await api.post({ | ||||
| 		url: "/nginx/certificates/test-http", | ||||
| 		params: { | ||||
| 			domains: domains.join(","), | ||||
| 		data: { | ||||
| 			domains, | ||||
| 		}, | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -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" />, | ||||
| 	}); | ||||
|  | ||||
|   | ||||
| @@ -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> | ||||
| 						)} | ||||
|   | ||||
| @@ -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[] = []; | ||||
|   | ||||
| @@ -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[]>([]); | ||||
|   | ||||
| @@ -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} | ||||
| 					/> | ||||
|   | ||||
| @@ -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} — Expires: ${ | ||||
| 				cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" | ||||
| 			}`, | ||||
| 			subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${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" />, | ||||
| 		}); | ||||
| 	} | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 }} /> | ||||
| 				— <span className="badge">{getEventValue(row)}</span> | ||||
| 			</div> | ||||
| 			<div className="text-secondary mt-1">{DateTimeFormat(row.createdOn)}</div> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|   "access-list.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.", | ||||
|   "access-list.pass-auth": "Pass Auth to Upstream", | ||||
|   "access-list.public": "Publicly Accessible", | ||||
|   "access-list.public.subtitle": "No basic auth required", | ||||
|   "access-list.satisfy-any": "Satisfy Any", | ||||
|   "access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}", | ||||
|   "access-lists": "Access Lists", | ||||
| @@ -21,8 +22,28 @@ | ||||
|   "auditlogs": "Audit Logs", | ||||
|   "cancel": "Cancel", | ||||
|   "certificate": "Certificate", | ||||
|   "certificate.none.subtitle": "No certificate assigned", | ||||
|   "certificate.none.subtitle.for-http": "This host will not use HTTPS", | ||||
|   "certificate.none.title": "None", | ||||
|   "certificates": "Certificates", | ||||
|   "certificates.custom": "Custom Certificate", | ||||
|   "certificates.dns.credentials": "Credentials File Content", | ||||
|   "certificates.dns.credentials-note": "This plugin requires a configuration file containing an API token or other credentials for your provider", | ||||
|   "certificates.dns.credentials-warning": "This data will be stored as plaintext in the database and in a file!", | ||||
|   "certificates.dns.propagation-seconds": "Propagation Seconds", | ||||
|   "certificates.dns.propagation-seconds-note": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.", | ||||
|   "certificates.dns.provider": "DNS Provider", | ||||
|   "certificates.dns.warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.", | ||||
|   "certificates.http.reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.", | ||||
|   "certificates.http.reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.", | ||||
|   "certificates.http.reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.", | ||||
|   "certificates.http.reachability-ok": "Your server is reachable and creating certificates should be possible.", | ||||
|   "certificates.http.reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", | ||||
|   "certificates.http.reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", | ||||
|   "certificates.http.test-results": "Test Results", | ||||
|   "certificates.http.warning": "These domains must be already configured to point to this installation.", | ||||
|   "certificates.request.subtitle": "with Let's Encrypt", | ||||
|   "certificates.request.title": "Request a new Certificate", | ||||
|   "column.access": "Access", | ||||
|   "column.authorization": "Authorization", | ||||
|   "column.authorizations": "Authorizations", | ||||
| @@ -74,22 +95,7 @@ | ||||
|   "error.max-domains": "Too many domains, max is {max}", | ||||
|   "error.passwords-must-match": "Passwords must match", | ||||
|   "error.required": "This is required", | ||||
|   "event.created-access-list": "Created Access List", | ||||
|   "event.created-dead-host": "Created 404 Host", | ||||
|   "event.created-redirection-host": "Created Redirection Host", | ||||
|   "event.created-stream": "Created Stream", | ||||
|   "event.created-user": "Created User", | ||||
|   "event.deleted-dead-host": "Deleted 404 Host", | ||||
|   "event.deleted-stream": "Deleted Stream", | ||||
|   "event.deleted-user": "Deleted User", | ||||
|   "event.disabled-dead-host": "Disabled 404 Host", | ||||
|   "event.disabled-redirection-host": "Disabled Redirection Host", | ||||
|   "event.disabled-stream": "Disabled Stream", | ||||
|   "event.enabled-dead-host": "Enabled 404 Host", | ||||
|   "event.enabled-redirection-host": "Enabled Redirection Host", | ||||
|   "event.enabled-stream": "Enabled Stream", | ||||
|   "event.updated-redirection-host": "Updated Redirection Host", | ||||
|   "event.updated-user": "Updated User", | ||||
|   "expires.on": "Expires: {date}", | ||||
|   "footer.github-fork": "Fork me on Github", | ||||
|   "host.flags.block-exploits": "Block Common Exploits", | ||||
|   "host.flags.cache-assets": "Cache Assets", | ||||
| @@ -101,6 +107,8 @@ | ||||
|   "hosts": "Hosts", | ||||
|   "http-only": "HTTP Only", | ||||
|   "lets-encrypt": "Let's Encrypt", | ||||
|   "lets-encrypt-via-dns": "Let's Encrypt via DNS", | ||||
|   "lets-encrypt-via-http": "Let's Encrypt via HTTP", | ||||
|   "loading": "Loading…", | ||||
|   "login.title": "Login to your account", | ||||
|   "nginx-config.label": "Custom Nginx Configuration", | ||||
| @@ -121,6 +129,11 @@ | ||||
|   "object.delete.content": "Are you sure you want to delete this {object}?", | ||||
|   "object.edit": "Edit {object}", | ||||
|   "object.empty": "There are no {objects}", | ||||
|   "object.event.created": "Created {object}", | ||||
|   "object.event.deleted": "Deleted {object}", | ||||
|   "object.event.disabled": "Disabled {object}", | ||||
|   "object.event.enabled": "Enabled {object}", | ||||
|   "object.event.updated": "Updated {object}", | ||||
|   "offline": "Offline", | ||||
|   "online": "Online", | ||||
|   "options": "Options", | ||||
| @@ -158,6 +171,7 @@ | ||||
|   "streams.count": "{count} {count, plural, one {Stream} other {Streams}}", | ||||
|   "streams.tcp": "TCP", | ||||
|   "streams.udp": "UDP", | ||||
|   "test": "Test", | ||||
|   "user": "User", | ||||
|   "user.change-password": "Change Password", | ||||
|   "user.confirm-password": "Confirm Password", | ||||
|   | ||||
| @@ -20,6 +20,9 @@ | ||||
| 	"access-list.public": { | ||||
| 		"defaultMessage": "Publicly Accessible" | ||||
| 	}, | ||||
| 	"access-list.public.subtitle": { | ||||
| 		"defaultMessage": "No basic auth required" | ||||
| 	}, | ||||
| 	"access-list.satisfy-any": { | ||||
| 		"defaultMessage": "Satisfy Any" | ||||
| 	}, | ||||
| @@ -65,12 +68,72 @@ | ||||
| 	"certificate": { | ||||
| 		"defaultMessage": "Certificate" | ||||
| 	}, | ||||
| 	"certificate.none.subtitle": { | ||||
| 		"defaultMessage": "No certificate assigned" | ||||
| 	}, | ||||
| 	"certificate.none.subtitle.for-http": { | ||||
| 		"defaultMessage": "This host will not use HTTPS" | ||||
| 	}, | ||||
| 	"certificate.none.title": { | ||||
| 		"defaultMessage": "None" | ||||
| 	}, | ||||
| 	"certificates": { | ||||
| 		"defaultMessage": "Certificates" | ||||
| 	}, | ||||
| 	"certificates.custom": { | ||||
| 		"defaultMessage": "Custom Certificate" | ||||
| 	}, | ||||
| 	"certificates.dns.credentials": { | ||||
| 		"defaultMessage": "Credentials File Content" | ||||
| 	}, | ||||
| 	"certificates.dns.credentials-note": { | ||||
| 		"defaultMessage": "This plugin requires a configuration file containing an API token or other credentials for your provider" | ||||
| 	}, | ||||
| 	"certificates.dns.credentials-warning": { | ||||
| 		"defaultMessage": "This data will be stored as plaintext in the database and in a file!" | ||||
| 	}, | ||||
| 	"certificates.dns.propagation-seconds": { | ||||
| 		"defaultMessage": "Propagation Seconds" | ||||
| 	}, | ||||
| 	"certificates.dns.propagation-seconds-note": { | ||||
| 		"defaultMessage": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation." | ||||
| 	}, | ||||
| 	"certificates.dns.provider": { | ||||
| 		"defaultMessage": "DNS Provider" | ||||
| 	}, | ||||
| 	"certificates.dns.warning": { | ||||
| 		"defaultMessage": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation." | ||||
| 	}, | ||||
| 	"certificates.http.reachability-404": { | ||||
| 		"defaultMessage": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running." | ||||
| 	}, | ||||
| 	"certificates.http.reachability-failed-to-check": { | ||||
| 		"defaultMessage": "Failed to check the reachability due to a communication error with site24x7.com." | ||||
| 	}, | ||||
| 	"certificates.http.reachability-not-resolved": { | ||||
| 		"defaultMessage": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router." | ||||
| 	}, | ||||
| 	"certificates.http.reachability-ok": { | ||||
| 		"defaultMessage": "Your server is reachable and creating certificates should be possible." | ||||
| 	}, | ||||
| 	"certificates.http.reachability-other": { | ||||
| 		"defaultMessage": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running." | ||||
| 	}, | ||||
| 	"certificates.http.reachability-wrong-data": { | ||||
| 		"defaultMessage": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running." | ||||
| 	}, | ||||
| 	"certificates.http.test-results": { | ||||
| 		"defaultMessage": "Test Results" | ||||
| 	}, | ||||
| 	"certificates.http.warning": { | ||||
| 		"defaultMessage": "These domains must be already configured to point to this installation." | ||||
| 	}, | ||||
| 	"certificates.request.subtitle": { | ||||
| 		"defaultMessage": "with Let's Encrypt" | ||||
| 	}, | ||||
| 	"certificates.request.title": { | ||||
| 		"defaultMessage": "Request a new Certificate" | ||||
| 	}, | ||||
| 	"column.access": { | ||||
| 		"defaultMessage": "Access" | ||||
| 	}, | ||||
| @@ -224,53 +287,8 @@ | ||||
| 	"error.required": { | ||||
| 		"defaultMessage": "This is required" | ||||
| 	}, | ||||
| 	"event.created-access-list": { | ||||
| 		"defaultMessage": "Created Access List" | ||||
| 	}, | ||||
| 	"event.created-dead-host": { | ||||
| 		"defaultMessage": "Created 404 Host" | ||||
| 	}, | ||||
| 	"event.created-redirection-host": { | ||||
| 		"defaultMessage": "Created Redirection Host" | ||||
| 	}, | ||||
| 	"event.created-stream": { | ||||
| 		"defaultMessage": "Created Stream" | ||||
| 	}, | ||||
| 	"event.created-user": { | ||||
| 		"defaultMessage": "Created User" | ||||
| 	}, | ||||
| 	"event.deleted-dead-host": { | ||||
| 		"defaultMessage": "Deleted 404 Host" | ||||
| 	}, | ||||
| 	"event.deleted-stream": { | ||||
| 		"defaultMessage": "Deleted Stream" | ||||
| 	}, | ||||
| 	"event.deleted-user": { | ||||
| 		"defaultMessage": "Deleted User" | ||||
| 	}, | ||||
| 	"event.disabled-dead-host": { | ||||
| 		"defaultMessage": "Disabled 404 Host" | ||||
| 	}, | ||||
| 	"event.disabled-redirection-host": { | ||||
| 		"defaultMessage": "Disabled Redirection Host" | ||||
| 	}, | ||||
| 	"event.disabled-stream": { | ||||
| 		"defaultMessage": "Disabled Stream" | ||||
| 	}, | ||||
| 	"event.enabled-dead-host": { | ||||
| 		"defaultMessage": "Enabled 404 Host" | ||||
| 	}, | ||||
| 	"event.enabled-redirection-host": { | ||||
| 		"defaultMessage": "Enabled Redirection Host" | ||||
| 	}, | ||||
| 	"event.enabled-stream": { | ||||
| 		"defaultMessage": "Enabled Stream" | ||||
| 	}, | ||||
| 	"event.updated-redirection-host": { | ||||
| 		"defaultMessage": "Updated Redirection Host" | ||||
| 	}, | ||||
| 	"event.updated-user": { | ||||
| 		"defaultMessage": "Updated User" | ||||
| 	"expires.on": { | ||||
| 		"defaultMessage": "Expires: {date}" | ||||
| 	}, | ||||
| 	"footer.github-fork": { | ||||
| 		"defaultMessage": "Fork me on Github" | ||||
| @@ -305,6 +323,12 @@ | ||||
| 	"lets-encrypt": { | ||||
| 		"defaultMessage": "Let's Encrypt" | ||||
| 	}, | ||||
| 	"lets-encrypt-via-dns": { | ||||
| 		"defaultMessage": "Let's Encrypt via DNS" | ||||
| 	}, | ||||
| 	"lets-encrypt-via-http": { | ||||
| 		"defaultMessage": "Let's Encrypt via HTTP" | ||||
| 	}, | ||||
| 	"loading": { | ||||
| 		"defaultMessage": "Loading…" | ||||
| 	}, | ||||
| @@ -365,6 +389,21 @@ | ||||
| 	"object.empty": { | ||||
| 		"defaultMessage": "There are no {objects}" | ||||
| 	}, | ||||
| 	"object.event.created": { | ||||
| 		"defaultMessage": "Created {object}" | ||||
| 	}, | ||||
| 	"object.event.deleted": { | ||||
| 		"defaultMessage": "Deleted {object}" | ||||
| 	}, | ||||
| 	"object.event.disabled": { | ||||
| 		"defaultMessage": "Disabled {object}" | ||||
| 	}, | ||||
| 	"object.event.enabled": { | ||||
| 		"defaultMessage": "Enabled {object}" | ||||
| 	}, | ||||
| 	"object.event.updated": { | ||||
| 		"defaultMessage": "Updated {object}" | ||||
| 	}, | ||||
| 	"offline": { | ||||
| 		"defaultMessage": "Offline" | ||||
| 	}, | ||||
| @@ -476,6 +515,9 @@ | ||||
| 	"streams.udp": { | ||||
| 		"defaultMessage": "UDP" | ||||
| 	}, | ||||
| 	"test": { | ||||
| 		"defaultMessage": "Test" | ||||
| 	}, | ||||
| 	"user": { | ||||
| 		"defaultMessage": "User" | ||||
| 	}, | ||||
|   | ||||
							
								
								
									
										88
									
								
								frontend/src/modals/CustomCertificateModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								frontend/src/modals/CustomCertificateModal.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| import EasyModal, { type InnerModalProps } from "ez-modal-react"; | ||||
| import { Form, Formik } from "formik"; | ||||
| import { type ReactNode, useState } from "react"; | ||||
| import { Alert } from "react-bootstrap"; | ||||
| import Modal from "react-bootstrap/Modal"; | ||||
| import { Button, DomainNamesField } from "src/components"; | ||||
| import { useSetProxyHost } from "src/hooks"; | ||||
| import { T } from "src/locale"; | ||||
| import { showObjectSuccess } from "src/notifications"; | ||||
|  | ||||
| const showCustomCertificateModal = () => { | ||||
| 	EasyModal.show(CustomCertificateModal); | ||||
| }; | ||||
|  | ||||
| const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { | ||||
| 	const { mutate: setProxyHost } = useSetProxyHost(); | ||||
| 	const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null); | ||||
| 	const [isSubmitting, setIsSubmitting] = useState(false); | ||||
|  | ||||
| 	const onSubmit = async (values: any, { setSubmitting }: any) => { | ||||
| 		if (isSubmitting) return; | ||||
| 		setIsSubmitting(true); | ||||
| 		setErrorMsg(null); | ||||
|  | ||||
| 		setProxyHost(values, { | ||||
| 			onError: (err: any) => setErrorMsg(<T id={err.message} />), | ||||
| 			onSuccess: () => { | ||||
| 				showObjectSuccess("certificate", "saved"); | ||||
| 				remove(); | ||||
| 			}, | ||||
| 			onSettled: () => { | ||||
| 				setIsSubmitting(false); | ||||
| 				setSubmitting(false); | ||||
| 			}, | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<Modal show={visible} onHide={remove}> | ||||
| 			<Formik | ||||
| 				initialValues={ | ||||
| 					{ | ||||
| 						domainNames: [], | ||||
| 					} as any | ||||
| 				} | ||||
| 				onSubmit={onSubmit} | ||||
| 			> | ||||
| 				{() => ( | ||||
| 					<Form> | ||||
| 						<Modal.Header closeButton> | ||||
| 							<Modal.Title> | ||||
| 								<T id="object.add" tData={{ object: "certificate" }} /> | ||||
| 							</Modal.Title> | ||||
| 						</Modal.Header> | ||||
| 						<Modal.Body className="p-0"> | ||||
| 							<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible> | ||||
| 								{errorMsg} | ||||
| 							</Alert> | ||||
| 							<div className="card m-0 border-0"> | ||||
| 								<div className="card-header">asd</div> | ||||
| 								<div className="card-body"> | ||||
| 									<DomainNamesField /> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</Modal.Body> | ||||
| 						<Modal.Footer> | ||||
| 							<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}> | ||||
| 								<T id="cancel" /> | ||||
| 							</Button> | ||||
| 							<Button | ||||
| 								type="submit" | ||||
| 								actionType="primary" | ||||
| 								className="ms-auto bg-lime" | ||||
| 								data-bs-dismiss="modal" | ||||
| 								isLoading={isSubmitting} | ||||
| 								disabled={isSubmitting} | ||||
| 							> | ||||
| 								<T id="save" /> | ||||
| 							</Button> | ||||
| 						</Modal.Footer> | ||||
| 					</Form> | ||||
| 				)} | ||||
| 			</Formik> | ||||
| 		</Modal> | ||||
| 	); | ||||
| }); | ||||
|  | ||||
| export { showCustomCertificateModal }; | ||||
							
								
								
									
										89
									
								
								frontend/src/modals/DNSCertificateModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								frontend/src/modals/DNSCertificateModal.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| import { useQueryClient } from "@tanstack/react-query"; | ||||
| import EasyModal, { type InnerModalProps } from "ez-modal-react"; | ||||
| import { Form, Formik } from "formik"; | ||||
| import { type ReactNode, useState } from "react"; | ||||
| import { Alert } from "react-bootstrap"; | ||||
| import Modal from "react-bootstrap/Modal"; | ||||
| import { createCertificate } from "src/api/backend"; | ||||
| import { Button, DNSProviderFields, DomainNamesField } from "src/components"; | ||||
| import { T } from "src/locale"; | ||||
| import { showObjectSuccess } from "src/notifications"; | ||||
|  | ||||
| const showDNSCertificateModal = () => { | ||||
| 	EasyModal.show(DNSCertificateModal); | ||||
| }; | ||||
|  | ||||
| const DNSCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { | ||||
| 	const queryClient = useQueryClient(); | ||||
| 	const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null); | ||||
| 	const [isSubmitting, setIsSubmitting] = useState(false); | ||||
|  | ||||
| 	const onSubmit = async (values: any, { setSubmitting }: any) => { | ||||
| 		if (isSubmitting) return; | ||||
| 		setIsSubmitting(true); | ||||
| 		setErrorMsg(null); | ||||
|  | ||||
| 		try { | ||||
| 			await createCertificate(values); | ||||
| 			showObjectSuccess("certificate", "saved"); | ||||
| 			remove(); | ||||
| 		} catch (err: any) { | ||||
| 			setErrorMsg(<T id={err.message} />); | ||||
| 		} | ||||
| 		queryClient.invalidateQueries({ queryKey: ["certificates"] }); | ||||
| 		setIsSubmitting(false); | ||||
| 		setSubmitting(false); | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<Modal show={visible} onHide={remove}> | ||||
| 			<Formik | ||||
| 				initialValues={ | ||||
| 					{ | ||||
| 						domainNames: [], | ||||
| 						provider: "letsencrypt", | ||||
| 					} as any | ||||
| 				} | ||||
| 				onSubmit={onSubmit} | ||||
| 			> | ||||
| 				{() => ( | ||||
| 					<Form> | ||||
| 						<Modal.Header closeButton> | ||||
| 							<Modal.Title> | ||||
| 								<T id="object.add" tData={{ object: "lets-encrypt-via-dns" }} /> | ||||
| 							</Modal.Title> | ||||
| 						</Modal.Header> | ||||
| 						<Modal.Body className="p-0"> | ||||
| 							<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible> | ||||
| 								{errorMsg} | ||||
| 							</Alert> | ||||
| 							<div className="card m-0 border-0"> | ||||
| 								<div className="card-body"> | ||||
| 									<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||
| 									<DNSProviderFields /> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</Modal.Body> | ||||
| 						<Modal.Footer> | ||||
| 							<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}> | ||||
| 								<T id="cancel" /> | ||||
| 							</Button> | ||||
| 							<Button | ||||
| 								type="submit" | ||||
| 								actionType="primary" | ||||
| 								className="ms-auto bg-pink" | ||||
| 								data-bs-dismiss="modal" | ||||
| 								isLoading={isSubmitting} | ||||
| 								disabled={isSubmitting} | ||||
| 							> | ||||
| 								<T id="save" /> | ||||
| 							</Button> | ||||
| 						</Modal.Footer> | ||||
| 					</Form> | ||||
| 				)} | ||||
| 			</Formik> | ||||
| 		</Modal> | ||||
| 	); | ||||
| }); | ||||
|  | ||||
| export { showDNSCertificateModal }; | ||||
| @@ -131,7 +131,7 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => { | ||||
| 									<div className="card-body"> | ||||
| 										<div className="tab-content"> | ||||
| 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | ||||
| 												<DomainNamesField isWildcardPermitted /> | ||||
| 												<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||
| 											</div> | ||||
| 											<div className="tab-pane" id="tab-ssl" role="tabpanel"> | ||||
| 												<SSLCertificateField | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import CodeEditor from "@uiw/react-textarea-code-editor"; | ||||
| import EasyModal, { type InnerModalProps } from "ez-modal-react"; | ||||
| import { Alert } from "react-bootstrap"; | ||||
| import Modal from "react-bootstrap/Modal"; | ||||
| @@ -39,9 +40,22 @@ const EventDetailsModal = EasyModal.create(({ id, visible, remove }: Props) => { | ||||
| 								<EventFormatter row={data} /> | ||||
| 							</div> | ||||
| 							<hr className="mt-4 mb-3" /> | ||||
| 							<pre> | ||||
| 								<code>{JSON.stringify(data.meta, null, 2)}</code> | ||||
| 							</pre> | ||||
| 							<CodeEditor | ||||
| 								language="json" | ||||
| 								padding={15} | ||||
| 								data-color-mode="dark" | ||||
| 								minHeight={200} | ||||
| 								indentWidth={2} | ||||
| 								style={{ | ||||
| 									fontFamily: | ||||
| 										"ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace", | ||||
| 									borderRadius: "0.3rem", | ||||
| 									minHeight: "200px", | ||||
| 									backgroundColor: "var(--tblr-bg-surface-dark)", | ||||
| 								}} | ||||
| 								readOnly | ||||
| 								value={JSON.stringify(data.meta, null, 2)} | ||||
| 							/> | ||||
| 						</div> | ||||
| 					</Modal.Body> | ||||
| 					<Modal.Footer> | ||||
|   | ||||
							
								
								
									
										188
									
								
								frontend/src/modals/HTTPCertificateModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								frontend/src/modals/HTTPCertificateModal.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| import { IconAlertTriangle } from "@tabler/icons-react"; | ||||
| import EasyModal, { type InnerModalProps } from "ez-modal-react"; | ||||
| import { Form, Formik } from "formik"; | ||||
| import { type ReactNode, useState } from "react"; | ||||
| import { Alert } from "react-bootstrap"; | ||||
| import Modal from "react-bootstrap/Modal"; | ||||
| import { createCertificate, testHttpCertificate } from "src/api/backend"; | ||||
| import { Button, DomainNamesField } from "src/components"; | ||||
| import { T } from "src/locale"; | ||||
| import { showObjectSuccess } from "src/notifications"; | ||||
|  | ||||
| const showHTTPCertificateModal = () => { | ||||
| 	EasyModal.show(HTTPCertificateModal); | ||||
| }; | ||||
|  | ||||
| const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { | ||||
| 	const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null); | ||||
| 	const [isSubmitting, setIsSubmitting] = useState(false); | ||||
| 	const [domains, setDomains] = useState([] as string[]); | ||||
| 	const [isTesting, setIsTesting] = useState(false); | ||||
| 	const [testResults, setTestResults] = useState(null as Record<string, string> | null); | ||||
|  | ||||
| 	const onSubmit = async (values: any, { setSubmitting }: any) => { | ||||
| 		if (isSubmitting) return; | ||||
| 		setIsSubmitting(true); | ||||
| 		setErrorMsg(null); | ||||
|  | ||||
| 		try { | ||||
| 			await createCertificate(values); | ||||
| 			showObjectSuccess("certificate", "saved"); | ||||
| 			remove(); | ||||
| 		} catch (err: any) { | ||||
| 			setErrorMsg(<T id={err.message} />); | ||||
| 		} | ||||
| 		setIsSubmitting(false); | ||||
| 		setSubmitting(false); | ||||
| 	}; | ||||
|  | ||||
| 	const handleTest = async () => { | ||||
| 		setIsTesting(true); | ||||
| 		setErrorMsg(null); | ||||
| 		setTestResults(null); | ||||
| 		try { | ||||
| 			const result = await testHttpCertificate(domains); | ||||
| 			setTestResults(result); | ||||
| 		} catch (err: any) { | ||||
| 			setErrorMsg(<T id={err.message} />); | ||||
| 		} | ||||
| 		setIsTesting(false); | ||||
| 	}; | ||||
|  | ||||
| 	const parseTestResults = () => { | ||||
| 		const elms = []; | ||||
| 		for (const domain in testResults) { | ||||
| 			const status = testResults[domain]; | ||||
| 			if (status === "ok") { | ||||
| 				elms.push( | ||||
| 					<p> | ||||
| 						<strong>{domain}:</strong> <T id="certificates.http.reachability-ok" /> | ||||
| 					</p>, | ||||
| 				); | ||||
| 			} else { | ||||
| 				if (status === "no-host") { | ||||
| 					elms.push( | ||||
| 						<p> | ||||
| 							<strong>{domain}:</strong> <T id="certificates.http.reachability-not-resolved" /> | ||||
| 						</p>, | ||||
| 					); | ||||
| 				} else if (status === "failed") { | ||||
| 					elms.push( | ||||
| 						<p> | ||||
| 							<strong>{domain}:</strong> <T id="certificates.http.reachability-failed-to-check" /> | ||||
| 						</p>, | ||||
| 					); | ||||
| 				} else if (status === "404") { | ||||
| 					elms.push( | ||||
| 						<p> | ||||
| 							<strong>{domain}:</strong> <T id="certificates.http.reachability-404" /> | ||||
| 						</p>, | ||||
| 					); | ||||
| 				} else if (status === "wrong-data") { | ||||
| 					elms.push( | ||||
| 						<p> | ||||
| 							<strong>{domain}:</strong> <T id="certificates.http.reachability-wrong-data" /> | ||||
| 						</p>, | ||||
| 					); | ||||
| 				} else if (status.startsWith("other:")) { | ||||
| 					const code = status.substring(6); | ||||
| 					elms.push( | ||||
| 						<p> | ||||
| 							<strong>{domain}:</strong> <T id="certificates.http.reachability-other" data={{ code }} /> | ||||
| 						</p>, | ||||
| 					); | ||||
| 				} else { | ||||
| 					// This should never happen | ||||
| 					elms.push( | ||||
| 						<p> | ||||
| 							<strong>{domain}:</strong> ? | ||||
| 						</p>, | ||||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return <>{elms}</>; | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<Modal show={visible} onHide={remove}> | ||||
| 			<Formik | ||||
| 				initialValues={ | ||||
| 					{ | ||||
| 						domainNames: [], | ||||
| 						provider: "letsencrypt", | ||||
| 					} as any | ||||
| 				} | ||||
| 				onSubmit={onSubmit} | ||||
| 			> | ||||
| 				{() => ( | ||||
| 					<Form> | ||||
| 						<Modal.Header closeButton> | ||||
| 							<Modal.Title> | ||||
| 								<T id="object.add" tData={{ object: "lets-encrypt-via-http" }} /> | ||||
| 							</Modal.Title> | ||||
| 						</Modal.Header> | ||||
| 						<Modal.Body className="p-0"> | ||||
| 							<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible> | ||||
| 								{errorMsg} | ||||
| 							</Alert> | ||||
| 							<div className="card m-0 border-0"> | ||||
| 								<div className="card-body"> | ||||
| 									<p className="text-warning"> | ||||
| 										<IconAlertTriangle size={16} className="me-1" /> | ||||
| 										<T id="certificates.http.warning" /> | ||||
| 									</p> | ||||
| 									<DomainNamesField | ||||
| 										onChange={(doms) => { | ||||
| 											setDomains(doms); | ||||
| 											setTestResults(null); | ||||
| 										}} | ||||
| 									/> | ||||
| 								</div> | ||||
| 								{testResults ? ( | ||||
| 									<div className="card-footer"> | ||||
| 										<h5> | ||||
| 											<T id="certificates.http.test-results" /> | ||||
| 										</h5> | ||||
| 										{parseTestResults()} | ||||
| 									</div> | ||||
| 								) : null} | ||||
| 							</div> | ||||
| 						</Modal.Body> | ||||
| 						<Modal.Footer> | ||||
| 							<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting || isTesting}> | ||||
| 								<T id="cancel" /> | ||||
| 							</Button> | ||||
| 							<div className="ms-auto"> | ||||
| 								<Button | ||||
| 									type="button" | ||||
| 									actionType="secondary" | ||||
| 									className="me-3" | ||||
| 									data-bs-dismiss="modal" | ||||
| 									isLoading={isTesting} | ||||
| 									disabled={isSubmitting || domains.length === 0} | ||||
| 									onClick={handleTest} | ||||
| 								> | ||||
| 									<T id="test" /> | ||||
| 								</Button> | ||||
| 								<Button | ||||
| 									type="submit" | ||||
| 									actionType="primary" | ||||
| 									className="bg-pink" | ||||
| 									data-bs-dismiss="modal" | ||||
| 									isLoading={isSubmitting} | ||||
| 									disabled={isSubmitting || isTesting} | ||||
| 								> | ||||
| 									<T id="save" /> | ||||
| 								</Button> | ||||
| 							</div> | ||||
| 						</Modal.Footer> | ||||
| 					</Form> | ||||
| 				)} | ||||
| 			</Formik> | ||||
| 		</Modal> | ||||
| 	); | ||||
| }); | ||||
|  | ||||
| export { showHTTPCertificateModal }; | ||||
| @@ -159,7 +159,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => { | ||||
| 									<div className="card-body"> | ||||
| 										<div className="tab-content"> | ||||
| 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | ||||
| 												<DomainNamesField isWildcardPermitted /> | ||||
| 												<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||
| 												<div className="row"> | ||||
| 													<div className="col-md-3"> | ||||
| 														<Field name="forwardScheme"> | ||||
|   | ||||
| @@ -144,7 +144,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = | ||||
| 									<div className="card-body"> | ||||
| 										<div className="tab-content"> | ||||
| 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | ||||
| 												<DomainNamesField isWildcardPermitted /> | ||||
| 												<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||
| 												<div className="row"> | ||||
| 													<div className="col-md-4"> | ||||
| 														<Field name="forwardScheme"> | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| export * from "./AccessListModal"; | ||||
| export * from "./ChangePasswordModal"; | ||||
| export * from "./CustomCertificateModal"; | ||||
| export * from "./DeadHostModal"; | ||||
| export * from "./DeleteConfirmModal"; | ||||
| export * from "./DNSCertificateModal"; | ||||
| export * from "./EventDetailsModal"; | ||||
| export * from "./HTTPCertificateModal"; | ||||
| export * from "./PermissionsModal"; | ||||
| export * from "./ProxyHostModal"; | ||||
| export * from "./RedirectionHostModal"; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import type { Certificate } from "src/api/backend"; | ||||
| import { DomainsFormatter, EmptyData, GravatarFormatter } from "src/components"; | ||||
| import { TableLayout } from "src/components/Table/TableLayout"; | ||||
| import { intl, T } from "src/locale"; | ||||
| import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals"; | ||||
|  | ||||
| interface Props { | ||||
| 	data: Certificate[]; | ||||
| @@ -121,17 +122,28 @@ export default function Table({ data, isFetching }: Props) { | ||||
| 					href="#" | ||||
| 					onClick={(e) => { | ||||
| 						e.preventDefault(); | ||||
| 						// onNew(); | ||||
| 						showHTTPCertificateModal(); | ||||
| 					}} | ||||
| 				> | ||||
| 					<T id="lets-encrypt" /> | ||||
| 					<T id="lets-encrypt-via-http" /> | ||||
| 				</a> | ||||
| 				<a | ||||
| 					className="dropdown-item" | ||||
| 					href="#" | ||||
| 					onClick={(e) => { | ||||
| 						e.preventDefault(); | ||||
| 						// onNewCustom(); | ||||
| 						showDNSCertificateModal(); | ||||
| 					}} | ||||
| 				> | ||||
| 					<T id="lets-encrypt-via-dns" /> | ||||
| 				</a> | ||||
| 				<div className="dropdown-divider" /> | ||||
| 				<a | ||||
| 					className="dropdown-item" | ||||
| 					href="#" | ||||
| 					onClick={(e) => { | ||||
| 						e.preventDefault(); | ||||
| 						showCustomCertificateModal(); | ||||
| 					}} | ||||
| 				> | ||||
| 					<T id="certificates.custom" /> | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import Alert from "react-bootstrap/Alert"; | ||||
| import { LoadingPage } from "src/components"; | ||||
| import { useCertificates } from "src/hooks"; | ||||
| import { T } from "src/locale"; | ||||
| import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals"; | ||||
| import Table from "./Table"; | ||||
|  | ||||
| export default function TableWrapper() { | ||||
| @@ -54,10 +55,35 @@ export default function TableWrapper() { | ||||
| 										<T id="object.add" tData={{ object: "certificate" }} /> | ||||
| 									</button> | ||||
| 									<div className="dropdown-menu"> | ||||
| 										<a className="dropdown-item" href="#"> | ||||
| 											<T id="lets-encrypt" /> | ||||
| 										<a | ||||
| 											className="dropdown-item" | ||||
| 											href="#" | ||||
| 											onClick={(e) => { | ||||
| 												e.preventDefault(); | ||||
| 												showHTTPCertificateModal(); | ||||
| 											}} | ||||
| 										> | ||||
| 											<T id="lets-encrypt-via-http" /> | ||||
| 										</a> | ||||
| 										<a className="dropdown-item" href="#"> | ||||
| 										<a | ||||
| 											className="dropdown-item" | ||||
| 											href="#" | ||||
| 											onClick={(e) => { | ||||
| 												e.preventDefault(); | ||||
| 												showDNSCertificateModal(); | ||||
| 											}} | ||||
| 										> | ||||
| 											<T id="lets-encrypt-via-dns" /> | ||||
| 										</a> | ||||
| 										<div className="dropdown-divider" /> | ||||
| 										<a | ||||
| 											className="dropdown-item" | ||||
| 											href="#" | ||||
| 											onClick={(e) => { | ||||
| 												e.preventDefault(); | ||||
| 												showCustomCertificateModal(); | ||||
| 											}} | ||||
| 										> | ||||
| 											<T id="certificates.custom" /> | ||||
| 										</a> | ||||
| 									</div> | ||||
|   | ||||
| @@ -118,15 +118,9 @@ const Dashboard = () => { | ||||
| - check mobile | ||||
| - add help docs for host types | ||||
| - REDO SCREENSHOTS in docs folder | ||||
| - translations for: | ||||
|   - src/components/Form/AccessField.tsx | ||||
|   - src/components/Form/SSLCertificateField.tsx | ||||
|   - src/components/Form/DNSProviderFields.tsx | ||||
| - search codebase for "TODO" | ||||
| - update documentation to add development notes for translations | ||||
| - use syntax highligting for audit logs json output | ||||
| - double check output of access field selection on proxy host dialog, after access lists are completed | ||||
| - proxy host custom locations dialog | ||||
| - check permissions in all places | ||||
|  | ||||
| More for api, then implement here: | ||||
|   | ||||
| @@ -37,13 +37,12 @@ export default function TableWrapper() { | ||||
|  | ||||
| 	let filtered = null; | ||||
| 	if (search && data) { | ||||
| 		filtered = data?.filter((_item) => { | ||||
| 			return true; | ||||
| 			// TODO | ||||
| 			// item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || | ||||
| 			// item.forwardDomainName.toLowerCase().includes(search) | ||||
| 			// ); | ||||
| 		}); | ||||
| 		filtered = data?.filter( | ||||
| 			(item) => | ||||
| 				item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || | ||||
| 				item.forwardHost.toLowerCase().includes(search) || | ||||
| 				`${item.forwardPort}`.includes(search), | ||||
| 		); | ||||
| 	} else if (search !== "") { | ||||
| 		// this can happen if someone deletes the last item while searching | ||||
| 		setSearch(""); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user