mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-11-04 17:35:15 +00:00
404 hosts add update complete, fix certbot renewals
and remove the need for email and agreement on cert requests
This commit is contained in:
@@ -1,16 +1,8 @@
|
||||
.dnsChallengeWarning {
|
||||
border: 1px solid #fecaca; /* Tailwind's red-300 */
|
||||
border: 1px solid var(--tblr-orange-lt);
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem; /* Tailwind's rounded-md */
|
||||
border-radius: 0.375rem;
|
||||
margin-top: 1rem;
|
||||
background-color: var(--tblr-cyan-lt);
|
||||
}
|
||||
|
||||
.textareaMono {
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
/* background-color: #f9fafb;
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
width: 100%; */
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import cn from "classnames";
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import { useState } from "react";
|
||||
import Select, { type ActionMeta } from "react-select";
|
||||
@@ -20,8 +19,8 @@ export function DNSProviderFields() {
|
||||
const v: any = values || {};
|
||||
|
||||
const handleChange = (newValue: any, _actionMeta: ActionMeta<DNSProviderOption>) => {
|
||||
setFieldValue("dnsProvider", newValue?.value);
|
||||
setFieldValue("dnsProviderCredentials", newValue?.credentials);
|
||||
setFieldValue("meta.dnsProvider", newValue?.value);
|
||||
setFieldValue("meta.dnsProviderCredentials", newValue?.credentials);
|
||||
setDnsProviderId(newValue?.value);
|
||||
};
|
||||
|
||||
@@ -34,12 +33,12 @@ export function DNSProviderFields() {
|
||||
|
||||
return (
|
||||
<div className={styles.dnsChallengeWarning}>
|
||||
<p className="text-danger">
|
||||
This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective
|
||||
<p className="text-info">
|
||||
This section requires some knowledge about Certbot and DNS plugins. Please consult the respective
|
||||
plugins documentation.
|
||||
</p>
|
||||
|
||||
<Field name="dnsProvider">
|
||||
<Field name="meta.dnsProvider">
|
||||
{({ field }: any) => (
|
||||
<div className="row">
|
||||
<label htmlFor="dnsProvider" className="form-label">
|
||||
@@ -64,33 +63,37 @@ export function DNSProviderFields() {
|
||||
|
||||
{dnsProviderId ? (
|
||||
<>
|
||||
<Field name="dnsProviderCredentials">
|
||||
<Field name="meta.dnsProviderCredentials">
|
||||
{({ field }: any) => (
|
||||
<div className="row mt-3">
|
||||
<div className="mt-3">
|
||||
<label htmlFor="dnsProviderCredentials" className="form-label">
|
||||
Credentials File Content
|
||||
</label>
|
||||
<textarea
|
||||
id="dnsProviderCredentials"
|
||||
className={cn("form-control", styles.textareaMono)}
|
||||
className="form-control textareaMono"
|
||||
rows={3}
|
||||
spellCheck={false}
|
||||
value={v.dnsProviderCredentials || ""}
|
||||
value={v.meta.dnsProviderCredentials || ""}
|
||||
{...field}
|
||||
/>
|
||||
<small className="text-muted">
|
||||
This plugin requires a configuration file containing an API token or other
|
||||
credentials to your provider
|
||||
</small>
|
||||
<small className="text-danger">
|
||||
This data will be stored as plaintext in the database and in a file!
|
||||
</small>
|
||||
<div>
|
||||
<small className="text-muted">
|
||||
This plugin requires a configuration file containing an API token or other
|
||||
credentials to your provider
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<small className="text-danger">
|
||||
This data will be stored as plaintext in the database and in a file!
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="propagationSeconds">
|
||||
<Field name="meta.propagationSeconds">
|
||||
{({ field }: any) => (
|
||||
<div className="row mt-3">
|
||||
<div className="mt-3">
|
||||
<label htmlFor="propagationSeconds" className="form-label">
|
||||
Propagation Seconds
|
||||
</label>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Field, useFormikContext } from "formik";
|
||||
import type { ActionMeta, MultiValue } from "react-select";
|
||||
import CreatableSelect from "react-select/creatable";
|
||||
import { intl } from "src/locale";
|
||||
import { validateDomain, validateDomains } from "src/modules/Validations";
|
||||
|
||||
export type SelectOption = {
|
||||
label: string;
|
||||
@@ -22,17 +23,10 @@ export function DomainNamesField({
|
||||
label = "domain-names",
|
||||
id = "domainNames",
|
||||
maxDomains,
|
||||
isWildcardPermitted,
|
||||
dnsProviderWildcardSupported,
|
||||
isWildcardPermitted = true,
|
||||
dnsProviderWildcardSupported = true,
|
||||
}: Props) {
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
|
||||
const getDomainCount = (v: string[] | undefined): number => {
|
||||
if (v?.length) {
|
||||
return v.length;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
const handleChange = (v: MultiValue<SelectOption>, _actionMeta: ActionMeta<SelectOption>) => {
|
||||
const doms = v?.map((i: SelectOption) => {
|
||||
@@ -41,50 +35,18 @@ export function DomainNamesField({
|
||||
setFieldValue(name, doms);
|
||||
};
|
||||
|
||||
const isDomainValid = (d: string): boolean => {
|
||||
const dom = d.trim().toLowerCase();
|
||||
const v: any = values;
|
||||
|
||||
// Deny if the list of domains is hit
|
||||
if (maxDomains && getDomainCount(v?.[name]) >= maxDomains) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dom.length < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent wildcards
|
||||
if ((!isWildcardPermitted || !dnsProviderWildcardSupported) && dom.indexOf("*") !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent duplicate * in domain
|
||||
if ((dom.match(/\*/g) || []).length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent some invalid characters
|
||||
if ((dom.match(/(@|,|!|&|\$|#|%|\^|\(|\))/g) || []).length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This will match *.com type domains,
|
||||
return dom.match(/\*\.[^.]+$/m) === null;
|
||||
};
|
||||
|
||||
const helperTexts: string[] = [];
|
||||
if (maxDomains) {
|
||||
helperTexts.push(intl.formatMessage({ id: "domain_names.max" }, { count: maxDomains }));
|
||||
helperTexts.push(intl.formatMessage({ id: "domain-names.max" }, { count: maxDomains }));
|
||||
}
|
||||
if (!isWildcardPermitted) {
|
||||
helperTexts.push(intl.formatMessage({ id: "wildcards-not-permitted" }));
|
||||
helperTexts.push(intl.formatMessage({ id: "domain-names.wildcards-not-permitted" }));
|
||||
} else if (!dnsProviderWildcardSupported) {
|
||||
helperTexts.push(intl.formatMessage({ id: "wildcards-not-supported" }));
|
||||
helperTexts.push(intl.formatMessage({ id: "domain-names.wildcards-not-supported" }));
|
||||
}
|
||||
|
||||
return (
|
||||
<Field name={name}>
|
||||
<Field name={name} validate={validateDomains(isWildcardPermitted && dnsProviderWildcardSupported, maxDomains)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label className="form-label" htmlFor={id}>
|
||||
@@ -97,21 +59,19 @@ export function DomainNamesField({
|
||||
id={id}
|
||||
closeMenuOnSelect={true}
|
||||
isClearable={false}
|
||||
isValidNewOption={isDomainValid}
|
||||
isValidNewOption={validateDomain(isWildcardPermitted && dnsProviderWildcardSupported)}
|
||||
isMulti
|
||||
placeholder="Start typing to add domain..."
|
||||
placeholder={intl.formatMessage({ id: "domain-names.placeholder" })}
|
||||
onChange={handleChange}
|
||||
value={field.value?.map((d: string) => ({ label: d, value: d }))}
|
||||
/>
|
||||
{form.errors[field.name] ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
|
||||
</div>
|
||||
{form.errors[field.name] && form.touched[field.name] ? (
|
||||
<small className="text-danger">{form.errors[field.name]}</small>
|
||||
) : helperTexts.length ? (
|
||||
helperTexts.map((i) => (
|
||||
<div key={i} className="invalid-feedback text-info">
|
||||
<small key={i} className="text-info">
|
||||
{i}
|
||||
</div>
|
||||
</small>
|
||||
))
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
40
frontend/src/components/Form/NginxConfigField.tsx
Normal file
40
frontend/src/components/Form/NginxConfigField.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import CodeEditor from "@uiw/react-textarea-code-editor";
|
||||
import { Field } from "formik";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
}
|
||||
export function NginxConfigField({
|
||||
name = "advancedConfig",
|
||||
label = "nginx-config.label",
|
||||
id = "advancedConfig",
|
||||
}: Props) {
|
||||
return (
|
||||
<Field name={name}>
|
||||
{({ field }: any) => (
|
||||
<div className="mt-3">
|
||||
<label htmlFor={id} className="form-label">
|
||||
{intl.formatMessage({ id: label })}
|
||||
</label>
|
||||
<CodeEditor
|
||||
language="nginx"
|
||||
placeholder={intl.formatMessage({ id: "nginx-config.placeholder" })}
|
||||
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",
|
||||
}}
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { IconShield } from "@tabler/icons-react";
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
|
||||
import type { Certificate } from "src/api/backend";
|
||||
import { useCertificates, useUser } from "src/hooks";
|
||||
import { useCertificates } from "src/hooks";
|
||||
import { DateTimeFormat, intl } from "src/locale";
|
||||
|
||||
interface CertOption {
|
||||
@@ -39,26 +39,33 @@ export function SSLCertificateField({
|
||||
required,
|
||||
allowNew,
|
||||
}: Props) {
|
||||
const { data: currentUser } = useUser("me");
|
||||
const { isLoading, isError, error, data } = useCertificates();
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
const v: any = values || {};
|
||||
|
||||
const handleChange = (newValue: any, _actionMeta: ActionMeta<CertOption>) => {
|
||||
setFieldValue(name, newValue?.value);
|
||||
const { sslForced, http2Support, hstsEnabled, hstsSubdomains, dnsChallenge, letsencryptEmail } = v;
|
||||
const {
|
||||
sslForced,
|
||||
http2Support,
|
||||
hstsEnabled,
|
||||
hstsSubdomains,
|
||||
dnsChallenge,
|
||||
dnsProvider,
|
||||
dnsProviderCredentials,
|
||||
propagationSeconds,
|
||||
} = v;
|
||||
if (!newValue?.value) {
|
||||
sslForced && setFieldValue("sslForced", false);
|
||||
http2Support && setFieldValue("http2Support", false);
|
||||
hstsEnabled && setFieldValue("hstsEnabled", false);
|
||||
hstsSubdomains && setFieldValue("hstsSubdomains", false);
|
||||
}
|
||||
if (newValue?.value === "new") {
|
||||
if (!letsencryptEmail) {
|
||||
setFieldValue("letsencryptEmail", currentUser?.email);
|
||||
}
|
||||
} else {
|
||||
dnsChallenge && setFieldValue("dnsChallenge", false);
|
||||
if (newValue?.value !== "new") {
|
||||
dnsChallenge && setFieldValue("dnsChallenge", undefined);
|
||||
dnsProvider && setFieldValue("dnsProvider", undefined);
|
||||
dnsProviderCredentials && setFieldValue("dnsProviderCredentials", undefined);
|
||||
propagationSeconds && setFieldValue("propagationSeconds", undefined);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,7 +112,7 @@ export function SSLCertificateField({
|
||||
<Select
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
defaultValue={options[0]}
|
||||
defaultValue={options.find((o) => o.value === field.value) || options[0]}
|
||||
options={options}
|
||||
components={{ Option }}
|
||||
styles={{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import cn from "classnames";
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import { DNSProviderFields } from "src/components";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
export function SSLOptionsFields() {
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
@@ -8,10 +9,16 @@ export function SSLOptionsFields() {
|
||||
|
||||
const newCertificate = v?.certificateId === "new";
|
||||
const hasCertificate = newCertificate || (v?.certificateId && v?.certificateId > 0);
|
||||
const { sslForced, http2Support, hstsEnabled, hstsSubdomains, dnsChallenge } = v;
|
||||
const { sslForced, http2Support, hstsEnabled, hstsSubdomains, meta } = v;
|
||||
const { dnsChallenge } = meta || {};
|
||||
|
||||
const handleToggleChange = (e: any, fieldName: string) => {
|
||||
setFieldValue(fieldName, e.target.checked);
|
||||
if (fieldName === "meta.dnsChallenge" && !e.target.checked) {
|
||||
setFieldValue("meta.dnsProvider", undefined);
|
||||
setFieldValue("meta.dnsProviderCredentials", undefined);
|
||||
setFieldValue("meta.propagationSeconds", undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleClasses = "form-check-input";
|
||||
@@ -31,7 +38,9 @@ export function SSLOptionsFields() {
|
||||
onChange={(e) => handleToggleChange(e, field.name)}
|
||||
disabled={!hasCertificate}
|
||||
/>
|
||||
<span className="form-check-label">Force SSL</span>
|
||||
<span className="form-check-label">
|
||||
{intl.formatMessage({ id: "domains.force-ssl" })}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
@@ -47,7 +56,9 @@ export function SSLOptionsFields() {
|
||||
onChange={(e) => handleToggleChange(e, field.name)}
|
||||
disabled={!hasCertificate}
|
||||
/>
|
||||
<span className="form-check-label">HTTP/2 Support</span>
|
||||
<span className="form-check-label">
|
||||
{intl.formatMessage({ id: "domains.http2-support" })}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
@@ -65,7 +76,9 @@ export function SSLOptionsFields() {
|
||||
onChange={(e) => handleToggleChange(e, field.name)}
|
||||
disabled={!hasCertificate || !sslForced}
|
||||
/>
|
||||
<span className="form-check-label">HSTS Enabled</span>
|
||||
<span className="form-check-label">
|
||||
{intl.formatMessage({ id: "domains.hsts-enabled" })}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
@@ -81,7 +94,9 @@ export function SSLOptionsFields() {
|
||||
onChange={(e) => handleToggleChange(e, field.name)}
|
||||
disabled={!hasCertificate || !hstsEnabled}
|
||||
/>
|
||||
<span className="form-check-label">HSTS Enabled</span>
|
||||
<span className="form-check-label">
|
||||
{intl.formatMessage({ id: "domains.hsts-subdomains" })}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
@@ -89,7 +104,7 @@ export function SSLOptionsFields() {
|
||||
</div>
|
||||
{newCertificate ? (
|
||||
<>
|
||||
<Field name="dnsChallenge">
|
||||
<Field name="meta.dnsChallenge">
|
||||
{({ field }: any) => (
|
||||
<label className="form-check form-switch mt-1">
|
||||
<input
|
||||
@@ -98,29 +113,14 @@ export function SSLOptionsFields() {
|
||||
checked={!!dnsChallenge}
|
||||
onChange={(e) => handleToggleChange(e, field.name)}
|
||||
/>
|
||||
<span className="form-check-label">Use a DNS Challenge</span>
|
||||
<span className="form-check-label">
|
||||
{intl.formatMessage({ id: "domains.use-dns" })}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{dnsChallenge ? <DNSProviderFields /> : null}
|
||||
|
||||
<Field name="letsencryptEmail">
|
||||
{({ field }: any) => (
|
||||
<div className="mt-5">
|
||||
<label htmlFor="letsencryptEmail" className="form-label">
|
||||
Email Address for Let's Encrypt
|
||||
</label>
|
||||
<input
|
||||
id="letsencryptEmail"
|
||||
type="email"
|
||||
className="form-control"
|
||||
required
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./DNSProviderFields";
|
||||
export * from "./DomainNamesField";
|
||||
export * from "./NginxConfigField";
|
||||
export * from "./SSLCertificateField";
|
||||
export * from "./SSLOptionsFields";
|
||||
|
||||
Reference in New Issue
Block a user