mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2026-11-15 15:18:05 +00:00
Merge pull request #5977 from mirfalltnixein/fix/unsaved-modal-changes
Prevent data loss when dismissing unsaved forms
This commit is contained in:
21 files changed
+950
-659
No files matched your search
@@ -38,8 +38,7 @@ export function BasicAuthFields({ initialValues, name = "items" }: Props) {
|
||||
};
|
||||
|
||||
const setFormField = (newValues: AccessListItem[]) => {
|
||||
const filtered = newValues.filter((v: AccessListItem) => v?.username?.trim() !== "");
|
||||
setFieldValue(name, filtered);
|
||||
setFieldValue(name, newValues);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { Form } from "formik";
|
||||
import type { ReactNode } from "react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { BasicAuthFields } from "./BasicAuthFields";
|
||||
import { FormModal } from "./FormModal";
|
||||
import { LocationsFields } from "./LocationsFields";
|
||||
|
||||
vi.mock("./AccessField", () => ({ AccessField: () => null }));
|
||||
|
||||
const renderDraft = (initialValues: Record<string, unknown>, children: ReactNode) => {
|
||||
const onHide = vi.fn();
|
||||
render(
|
||||
<FormModal show onHide={onHide} initialValues={initialValues} onSubmit={vi.fn()}>
|
||||
{({ onHide: hide }) => (
|
||||
<Form>
|
||||
{children}
|
||||
<button type="button" onClick={hide}>
|
||||
Cancel
|
||||
</button>
|
||||
</Form>
|
||||
)}
|
||||
</FormModal>,
|
||||
);
|
||||
return onHide;
|
||||
};
|
||||
|
||||
describe("FormModal local drafts", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal(
|
||||
"confirm",
|
||||
vi.fn(() => false),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("protects a location draft without a path", () => {
|
||||
const onHide = renderDraft({ locations: [] }, <LocationsFields initialValues={[]} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Add Location" }));
|
||||
fireEvent.change(screen.getByPlaceholderText("eg: 10.0.0.1/path/"), { target: { value: "example.com" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(window.confirm).toHaveBeenCalledOnce();
|
||||
expect(onHide).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("protects a basic auth password draft without a username", () => {
|
||||
const onHide = renderDraft({ items: [] }, <BasicAuthFields initialValues={[]} />);
|
||||
fireEvent.change(document.querySelector('input[type="password"]') as Element, {
|
||||
target: { value: "secret" },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(window.confirm).toHaveBeenCalledOnce();
|
||||
expect(onHide).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { Field, Form } from "formik";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { FormModal } from "./FormModal";
|
||||
|
||||
const renderModal = (onHide = vi.fn(), preventHide = false) => {
|
||||
render(
|
||||
<FormModal show onHide={onHide} preventHide={preventHide} initialValues={{ name: "" }} onSubmit={vi.fn()}>
|
||||
{({ onHide: hide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton />
|
||||
<label htmlFor="name">Name</label>
|
||||
<Field id="name" name="name" />
|
||||
<button type="button" onClick={hide}>
|
||||
Cancel
|
||||
</button>
|
||||
</Form>
|
||||
)}
|
||||
</FormModal>,
|
||||
);
|
||||
return onHide;
|
||||
};
|
||||
|
||||
describe("FormModal", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal("confirm", vi.fn());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("dismisses a pristine form without confirmation", () => {
|
||||
const onHide = renderModal();
|
||||
const confirm = vi.spyOn(window, "confirm");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expect(onHide).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("keeps a dirty form open when dismissal is cancelled", () => {
|
||||
const onHide = renderModal();
|
||||
const confirm = vi.spyOn(window, "confirm").mockReturnValue(false);
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||
fireEvent.click(document.querySelector(".modal") as Element);
|
||||
fireEvent.keyDown(document, { key: "Escape", keyCode: 27 });
|
||||
|
||||
expect(confirm).toHaveBeenCalledTimes(4);
|
||||
expect(onHide).not.toHaveBeenCalled();
|
||||
expect(screen.getByLabelText("Name")).toHaveValue("example.com");
|
||||
});
|
||||
|
||||
it("dismisses a dirty form after confirmation", () => {
|
||||
const onHide = renderModal();
|
||||
vi.spyOn(window, "confirm").mockReturnValue(true);
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(onHide).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not offer to discard changes while submission is in progress", () => {
|
||||
const onHide = renderModal(vi.fn(), true);
|
||||
const confirm = vi.spyOn(window, "confirm");
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expect(onHide).not.toHaveBeenCalled();
|
||||
expect(screen.getByLabelText("Name")).toHaveValue("example.com");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Formik, type FormikConfig, type FormikProps, type FormikValues } from "formik";
|
||||
import { type ReactNode, useEffect } from "react";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
const confirmDiscardChanges = (dirty: boolean) =>
|
||||
!dirty ||
|
||||
window.confirm(
|
||||
intl.formatMessage({
|
||||
id: "form.unsaved-changes",
|
||||
defaultMessage: "You have unsaved changes. Are you sure you want to discard them?",
|
||||
}),
|
||||
);
|
||||
|
||||
const FormDirtyState = ({ dirty, onChange }: { dirty: boolean; onChange: (dirty: boolean) => void }) => {
|
||||
useEffect(() => {
|
||||
onChange(dirty);
|
||||
return () => onChange(false);
|
||||
}, [dirty, onChange]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
interface FormModalProps<Values extends FormikValues> extends Omit<FormikConfig<Values>, "children"> {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
preventHide?: boolean;
|
||||
children: (props: FormikProps<Values> & { onHide: () => void }) => ReactNode;
|
||||
}
|
||||
|
||||
const FormModal = <Values extends FormikValues>({
|
||||
show,
|
||||
onHide,
|
||||
preventHide = false,
|
||||
children,
|
||||
...formikProps
|
||||
}: FormModalProps<Values>) => (
|
||||
<Formik {...formikProps}>
|
||||
{(formikProps) => {
|
||||
const handleHide = () => {
|
||||
if (!preventHide && confirmDiscardChanges(formikProps.dirty)) {
|
||||
onHide();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={handleHide}>
|
||||
{children({ ...formikProps, onHide: handleHide })}
|
||||
</Modal>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
);
|
||||
|
||||
export { confirmDiscardChanges, FormDirtyState, FormModal };
|
||||
@@ -88,8 +88,10 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
|
||||
};
|
||||
|
||||
const setFormField = (newRows: Row[]) => {
|
||||
const filtered = newRows.map((r: Row) => r.value).filter((v: ProxyLocation) => v?.path?.trim() !== "");
|
||||
setFieldValue(name, filtered);
|
||||
setFieldValue(
|
||||
name,
|
||||
newRows.map((r: Row) => r.value),
|
||||
);
|
||||
};
|
||||
|
||||
const forwardSummary = (item: ProxyLocation) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ export * from "./AccessField";
|
||||
export * from "./BasicAuthFields";
|
||||
export * from "./DNSProviderFields";
|
||||
export * from "./DomainNamesField";
|
||||
export * from "./FormModal";
|
||||
export * from "./LocationsFields";
|
||||
export * from "./NginxConfigField";
|
||||
export * from "./SSLCertificateField";
|
||||
|
||||
@@ -437,6 +437,9 @@
|
||||
"footer.github-fork": {
|
||||
"defaultMessage": "Fork me on Github"
|
||||
},
|
||||
"form.unsaved-changes": {
|
||||
"defaultMessage": "You have unsaved changes. Are you sure you want to discard them?"
|
||||
},
|
||||
"host.flags.block-exploits": {
|
||||
"defaultMessage": "Block Common Exploits"
|
||||
},
|
||||
|
||||
@@ -437,6 +437,9 @@
|
||||
"footer.github-fork": {
|
||||
"defaultMessage": "Tee GitHubis fork"
|
||||
},
|
||||
"form.unsaved-changes": {
|
||||
"defaultMessage": "Sul on salvestamata muudatusi. Kas oled kindel, et soovid need tühistada?"
|
||||
},
|
||||
"host.flags.block-exploits": {
|
||||
"defaultMessage": "Blokeeri levinud ründed"
|
||||
},
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import cn from "classnames";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import type { AccessList, AccessListClient, AccessListItem } from "src/api/backend";
|
||||
import { AccessClientFields, BasicAuthFields, Button, Loading } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useAccessList, useSetAccessList } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
@@ -25,13 +26,16 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const validate = (values: any): string | null => {
|
||||
const items = (values.items || []).filter((i: AccessListItem) => i.username?.trim() !== "");
|
||||
const clients = (values.clients || []).filter((i: AccessListClient) => i.address?.trim() !== "");
|
||||
|
||||
// either Auths or Clients must be defined
|
||||
if (values.items?.length === 0 && values.clients?.length === 0) {
|
||||
if (items.length === 0 && clients.length === 0) {
|
||||
return intl.formatMessage({ id: "error.access.at-least-one" });
|
||||
}
|
||||
|
||||
// ensure the items don't contain the same username twice
|
||||
const usernames = values.items.map((i: any) => i.username);
|
||||
const usernames = items.map((i: AccessListItem) => i.username);
|
||||
const uniqueUsernames = Array.from(new Set(usernames));
|
||||
if (usernames.length !== uniqueUsernames.length) {
|
||||
return intl.formatMessage({ id: "error.access.duplicate-usernames" });
|
||||
@@ -58,10 +62,12 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
// Filter out "items" to only use the "username" and "password" fields
|
||||
payload.items = (values.items || []).map((i: AccessListItem) => ({
|
||||
username: i.username,
|
||||
password: i.password,
|
||||
}));
|
||||
payload.items = (values.items || [])
|
||||
.filter((i: AccessListItem) => i.username?.trim() !== "")
|
||||
.map((i: AccessListItem) => ({
|
||||
username: i.username,
|
||||
password: i.password,
|
||||
}));
|
||||
|
||||
// Filter out "clients" to only use the "directive" and "address" fields
|
||||
payload.clients = (values.clients || []).map((i: AccessListClient) => ({
|
||||
@@ -86,15 +92,22 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
const toggleEnabled = cn(toggleClasses, "bg-cyan");
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || (error && !data)) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
{!isLoading && data && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
name: data?.name,
|
||||
@@ -106,7 +119,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ setFieldValue }: any) => (
|
||||
{({ onHide, setFieldValue }: any) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -267,7 +280,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -283,9 +296,9 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { updateAuth } from "src/api/backend";
|
||||
import { Button } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
|
||||
@@ -41,129 +42,130 @@ const ChangePasswordModal = EasyModal.create(({ id, visible, remove }: Props) =>
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
current: "",
|
||||
new: "",
|
||||
confirm: "",
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="user.change-password" />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
<div className="mb-3">
|
||||
<Field name="current">
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="current"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className={`form-control ${form.errors.current && form.touched.current ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "user.current-password",
|
||||
})}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="current">
|
||||
<T id="user.current-password" />
|
||||
</label>
|
||||
{form.errors.name ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.current && form.touched.current
|
||||
? form.errors.current
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Field name="new" validate={validateString(8, 100)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="new"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`form-control ${form.errors.new && form.touched.new ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.new-password" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="new">
|
||||
<T id="user.new-password" />
|
||||
</label>
|
||||
{form.errors.new ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.new && form.touched.new ? form.errors.new : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Field name="confirm" validate={validateString(8, 100)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="confirm"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`form-control ${form.errors.confirm && form.touched.confirm ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.confirm-password" })}
|
||||
{...field}
|
||||
/>
|
||||
{form.errors.confirm ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.confirm && form.touched.confirm
|
||||
? form.errors.confirm
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
<label htmlFor="confirm">
|
||||
<T id="user.confirm-password" />
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</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"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<T id="save" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
current: "",
|
||||
new: "",
|
||||
confirm: "",
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="user.change-password" />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
<div className="mb-3">
|
||||
<Field name="current">
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="current"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className={`form-control ${form.errors.current && form.touched.current ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "user.current-password",
|
||||
})}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="current">
|
||||
<T id="user.current-password" />
|
||||
</label>
|
||||
{form.errors.name ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.current && form.touched.current
|
||||
? form.errors.current
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Field name="new" validate={validateString(8, 100)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="new"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`form-control ${form.errors.new && form.touched.new ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.new-password" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="new">
|
||||
<T id="user.new-password" />
|
||||
</label>
|
||||
{form.errors.new ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.new && form.touched.new ? form.errors.new : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Field name="confirm" validate={validateString(8, 100)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="confirm"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`form-control ${form.errors.confirm && form.touched.confirm ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.confirm-password" })}
|
||||
{...field}
|
||||
/>
|
||||
{form.errors.confirm ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.confirm && form.touched.confirm
|
||||
? form.errors.confirm
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
<label htmlFor="confirm">
|
||||
<T id="user.confirm-password" />
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<T id="save" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</FormModal>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { type Certificate, createCertificate, uploadCertificate, validateCertificate } from "src/api/backend";
|
||||
import { Button } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
@@ -57,175 +58,176 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
niceName: "",
|
||||
provider: "other",
|
||||
certificate: null,
|
||||
certificateKey: null,
|
||||
intermediateCertificate: null,
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="object.add" tData={{ object: "certificates.custom" }} />
|
||||
</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.custom.warning" />
|
||||
</p>
|
||||
<Field name="niceName" validate={validateString(1, 255)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="niceName" className="form-label">
|
||||
<T id="column.name" />
|
||||
</label>
|
||||
<input
|
||||
id="niceName"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
{...field}
|
||||
/>
|
||||
{form.errors.niceName ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.niceName && form.touched.niceName
|
||||
? form.errors.niceName
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="certificateKey">
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="certificateKey" className="form-label">
|
||||
<T id="certificate.custom-certificate-key" />
|
||||
</label>
|
||||
<input
|
||||
id="certificateKey"
|
||||
type="file"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
onChange={(event) => {
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
event.currentTarget.files?.length
|
||||
? event.currentTarget.files[0]
|
||||
: null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{form.errors.certificateKey ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.certificateKey && form.touched.certificateKey
|
||||
? form.errors.certificateKey
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="certificate">
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="certificate" className="form-label">
|
||||
<T id="certificate.custom-certificate" />
|
||||
</label>
|
||||
<input
|
||||
id="certificate"
|
||||
type="file"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
onChange={(event) => {
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
event.currentTarget.files?.length
|
||||
? event.currentTarget.files[0]
|
||||
: null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{form.errors.certificate ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.certificate && form.touched.certificate
|
||||
? form.errors.certificate
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="intermediateCertificate">
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="intermediateCertificate" className="form-label">
|
||||
<T id="certificate.custom-intermediate" />
|
||||
</label>
|
||||
<input
|
||||
id="intermediateCertificate"
|
||||
type="file"
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
onChange={(event) => {
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
event.currentTarget.files?.length
|
||||
? event.currentTarget.files[0]
|
||||
: null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{form.errors.intermediateCertificate ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.intermediateCertificate &&
|
||||
form.touched.intermediateCertificate
|
||||
? form.errors.intermediateCertificate
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
niceName: "",
|
||||
provider: "other",
|
||||
certificate: null,
|
||||
certificateKey: null,
|
||||
intermediateCertificate: null,
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="object.add" tData={{ object: "certificates.custom" }} />
|
||||
</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.custom.warning" />
|
||||
</p>
|
||||
<Field name="niceName" validate={validateString(1, 255)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="niceName" className="form-label">
|
||||
<T id="column.name" />
|
||||
</label>
|
||||
<input
|
||||
id="niceName"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
{...field}
|
||||
/>
|
||||
{form.errors.niceName ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.niceName && form.touched.niceName
|
||||
? form.errors.niceName
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="certificateKey">
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="certificateKey" className="form-label">
|
||||
<T id="certificate.custom-certificate-key" />
|
||||
</label>
|
||||
<input
|
||||
id="certificateKey"
|
||||
type="file"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
onChange={(event) => {
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
event.currentTarget.files?.length
|
||||
? event.currentTarget.files[0]
|
||||
: null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{form.errors.certificateKey ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.certificateKey && form.touched.certificateKey
|
||||
? form.errors.certificateKey
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="certificate">
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="certificate" className="form-label">
|
||||
<T id="certificate.custom-certificate" />
|
||||
</label>
|
||||
<input
|
||||
id="certificate"
|
||||
type="file"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
onChange={(event) => {
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
event.currentTarget.files?.length
|
||||
? event.currentTarget.files[0]
|
||||
: null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{form.errors.certificate ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.certificate && form.touched.certificate
|
||||
? form.errors.certificate
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="intermediateCertificate">
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="intermediateCertificate" className="form-label">
|
||||
<T id="certificate.custom-intermediate" />
|
||||
</label>
|
||||
<input
|
||||
id="intermediateCertificate"
|
||||
type="file"
|
||||
autoComplete="off"
|
||||
className="form-control"
|
||||
onChange={(event) => {
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
event.currentTarget.files?.length
|
||||
? event.currentTarget.files[0]
|
||||
: null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{form.errors.intermediateCertificate ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.intermediateCertificate &&
|
||||
form.touched.intermediateCertificate
|
||||
? form.errors.intermediateCertificate
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</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>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} 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>
|
||||
)}
|
||||
</FormModal>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Form, Formik, Field } from "formik";
|
||||
import { Field, Form } 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 { FormModal } from "src/components/Form";
|
||||
import { T } from "src/locale";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
@@ -36,77 +37,78 @@ const DNSCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPro
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
domainNames: [],
|
||||
provider: "letsencrypt",
|
||||
meta: {
|
||||
dnsChallenge: true,
|
||||
keyType: "ecdsa",
|
||||
},
|
||||
} 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 />
|
||||
<Field name="meta.keyType">
|
||||
{({ field }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="keyType" className="form-label">
|
||||
<T id="certificates.key-type" />
|
||||
</label>
|
||||
<select id="keyType" className="form-select" {...field}>
|
||||
<option value="rsa">
|
||||
<T id="certificates.key-type-rsa" />
|
||||
</option>
|
||||
<option value="ecdsa">
|
||||
<T id="certificates.key-type-ecdsa" />
|
||||
</option>
|
||||
</select>
|
||||
<small className="form-text text-muted">
|
||||
<T id="certificates.key-type-description" />
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<DNSProviderFields />
|
||||
</div>
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
domainNames: [],
|
||||
provider: "letsencrypt",
|
||||
meta: {
|
||||
dnsChallenge: true,
|
||||
keyType: "ecdsa",
|
||||
},
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ onHide }) => (
|
||||
<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 />
|
||||
<Field name="meta.keyType">
|
||||
{({ field }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="keyType" className="form-label">
|
||||
<T id="certificates.key-type" />
|
||||
</label>
|
||||
<select id="keyType" className="form-select" {...field}>
|
||||
<option value="rsa">
|
||||
<T id="certificates.key-type-rsa" />
|
||||
</option>
|
||||
<option value="ecdsa">
|
||||
<T id="certificates.key-type-ecdsa" />
|
||||
</option>
|
||||
</select>
|
||||
<small className="form-text text-muted">
|
||||
<T id="certificates.key-type-description" />
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<DNSProviderFields />
|
||||
</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>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} 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>
|
||||
)}
|
||||
</FormModal>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Form, Formik } from "formik";
|
||||
import { Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SSLCertificateField,
|
||||
SSLOptionsFields,
|
||||
} from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useDeadHost, useSetDeadHost } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
@@ -53,15 +54,22 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || (error && !data)) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
{!isLoading && data && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
domainNames: data?.domainNames,
|
||||
@@ -76,7 +84,7 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -149,7 +157,7 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -165,9 +173,9 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Form, Formik, Field } from "formik";
|
||||
import { Field, Form } 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 { FormModal } from "src/components/Form";
|
||||
import { T } from "src/locale";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
@@ -109,105 +110,106 @@ const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPr
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
domainNames: [],
|
||||
provider: "letsencrypt",
|
||||
meta: {
|
||||
keyType: "ecdsa",
|
||||
},
|
||||
} 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);
|
||||
}}
|
||||
/>
|
||||
<Field name="meta.keyType">
|
||||
{({ field }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="keyType" className="form-label">
|
||||
<T id="certificates.key-type" />
|
||||
</label>
|
||||
<select id="keyType" className="form-select" {...field}>
|
||||
<option value="rsa">
|
||||
<T id="certificates.key-type-rsa" />
|
||||
</option>
|
||||
<option value="ecdsa">
|
||||
<T id="certificates.key-type-ecdsa" />
|
||||
</option>
|
||||
</select>
|
||||
<small className="form-text text-muted">
|
||||
<T id="certificates.key-type-description" />
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting || isTesting}
|
||||
initialValues={
|
||||
{
|
||||
domainNames: [],
|
||||
provider: "letsencrypt",
|
||||
meta: {
|
||||
keyType: "ecdsa",
|
||||
},
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ onHide }) => (
|
||||
<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);
|
||||
}}
|
||||
/>
|
||||
<Field name="meta.keyType">
|
||||
{({ field }: any) => (
|
||||
<div className="mb-3">
|
||||
<label htmlFor="keyType" className="form-label">
|
||||
<T id="certificates.key-type" />
|
||||
</label>
|
||||
<select id="keyType" className="form-select" {...field}>
|
||||
<option value="rsa">
|
||||
<T id="certificates.key-type-rsa" />
|
||||
</option>
|
||||
<option value="ecdsa">
|
||||
<T id="certificates.key-type-ecdsa" />
|
||||
</option>
|
||||
</select>
|
||||
<small className="form-text text-muted">
|
||||
<T id="certificates.key-type-description" />
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
{testResults ? (
|
||||
<div className="card-footer">
|
||||
<h5>
|
||||
<T id="certificates.http.test-results" />
|
||||
</h5>
|
||||
{parseTestResults()}
|
||||
</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" />
|
||||
) : null}
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} 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>
|
||||
<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>
|
||||
<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>
|
||||
)}
|
||||
</FormModal>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import cn from "classnames";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { setPermissions } from "src/api/backend";
|
||||
import { Button, Loading } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useUser } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import styles from "./PermissionsModal.module.css";
|
||||
@@ -133,15 +134,22 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
const isAdmin = data?.roles.indexOf("admin") !== -1;
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || (error && !data)) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
{!isLoading && data && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
visibility: data.permissions?.visibility,
|
||||
@@ -155,7 +163,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -263,7 +271,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -278,9 +286,9 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import cn from "classnames";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
SSLCertificateField,
|
||||
SSLOptionsFields,
|
||||
} from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useProxyHost, useSetProxyHost, useUser } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
|
||||
@@ -45,6 +46,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
id: id === "new" ? undefined : id,
|
||||
...values,
|
||||
};
|
||||
payload.locations = (values.locations || []).filter((location: any) => location.path?.trim() !== "");
|
||||
|
||||
setProxyHost(payload, {
|
||||
onError: (err: any) => setErrorMsg(<T id={err.message} />),
|
||||
@@ -60,15 +62,22 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && (error || userError) && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || userError?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || userIsLoading || ((error || userError) && (!data || !currentUser))) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && (error || userError) && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || userError?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{(isLoading || userIsLoading) && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{isLoading || (userIsLoading && <Loading noLogo />)}
|
||||
{!isLoading && !userIsLoading && data && currentUser && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
// Details tab
|
||||
@@ -96,7 +105,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -350,7 +359,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
|
||||
@@ -368,9 +377,9 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import cn from "classnames";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
SSLCertificateField,
|
||||
SSLOptionsFields,
|
||||
} from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useRedirectionHost, useSetRedirectionHost } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
@@ -55,15 +56,22 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || (error && !data)) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
{!isLoading && data && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
// Details tab
|
||||
@@ -86,7 +94,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -326,7 +334,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -342,9 +350,9 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { generate } from "generate-password-browser";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { updateAuth } from "src/api/backend";
|
||||
import { Button } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
|
||||
@@ -35,103 +36,104 @@ const SetPasswordModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
new: "",
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="user.set-password" />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
<div className="mb-3">
|
||||
<Field name="new" validate={validateString(8, 100)}>
|
||||
{({ field, form }: any) => (
|
||||
<>
|
||||
<p className="text-end">
|
||||
<small>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
generate({
|
||||
length: 12,
|
||||
numbers: true,
|
||||
}),
|
||||
);
|
||||
setShowPassword(true);
|
||||
}}
|
||||
>
|
||||
<T id="password.generate" />
|
||||
</a>{" "}
|
||||
—{" "}
|
||||
<a
|
||||
href="#"
|
||||
className="text-xs"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setShowPassword(!showPassword);
|
||||
}}
|
||||
>
|
||||
<T id={showPassword ? "password.hide" : "password.show"} />
|
||||
</a>
|
||||
</small>
|
||||
</p>
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="new"
|
||||
type={showPassword ? "text" : "password"}
|
||||
required
|
||||
className={`form-control ${form.errors.new && form.touched.new ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.new-password" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="new">
|
||||
<T id="user.new-password" />
|
||||
</label>
|
||||
{form.errors.new ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.new && form.touched.new ? form.errors.new : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</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"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<T id="save" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
new: "",
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="user.set-password" />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
<div className="mb-3">
|
||||
<Field name="new" validate={validateString(8, 100)}>
|
||||
{({ field, form }: any) => (
|
||||
<>
|
||||
<p className="text-end">
|
||||
<small>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
generate({
|
||||
length: 12,
|
||||
numbers: true,
|
||||
}),
|
||||
);
|
||||
setShowPassword(true);
|
||||
}}
|
||||
>
|
||||
<T id="password.generate" />
|
||||
</a>{" "}
|
||||
—{" "}
|
||||
<a
|
||||
href="#"
|
||||
className="text-xs"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setShowPassword(!showPassword);
|
||||
}}
|
||||
>
|
||||
<T id={showPassword ? "password.hide" : "password.show"} />
|
||||
</a>
|
||||
</small>
|
||||
</p>
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="new"
|
||||
type={showPassword ? "text" : "password"}
|
||||
required
|
||||
className={`form-control ${form.errors.new && form.touched.new ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.new-password" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="new">
|
||||
<T id="user.new-password" />
|
||||
</label>
|
||||
{form.errors.new ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.new && form.touched.new ? form.errors.new : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<T id="save" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</FormModal>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { Button, Loading, SSLCertificateField, SSLOptionsFields } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useSetStream, useStream } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateNumber, validateString } from "src/modules/Validations";
|
||||
@@ -46,15 +47,22 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || (error && !data)) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
{!isLoading && data && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
incomingPort: data?.incomingPort,
|
||||
@@ -68,7 +76,7 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ setFieldValue }: any) => (
|
||||
{({ onHide, setFieldValue }: any) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -302,7 +310,7 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -318,9 +326,9 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import Modal from "react-bootstrap/Modal";
|
||||
import QRCode from "react-qr-code";
|
||||
import { disable2FA, enable2FA, get2FAStatus, regenerateBackupCodes, start2FASetup } from "src/api/backend";
|
||||
import { Button } from "src/components";
|
||||
import { confirmDiscardChanges, FormDirtyState } from "src/components/Form";
|
||||
import { T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
import styles from "./TwoFactorModal.module.css";
|
||||
@@ -28,6 +29,13 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
const [setupData, setSetupData] = useState<{ secret: string; otpauthUrl: string } | null>(null);
|
||||
const [backupCodes, setBackupCodes] = useState<string[]>([]);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [formDirty, setFormDirty] = useState(false);
|
||||
|
||||
const handleHide = () => {
|
||||
if (!isSubmitting && confirmDiscardChanges(formDirty)) {
|
||||
remove();
|
||||
}
|
||||
};
|
||||
|
||||
const loadStatus = useCallback(async () => {
|
||||
try {
|
||||
@@ -172,8 +180,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
/>
|
||||
</label>
|
||||
<Formik initialValues={{ code: "" }} onSubmit={handleVerify}>
|
||||
{() => (
|
||||
{({ dirty }) => (
|
||||
<Form>
|
||||
<FormDirtyState dirty={dirty} onChange={setFormDirty} />
|
||||
<Field name="code" validate={validateString(6, 6)}>
|
||||
{({ field, form }: any) => (
|
||||
<label className="mb-3 d-block">
|
||||
@@ -242,8 +251,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<T id="2fa.disable-warning" />
|
||||
</Alert>
|
||||
<Formik initialValues={{ code: "" }} onSubmit={handleDisable}>
|
||||
{() => (
|
||||
{({ dirty }) => (
|
||||
<Form>
|
||||
<FormDirtyState dirty={dirty} onChange={setFormDirty} />
|
||||
<Field name="code" validate={validateString(6, 6)}>
|
||||
{({ field, form }: any) => (
|
||||
<label className="mb-3 d-block">
|
||||
@@ -290,8 +300,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<T id="2fa.regenerate-instructions" />
|
||||
</p>
|
||||
<Formik initialValues={{ code: "" }} onSubmit={handleRegenerateBackup}>
|
||||
{() => (
|
||||
{({ dirty }) => (
|
||||
<Form>
|
||||
<FormDirtyState dirty={dirty} onChange={setFormDirty} />
|
||||
<Field name="code" validate={validateString(6, 6)}>
|
||||
{({ field, form }: any) => (
|
||||
<label className="mb-3 d-block">
|
||||
@@ -335,7 +346,7 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Modal show={visible} onHide={handleHide}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id="2fa.title" />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { Field, Form } from "formik";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { Button, Loading } from "src/components";
|
||||
import { FormModal } from "src/components/Form";
|
||||
import { useSetUser, useUser } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateEmail, validateString } from "src/modules/Validations";
|
||||
@@ -18,7 +19,7 @@ interface Props extends InnerModalProps {
|
||||
}
|
||||
const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
const { data, isLoading, error } = useUser(id);
|
||||
const { data: currentUser, isLoading: currentIsLoading } = useUser("me");
|
||||
const { data: currentUser, isLoading: currentIsLoading, error: currentError } = useUser("me");
|
||||
const { mutate: setUser } = useSetUser();
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
@@ -59,15 +60,22 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
<>
|
||||
{(isLoading || currentIsLoading || ((error || currentError) && (!data || !currentUser))) && (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
{!isLoading && (error || currentError) && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || currentError?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{(isLoading || currentIsLoading) && <Loading noLogo />}
|
||||
</Modal>
|
||||
)}
|
||||
{(isLoading || currentIsLoading) && <Loading noLogo />}
|
||||
{!isLoading && !currentIsLoading && data && currentUser && (
|
||||
<Formik
|
||||
<FormModal
|
||||
show={visible}
|
||||
onHide={remove}
|
||||
preventHide={isSubmitting}
|
||||
initialValues={
|
||||
{
|
||||
name: data?.name,
|
||||
@@ -79,7 +87,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{() => (
|
||||
{({ onHide }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
@@ -222,7 +230,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
) : null}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<Button data-bs-dismiss="modal" onClick={onHide} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -237,9 +245,9 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormModal>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user