mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 15:53: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> { | export async function createCertificate(item: Certificate): Promise<Certificate> { | ||||||
| 	return await api.post({ | 	return await api.post({ | ||||||
| 		url: "/nginx/certificates", | 		url: "/nginx/certificates", | ||||||
| 		// todo: only use whitelist of fields for this data |  | ||||||
| 		data: item, | 		data: item, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import * as api from "./base"; | import * as api from "./base"; | ||||||
|  |  | ||||||
| export async function testHttpCertificate(domains: string[]): Promise<Record<string, string>> { | export async function testHttpCertificate(domains: string[]): Promise<Record<string, string>> { | ||||||
| 	return await api.get({ | 	return await api.post({ | ||||||
| 		url: "/nginx/certificates/test-http", | 		url: "/nginx/certificates/test-http", | ||||||
| 		params: { | 		data: { | ||||||
| 			domains: domains.join(","), | 			domains, | ||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id = | |||||||
| 	options?.unshift({ | 	options?.unshift({ | ||||||
| 		value: 0, | 		value: 0, | ||||||
| 		label: intl.formatMessage({ id: "access-list.public" }), | 		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" />, | 		icon: <IconLockOpen2 size={14} className="text-red" />, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
|  | import { IconAlertTriangle } from "@tabler/icons-react"; | ||||||
| import { Field, useFormikContext } from "formik"; | import { Field, useFormikContext } from "formik"; | ||||||
| import { useState } from "react"; | import { useState } from "react"; | ||||||
| import Select, { type ActionMeta } from "react-select"; | import Select, { type ActionMeta } from "react-select"; | ||||||
| import type { DNSProvider } from "src/api/backend"; | import type { DNSProvider } from "src/api/backend"; | ||||||
| import { useDnsProviders } from "src/hooks"; | import { useDnsProviders } from "src/hooks"; | ||||||
|  | import { T } from "src/locale"; | ||||||
| import styles from "./DNSProviderFields.module.css"; | import styles from "./DNSProviderFields.module.css"; | ||||||
|  |  | ||||||
| interface DNSProviderOption { | interface DNSProviderOption { | ||||||
| @@ -10,7 +12,11 @@ interface DNSProviderOption { | |||||||
| 	readonly label: string; | 	readonly label: string; | ||||||
| 	readonly credentials: string; | 	readonly credentials: string; | ||||||
| } | } | ||||||
| export function DNSProviderFields() { |  | ||||||
|  | interface Props { | ||||||
|  | 	showBoundaryBox?: boolean; | ||||||
|  | } | ||||||
|  | export function DNSProviderFields({ showBoundaryBox = false }: Props) { | ||||||
| 	const { values, setFieldValue } = useFormikContext(); | 	const { values, setFieldValue } = useFormikContext(); | ||||||
| 	const { data: dnsProviders, isLoading } = useDnsProviders(); | 	const { data: dnsProviders, isLoading } = useDnsProviders(); | ||||||
| 	const [dnsProviderId, setDnsProviderId] = useState<string | null>(null); | 	const [dnsProviderId, setDnsProviderId] = useState<string | null>(null); | ||||||
| @@ -31,17 +37,17 @@ export function DNSProviderFields() { | |||||||
| 		})) || []; | 		})) || []; | ||||||
|  |  | ||||||
| 	return ( | 	return ( | ||||||
| 		<div className={styles.dnsChallengeWarning}> | 		<div className={showBoundaryBox ? styles.dnsChallengeWarning : undefined}> | ||||||
| 			<p className="text-info"> | 			<p className="text-warning"> | ||||||
| 				This section requires some knowledge about Certbot and DNS plugins. Please consult the respective | 				<IconAlertTriangle size={16} className="me-1" /> | ||||||
| 				plugins documentation. | 				<T id="certificates.dns.warning" /> | ||||||
| 			</p> | 			</p> | ||||||
|  |  | ||||||
| 			<Field name="meta.dnsProvider"> | 			<Field name="meta.dnsProvider"> | ||||||
| 				{({ field }: any) => ( | 				{({ field }: any) => ( | ||||||
| 					<div className="row"> | 					<div className="row"> | ||||||
| 						<label htmlFor="dnsProvider" className="form-label"> | 						<label htmlFor="dnsProvider" className="form-label"> | ||||||
| 							DNS Provider | 							<T id="certificates.dns.provider" /> | ||||||
| 						</label> | 						</label> | ||||||
| 						<Select | 						<Select | ||||||
| 							className="react-select-container" | 							className="react-select-container" | ||||||
| @@ -66,7 +72,7 @@ export function DNSProviderFields() { | |||||||
| 						{({ field }: any) => ( | 						{({ field }: any) => ( | ||||||
| 							<div className="mt-3"> | 							<div className="mt-3"> | ||||||
| 								<label htmlFor="dnsProviderCredentials" className="form-label"> | 								<label htmlFor="dnsProviderCredentials" className="form-label"> | ||||||
| 									Credentials File Content | 									<T id="certificates.dns.credentials" /> | ||||||
| 								</label> | 								</label> | ||||||
| 								<textarea | 								<textarea | ||||||
| 									id="dnsProviderCredentials" | 									id="dnsProviderCredentials" | ||||||
| @@ -78,13 +84,12 @@ export function DNSProviderFields() { | |||||||
| 								/> | 								/> | ||||||
| 								<div> | 								<div> | ||||||
| 									<small className="text-muted"> | 									<small className="text-muted"> | ||||||
| 										This plugin requires a configuration file containing an API token or other | 										<T id="certificates.dns.credentials-note" /> | ||||||
| 										credentials to your provider |  | ||||||
| 									</small> | 									</small> | ||||||
| 								</div> | 								</div> | ||||||
| 								<div> | 								<div> | ||||||
| 									<small className="text-danger"> | 									<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> | 									</small> | ||||||
| 								</div> | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
| @@ -94,20 +99,18 @@ export function DNSProviderFields() { | |||||||
| 						{({ field }: any) => ( | 						{({ field }: any) => ( | ||||||
| 							<div className="mt-3"> | 							<div className="mt-3"> | ||||||
| 								<label htmlFor="propagationSeconds" className="form-label"> | 								<label htmlFor="propagationSeconds" className="form-label"> | ||||||
| 									Propagation Seconds | 									<T id="certificates.dns.propagation-seconds" /> | ||||||
| 								</label> | 								</label> | ||||||
| 								<input | 								<input | ||||||
| 									id="propagationSeconds" | 									id="propagationSeconds" | ||||||
| 									type="number" | 									type="number" | ||||||
| 									x |  | ||||||
| 									className="form-control" | 									className="form-control" | ||||||
| 									min={0} | 									min={0} | ||||||
| 									max={600} | 									max={600} | ||||||
| 									{...field} | 									{...field} | ||||||
| 								/> | 								/> | ||||||
| 								<small className="text-muted"> | 								<small className="text-muted"> | ||||||
| 									Leave empty to use the plugins default value. Number of seconds to wait for DNS | 									<T id="certificates.dns.propagation-seconds-note" /> | ||||||
| 									propagation. |  | ||||||
| 								</small> | 								</small> | ||||||
| 							</div> | 							</div> | ||||||
| 						)} | 						)} | ||||||
|   | |||||||
| @@ -18,14 +18,16 @@ interface Props { | |||||||
| 	dnsProviderWildcardSupported?: boolean; | 	dnsProviderWildcardSupported?: boolean; | ||||||
| 	name?: string; | 	name?: string; | ||||||
| 	label?: string; | 	label?: string; | ||||||
|  | 	onChange?: (domains: string[]) => void; | ||||||
| } | } | ||||||
| export function DomainNamesField({ | export function DomainNamesField({ | ||||||
| 	name = "domainNames", | 	name = "domainNames", | ||||||
| 	label = "domain-names", | 	label = "domain-names", | ||||||
| 	id = "domainNames", | 	id = "domainNames", | ||||||
| 	maxDomains, | 	maxDomains, | ||||||
| 	isWildcardPermitted = true, | 	isWildcardPermitted = false, | ||||||
| 	dnsProviderWildcardSupported = true, | 	dnsProviderWildcardSupported = false, | ||||||
|  | 	onChange, | ||||||
| }: Props) { | }: Props) { | ||||||
| 	const { setFieldValue } = useFormikContext(); | 	const { setFieldValue } = useFormikContext(); | ||||||
|  |  | ||||||
| @@ -34,6 +36,7 @@ export function DomainNamesField({ | |||||||
| 			return i.value; | 			return i.value; | ||||||
| 		}); | 		}); | ||||||
| 		setFieldValue(name, doms); | 		setFieldValue(name, doms); | ||||||
|  | 		onChange?.(doms); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	const helperTexts: ReactNode[] = []; | 	const helperTexts: ReactNode[] = []; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ interface Props { | |||||||
| 	initialValues: ProxyLocation[]; | 	initialValues: ProxyLocation[]; | ||||||
| 	name?: string; | 	name?: string; | ||||||
| } | } | ||||||
| export function LocationsFields({ initialValues, name = "items" }: Props) { | export function LocationsFields({ initialValues, name = "locations" }: Props) { | ||||||
| 	const [values, setValues] = useState<ProxyLocation[]>(initialValues || []); | 	const [values, setValues] = useState<ProxyLocation[]>(initialValues || []); | ||||||
| 	const { setFieldValue } = useFormikContext(); | 	const { setFieldValue } = useFormikContext(); | ||||||
| 	const [advVisible, setAdvVisible] = useState<number[]>([]); | 	const [advVisible, setAdvVisible] = useState<number[]>([]); | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ export function NginxConfigField({ | |||||||
| 							fontFamily: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace", | 							fontFamily: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace", | ||||||
| 							borderRadius: "0.3rem", | 							borderRadius: "0.3rem", | ||||||
| 							minHeight: "200px", | 							minHeight: "200px", | ||||||
|  | 							backgroundColor: "var(--tblr-bg-surface-dark)", | ||||||
| 						}} | 						}} | ||||||
| 						{...field} | 						{...field} | ||||||
| 					/> | 					/> | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import { Field, useFormikContext } from "formik"; | |||||||
| import Select, { type ActionMeta, components, type OptionProps } from "react-select"; | import Select, { type ActionMeta, components, type OptionProps } from "react-select"; | ||||||
| import type { Certificate } from "src/api/backend"; | import type { Certificate } from "src/api/backend"; | ||||||
| import { useCertificates } from "src/hooks"; | import { useCertificates } from "src/hooks"; | ||||||
| import { DateTimeFormat, T } from "src/locale"; | import { DateTimeFormat, intl, T } from "src/locale"; | ||||||
|  |  | ||||||
| interface CertOption { | interface CertOption { | ||||||
| 	readonly value: number | "new"; | 	readonly value: number | "new"; | ||||||
| @@ -75,9 +75,7 @@ export function SSLCertificateField({ | |||||||
| 		data?.map((cert: Certificate) => ({ | 		data?.map((cert: Certificate) => ({ | ||||||
| 			value: cert.id, | 			value: cert.id, | ||||||
| 			label: cert.niceName, | 			label: cert.niceName, | ||||||
| 			subLabel: `${cert.provider === "letsencrypt" ? "Let's Encrypt" : cert.provider} — Expires: ${ | 			subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" })}`, | ||||||
| 				cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" |  | ||||||
| 			}`, |  | ||||||
| 			icon: <IconShield size={14} className="text-pink" />, | 			icon: <IconShield size={14} className="text-pink" />, | ||||||
| 		})) || []; | 		})) || []; | ||||||
|  |  | ||||||
| @@ -85,8 +83,8 @@ export function SSLCertificateField({ | |||||||
| 	if (allowNew) { | 	if (allowNew) { | ||||||
| 		options?.unshift({ | 		options?.unshift({ | ||||||
| 			value: "new", | 			value: "new", | ||||||
| 			label: "Request a new Certificate", | 			label: intl.formatMessage({ id: "certificates.request.title" }), | ||||||
| 			subLabel: "with Let's Encrypt", | 			subLabel: intl.formatMessage({ id: "certificates.request.subtitle" }), | ||||||
| 			icon: <IconShield size={14} className="text-lime" />, | 			icon: <IconShield size={14} className="text-lime" />, | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| @@ -95,8 +93,10 @@ export function SSLCertificateField({ | |||||||
| 	if (!required) { | 	if (!required) { | ||||||
| 		options?.unshift({ | 		options?.unshift({ | ||||||
| 			value: 0, | 			value: 0, | ||||||
| 			label: "None", | 			label: intl.formatMessage({ id: "certificate.none.title" }), | ||||||
| 			subLabel: forHttp ? "This host will not use HTTPS" : "No certificate assigned", | 			subLabel: forHttp | ||||||
|  | 				? intl.formatMessage({ id: "certificate.none.subtitle.for-http" }) | ||||||
|  | 				: intl.formatMessage({ id: "certificate.none.subtitle" }), | ||||||
| 			icon: <IconShield size={14} className="text-red" />, | 			icon: <IconShield size={14} className="text-red" />, | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -136,8 +136,8 @@ export function SSLOptionsFields({ forHttp = true, forceDNSForNew, requireDomain | |||||||
| 							</label> | 							</label> | ||||||
| 						)} | 						)} | ||||||
| 					</Field> | 					</Field> | ||||||
| 					{requireDomainNames ? <DomainNamesField /> : null} | 					{requireDomainNames ? <DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> : null} | ||||||
| 					{dnsChallenge ? <DNSProviderFields /> : null} | 					{dnsChallenge ? <DNSProviderFields showBoundaryBox /> : null} | ||||||
| 				</> | 				</> | ||||||
| 			) : null} | 			) : null} | ||||||
| 		</div> | 		</div> | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ export function EventFormatter({ row }: Props) { | |||||||
| 		<div className="flex-fill"> | 		<div className="flex-fill"> | ||||||
| 			<div className="font-weight-medium"> | 			<div className="font-weight-medium"> | ||||||
| 				{getIcon(row)} | 				{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> | 				— <span className="badge">{getEventValue(row)}</span> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div className="text-secondary mt-1">{DateTimeFormat(row.createdOn)}</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.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.pass-auth": "Pass Auth to Upstream", | ||||||
|   "access-list.public": "Publicly Accessible", |   "access-list.public": "Publicly Accessible", | ||||||
|  |   "access-list.public.subtitle": "No basic auth required", | ||||||
|   "access-list.satisfy-any": "Satisfy Any", |   "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-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}", | ||||||
|   "access-lists": "Access Lists", |   "access-lists": "Access Lists", | ||||||
| @@ -21,8 +22,28 @@ | |||||||
|   "auditlogs": "Audit Logs", |   "auditlogs": "Audit Logs", | ||||||
|   "cancel": "Cancel", |   "cancel": "Cancel", | ||||||
|   "certificate": "Certificate", |   "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": "Certificates", | ||||||
|   "certificates.custom": "Custom Certificate", |   "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.access": "Access", | ||||||
|   "column.authorization": "Authorization", |   "column.authorization": "Authorization", | ||||||
|   "column.authorizations": "Authorizations", |   "column.authorizations": "Authorizations", | ||||||
| @@ -74,22 +95,7 @@ | |||||||
|   "error.max-domains": "Too many domains, max is {max}", |   "error.max-domains": "Too many domains, max is {max}", | ||||||
|   "error.passwords-must-match": "Passwords must match", |   "error.passwords-must-match": "Passwords must match", | ||||||
|   "error.required": "This is required", |   "error.required": "This is required", | ||||||
|   "event.created-access-list": "Created Access List", |   "expires.on": "Expires: {date}", | ||||||
|   "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", |  | ||||||
|   "footer.github-fork": "Fork me on Github", |   "footer.github-fork": "Fork me on Github", | ||||||
|   "host.flags.block-exploits": "Block Common Exploits", |   "host.flags.block-exploits": "Block Common Exploits", | ||||||
|   "host.flags.cache-assets": "Cache Assets", |   "host.flags.cache-assets": "Cache Assets", | ||||||
| @@ -101,6 +107,8 @@ | |||||||
|   "hosts": "Hosts", |   "hosts": "Hosts", | ||||||
|   "http-only": "HTTP Only", |   "http-only": "HTTP Only", | ||||||
|   "lets-encrypt": "Let's Encrypt", |   "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…", |   "loading": "Loading…", | ||||||
|   "login.title": "Login to your account", |   "login.title": "Login to your account", | ||||||
|   "nginx-config.label": "Custom Nginx Configuration", |   "nginx-config.label": "Custom Nginx Configuration", | ||||||
| @@ -121,6 +129,11 @@ | |||||||
|   "object.delete.content": "Are you sure you want to delete this {object}?", |   "object.delete.content": "Are you sure you want to delete this {object}?", | ||||||
|   "object.edit": "Edit {object}", |   "object.edit": "Edit {object}", | ||||||
|   "object.empty": "There are no {objects}", |   "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", |   "offline": "Offline", | ||||||
|   "online": "Online", |   "online": "Online", | ||||||
|   "options": "Options", |   "options": "Options", | ||||||
| @@ -158,6 +171,7 @@ | |||||||
|   "streams.count": "{count} {count, plural, one {Stream} other {Streams}}", |   "streams.count": "{count} {count, plural, one {Stream} other {Streams}}", | ||||||
|   "streams.tcp": "TCP", |   "streams.tcp": "TCP", | ||||||
|   "streams.udp": "UDP", |   "streams.udp": "UDP", | ||||||
|  |   "test": "Test", | ||||||
|   "user": "User", |   "user": "User", | ||||||
|   "user.change-password": "Change Password", |   "user.change-password": "Change Password", | ||||||
|   "user.confirm-password": "Confirm Password", |   "user.confirm-password": "Confirm Password", | ||||||
|   | |||||||
| @@ -20,6 +20,9 @@ | |||||||
| 	"access-list.public": { | 	"access-list.public": { | ||||||
| 		"defaultMessage": "Publicly Accessible" | 		"defaultMessage": "Publicly Accessible" | ||||||
| 	}, | 	}, | ||||||
|  | 	"access-list.public.subtitle": { | ||||||
|  | 		"defaultMessage": "No basic auth required" | ||||||
|  | 	}, | ||||||
| 	"access-list.satisfy-any": { | 	"access-list.satisfy-any": { | ||||||
| 		"defaultMessage": "Satisfy Any" | 		"defaultMessage": "Satisfy Any" | ||||||
| 	}, | 	}, | ||||||
| @@ -65,12 +68,72 @@ | |||||||
| 	"certificate": { | 	"certificate": { | ||||||
| 		"defaultMessage": "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": { | 	"certificates": { | ||||||
| 		"defaultMessage": "Certificates" | 		"defaultMessage": "Certificates" | ||||||
| 	}, | 	}, | ||||||
| 	"certificates.custom": { | 	"certificates.custom": { | ||||||
| 		"defaultMessage": "Custom Certificate" | 		"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": { | 	"column.access": { | ||||||
| 		"defaultMessage": "Access" | 		"defaultMessage": "Access" | ||||||
| 	}, | 	}, | ||||||
| @@ -224,53 +287,8 @@ | |||||||
| 	"error.required": { | 	"error.required": { | ||||||
| 		"defaultMessage": "This is required" | 		"defaultMessage": "This is required" | ||||||
| 	}, | 	}, | ||||||
| 	"event.created-access-list": { | 	"expires.on": { | ||||||
| 		"defaultMessage": "Created Access List" | 		"defaultMessage": "Expires: {date}" | ||||||
| 	}, |  | ||||||
| 	"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" |  | ||||||
| 	}, | 	}, | ||||||
| 	"footer.github-fork": { | 	"footer.github-fork": { | ||||||
| 		"defaultMessage": "Fork me on Github" | 		"defaultMessage": "Fork me on Github" | ||||||
| @@ -305,6 +323,12 @@ | |||||||
| 	"lets-encrypt": { | 	"lets-encrypt": { | ||||||
| 		"defaultMessage": "Let's 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": { | 	"loading": { | ||||||
| 		"defaultMessage": "Loading…" | 		"defaultMessage": "Loading…" | ||||||
| 	}, | 	}, | ||||||
| @@ -365,6 +389,21 @@ | |||||||
| 	"object.empty": { | 	"object.empty": { | ||||||
| 		"defaultMessage": "There are no {objects}" | 		"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": { | 	"offline": { | ||||||
| 		"defaultMessage": "Offline" | 		"defaultMessage": "Offline" | ||||||
| 	}, | 	}, | ||||||
| @@ -476,6 +515,9 @@ | |||||||
| 	"streams.udp": { | 	"streams.udp": { | ||||||
| 		"defaultMessage": "UDP" | 		"defaultMessage": "UDP" | ||||||
| 	}, | 	}, | ||||||
|  | 	"test": { | ||||||
|  | 		"defaultMessage": "Test" | ||||||
|  | 	}, | ||||||
| 	"user": { | 	"user": { | ||||||
| 		"defaultMessage": "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="card-body"> | ||||||
| 										<div className="tab-content"> | 										<div className="tab-content"> | ||||||
| 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | ||||||
| 												<DomainNamesField isWildcardPermitted /> | 												<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||||
| 											</div> | 											</div> | ||||||
| 											<div className="tab-pane" id="tab-ssl" role="tabpanel"> | 											<div className="tab-pane" id="tab-ssl" role="tabpanel"> | ||||||
| 												<SSLCertificateField | 												<SSLCertificateField | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import CodeEditor from "@uiw/react-textarea-code-editor"; | ||||||
| import EasyModal, { type InnerModalProps } from "ez-modal-react"; | import EasyModal, { type InnerModalProps } from "ez-modal-react"; | ||||||
| import { Alert } from "react-bootstrap"; | import { Alert } from "react-bootstrap"; | ||||||
| import Modal from "react-bootstrap/Modal"; | import Modal from "react-bootstrap/Modal"; | ||||||
| @@ -39,9 +40,22 @@ const EventDetailsModal = EasyModal.create(({ id, visible, remove }: Props) => { | |||||||
| 								<EventFormatter row={data} /> | 								<EventFormatter row={data} /> | ||||||
| 							</div> | 							</div> | ||||||
| 							<hr className="mt-4 mb-3" /> | 							<hr className="mt-4 mb-3" /> | ||||||
| 							<pre> | 							<CodeEditor | ||||||
| 								<code>{JSON.stringify(data.meta, null, 2)}</code> | 								language="json" | ||||||
| 							</pre> | 								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> | 						</div> | ||||||
| 					</Modal.Body> | 					</Modal.Body> | ||||||
| 					<Modal.Footer> | 					<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="card-body"> | ||||||
| 										<div className="tab-content"> | 										<div className="tab-content"> | ||||||
| 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | ||||||
| 												<DomainNamesField isWildcardPermitted /> | 												<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||||
| 												<div className="row"> | 												<div className="row"> | ||||||
| 													<div className="col-md-3"> | 													<div className="col-md-3"> | ||||||
| 														<Field name="forwardScheme"> | 														<Field name="forwardScheme"> | ||||||
|   | |||||||
| @@ -144,7 +144,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = | |||||||
| 									<div className="card-body"> | 									<div className="card-body"> | ||||||
| 										<div className="tab-content"> | 										<div className="tab-content"> | ||||||
| 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | 											<div className="tab-pane active show" id="tab-details" role="tabpanel"> | ||||||
| 												<DomainNamesField isWildcardPermitted /> | 												<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> | ||||||
| 												<div className="row"> | 												<div className="row"> | ||||||
| 													<div className="col-md-4"> | 													<div className="col-md-4"> | ||||||
| 														<Field name="forwardScheme"> | 														<Field name="forwardScheme"> | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
| export * from "./AccessListModal"; | export * from "./AccessListModal"; | ||||||
| export * from "./ChangePasswordModal"; | export * from "./ChangePasswordModal"; | ||||||
|  | export * from "./CustomCertificateModal"; | ||||||
| export * from "./DeadHostModal"; | export * from "./DeadHostModal"; | ||||||
| export * from "./DeleteConfirmModal"; | export * from "./DeleteConfirmModal"; | ||||||
|  | export * from "./DNSCertificateModal"; | ||||||
| export * from "./EventDetailsModal"; | export * from "./EventDetailsModal"; | ||||||
|  | export * from "./HTTPCertificateModal"; | ||||||
| export * from "./PermissionsModal"; | export * from "./PermissionsModal"; | ||||||
| export * from "./ProxyHostModal"; | export * from "./ProxyHostModal"; | ||||||
| export * from "./RedirectionHostModal"; | export * from "./RedirectionHostModal"; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import type { Certificate } from "src/api/backend"; | |||||||
| import { DomainsFormatter, EmptyData, GravatarFormatter } from "src/components"; | import { DomainsFormatter, EmptyData, GravatarFormatter } from "src/components"; | ||||||
| import { TableLayout } from "src/components/Table/TableLayout"; | import { TableLayout } from "src/components/Table/TableLayout"; | ||||||
| import { intl, T } from "src/locale"; | import { intl, T } from "src/locale"; | ||||||
|  | import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals"; | ||||||
|  |  | ||||||
| interface Props { | interface Props { | ||||||
| 	data: Certificate[]; | 	data: Certificate[]; | ||||||
| @@ -121,17 +122,28 @@ export default function Table({ data, isFetching }: Props) { | |||||||
| 					href="#" | 					href="#" | ||||||
| 					onClick={(e) => { | 					onClick={(e) => { | ||||||
| 						e.preventDefault(); | 						e.preventDefault(); | ||||||
| 						// onNew(); | 						showHTTPCertificateModal(); | ||||||
| 					}} | 					}} | ||||||
| 				> | 				> | ||||||
| 					<T id="lets-encrypt" /> | 					<T id="lets-encrypt-via-http" /> | ||||||
| 				</a> | 				</a> | ||||||
| 				<a | 				<a | ||||||
| 					className="dropdown-item" | 					className="dropdown-item" | ||||||
| 					href="#" | 					href="#" | ||||||
| 					onClick={(e) => { | 					onClick={(e) => { | ||||||
| 						e.preventDefault(); | 						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" /> | 					<T id="certificates.custom" /> | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import Alert from "react-bootstrap/Alert"; | |||||||
| import { LoadingPage } from "src/components"; | import { LoadingPage } from "src/components"; | ||||||
| import { useCertificates } from "src/hooks"; | import { useCertificates } from "src/hooks"; | ||||||
| import { T } from "src/locale"; | import { T } from "src/locale"; | ||||||
|  | import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals"; | ||||||
| import Table from "./Table"; | import Table from "./Table"; | ||||||
|  |  | ||||||
| export default function TableWrapper() { | export default function TableWrapper() { | ||||||
| @@ -54,10 +55,35 @@ export default function TableWrapper() { | |||||||
| 										<T id="object.add" tData={{ object: "certificate" }} /> | 										<T id="object.add" tData={{ object: "certificate" }} /> | ||||||
| 									</button> | 									</button> | ||||||
| 									<div className="dropdown-menu"> | 									<div className="dropdown-menu"> | ||||||
| 										<a className="dropdown-item" href="#"> | 										<a | ||||||
| 											<T id="lets-encrypt" /> | 											className="dropdown-item" | ||||||
|  | 											href="#" | ||||||
|  | 											onClick={(e) => { | ||||||
|  | 												e.preventDefault(); | ||||||
|  | 												showHTTPCertificateModal(); | ||||||
|  | 											}} | ||||||
|  | 										> | ||||||
|  | 											<T id="lets-encrypt-via-http" /> | ||||||
| 										</a> | 										</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" /> | 											<T id="certificates.custom" /> | ||||||
| 										</a> | 										</a> | ||||||
| 									</div> | 									</div> | ||||||
|   | |||||||
| @@ -118,15 +118,9 @@ const Dashboard = () => { | |||||||
| - check mobile | - check mobile | ||||||
| - add help docs for host types | - add help docs for host types | ||||||
| - REDO SCREENSHOTS in docs folder | - 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" | - search codebase for "TODO" | ||||||
| - update documentation to add development notes for translations | - 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 | - 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 | - check permissions in all places | ||||||
|  |  | ||||||
| More for api, then implement here: | More for api, then implement here: | ||||||
|   | |||||||
| @@ -37,13 +37,12 @@ export default function TableWrapper() { | |||||||
|  |  | ||||||
| 	let filtered = null; | 	let filtered = null; | ||||||
| 	if (search && data) { | 	if (search && data) { | ||||||
| 		filtered = data?.filter((_item) => { | 		filtered = data?.filter( | ||||||
| 			return true; | 			(item) => | ||||||
| 			// TODO | 				item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || | ||||||
| 			// item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || | 				item.forwardHost.toLowerCase().includes(search) || | ||||||
| 			// item.forwardDomainName.toLowerCase().includes(search) | 				`${item.forwardPort}`.includes(search), | ||||||
| 			// ); | 		); | ||||||
| 		}); |  | ||||||
| 	} else if (search !== "") { | 	} else if (search !== "") { | ||||||
| 		// this can happen if someone deletes the last item while searching | 		// this can happen if someone deletes the last item while searching | ||||||
| 		setSearch(""); | 		setSearch(""); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user