SSLCertificateField.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { IconShield } from "@tabler/icons-react";
  2. import { Field, useFormikContext } from "formik";
  3. import Select, { type ActionMeta, components, type OptionProps } from "react-select";
  4. import type { Certificate } from "src/api/backend";
  5. import { useCertificates } from "src/hooks";
  6. import { DateTimeFormat, intl } from "src/locale";
  7. interface CertOption {
  8. readonly value: number | "new";
  9. readonly label: string;
  10. readonly subLabel: string;
  11. readonly icon: React.ReactNode;
  12. }
  13. const Option = (props: OptionProps<CertOption>) => {
  14. return (
  15. <components.Option {...props}>
  16. <div className="flex-fill">
  17. <div className="font-weight-medium">
  18. {props.data.icon} <strong>{props.data.label}</strong>
  19. </div>
  20. <div className="text-secondary mt-1 ps-3">{props.data.subLabel}</div>
  21. </div>
  22. </components.Option>
  23. );
  24. };
  25. interface Props {
  26. id?: string;
  27. name?: string;
  28. label?: string;
  29. required?: boolean;
  30. allowNew?: boolean;
  31. }
  32. export function SSLCertificateField({
  33. name = "certificateId",
  34. label = "ssl-certificate",
  35. id = "certificateId",
  36. required,
  37. allowNew,
  38. }: Props) {
  39. const { isLoading, isError, error, data } = useCertificates();
  40. const { values, setFieldValue } = useFormikContext();
  41. const v: any = values || {};
  42. const handleChange = (newValue: any, _actionMeta: ActionMeta<CertOption>) => {
  43. setFieldValue(name, newValue?.value);
  44. const {
  45. sslForced,
  46. http2Support,
  47. hstsEnabled,
  48. hstsSubdomains,
  49. dnsChallenge,
  50. dnsProvider,
  51. dnsProviderCredentials,
  52. propagationSeconds,
  53. } = v;
  54. if (!newValue?.value) {
  55. sslForced && setFieldValue("sslForced", false);
  56. http2Support && setFieldValue("http2Support", false);
  57. hstsEnabled && setFieldValue("hstsEnabled", false);
  58. hstsSubdomains && setFieldValue("hstsSubdomains", false);
  59. }
  60. if (newValue?.value !== "new") {
  61. dnsChallenge && setFieldValue("dnsChallenge", undefined);
  62. dnsProvider && setFieldValue("dnsProvider", undefined);
  63. dnsProviderCredentials && setFieldValue("dnsProviderCredentials", undefined);
  64. propagationSeconds && setFieldValue("propagationSeconds", undefined);
  65. }
  66. };
  67. const options: CertOption[] =
  68. data?.map((cert: Certificate) => ({
  69. value: cert.id,
  70. label: cert.niceName,
  71. subLabel: `${cert.provider === "letsencrypt" ? "Let's Encrypt" : cert.provider} &mdash; Expires: ${
  72. cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A"
  73. }`,
  74. icon: <IconShield size={14} className="text-pink" />,
  75. })) || [];
  76. // Prepend the Add New option
  77. if (allowNew) {
  78. options?.unshift({
  79. value: "new",
  80. label: "Request a new Certificate",
  81. subLabel: "with Let's Encrypt",
  82. icon: <IconShield size={14} className="text-lime" />,
  83. });
  84. }
  85. // Prepend the None option
  86. if (!required) {
  87. options?.unshift({
  88. value: 0,
  89. label: "None",
  90. subLabel: "This host will not use HTTPS",
  91. icon: <IconShield size={14} className="text-red" />,
  92. });
  93. }
  94. return (
  95. <Field name={name}>
  96. {({ field, form }: any) => (
  97. <div className="mb-3">
  98. <label className="form-label" htmlFor={id}>
  99. {intl.formatMessage({ id: label })}
  100. </label>
  101. {isLoading ? <div className="placeholder placeholder-lg col-12 my-3 placeholder-glow" /> : null}
  102. {isError ? <div className="invalid-feedback">{`${error}`}</div> : null}
  103. {!isLoading && !isError ? (
  104. <Select
  105. className="react-select-container"
  106. classNamePrefix="react-select"
  107. defaultValue={options.find((o) => o.value === field.value) || options[0]}
  108. options={options}
  109. components={{ Option }}
  110. styles={{
  111. option: (base) => ({
  112. ...base,
  113. height: "100%",
  114. }),
  115. }}
  116. onChange={handleChange}
  117. />
  118. ) : null}
  119. {form.errors[field.name] ? (
  120. <div className="invalid-feedback">
  121. {form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
  122. </div>
  123. ) : null}
  124. </div>
  125. )}
  126. </Field>
  127. );
  128. }