diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 3ba1cd36..d63d47ae 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -196,10 +196,10 @@ export interface Stream { export interface Setting { id: string; - name: string; - description: string; + name?: string; + description?: string; value: string; - meta: Record; + meta?: Record; } export interface DNSProvider { diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 0ab4a2e6..1ef445a8 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -13,6 +13,7 @@ export * from "./useProxyHost"; export * from "./useProxyHosts"; export * from "./useRedirectionHost"; export * from "./useRedirectionHosts"; +export * from "./useSetting"; export * from "./useStream"; export * from "./useStreams"; export * from "./useTheme"; diff --git a/frontend/src/hooks/useSetting.ts b/frontend/src/hooks/useSetting.ts new file mode 100644 index 00000000..ce843d65 --- /dev/null +++ b/frontend/src/hooks/useSetting.ts @@ -0,0 +1,40 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getSetting, type Setting, updateSetting } from "src/api/backend"; + +const fetchSetting = (id: string) => { + return getSetting(id); +}; + +const useSetting = (id: string, options = {}) => { + return useQuery({ + queryKey: ["setting", id], + queryFn: () => fetchSetting(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetSetting = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: Setting) => updateSetting(values), + onMutate: (values: Setting) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["setting", values.id]); + queryClient.setQueryData(["setting", values.id], (old: Setting) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["setting", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: Setting) => { + queryClient.invalidateQueries({ queryKey: ["setting", id] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + }, + }); +}; + +export { useSetting, useSetSetting }; diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index 511059c5..eaf459e4 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -168,7 +168,16 @@ "role.admin": "Administrator", "role.standard-user": "Standard User", "save": "Save", + "setting": "Setting", "settings": "Settings", + "settings.default-site": "Default Site", + "settings.default-site.404": "404 Page", + "settings.default-site.444": "No Response (444)", + "settings.default-site.congratulations": "Congratulations Page", + "settings.default-site.description": "What to show when Nginx is hit with an unknown Host", + "settings.default-site.html": "Custom HTML", + "settings.default-site.html.placeholder": "", + "settings.default-site.redirect": "Redirect", "setup.preamble": "Get started by creating your admin account.", "setup.title": "Welcome!", "sign-in": "Sign in", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index c81de9f0..1732317e 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -506,9 +506,36 @@ "save": { "defaultMessage": "Save" }, + "setting": { + "defaultMessage": "Setting" + }, "settings": { "defaultMessage": "Settings" }, + "settings.default-site": { + "defaultMessage": "Default Site" + }, + "settings.default-site.404": { + "defaultMessage": "404 Page" + }, + "settings.default-site.444": { + "defaultMessage": "No Response (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Congratulations Page" + }, + "settings.default-site.description": { + "defaultMessage": "What to show when Nginx is hit with an unknown Host" + }, + "settings.default-site.html": { + "defaultMessage": "Custom HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Redirect" + }, "setup.preamble": { "defaultMessage": "Get started by creating your admin account." }, diff --git a/frontend/src/pages/Settings/DefaultSite.tsx b/frontend/src/pages/Settings/DefaultSite.tsx new file mode 100644 index 00000000..bc9cf176 --- /dev/null +++ b/frontend/src/pages/Settings/DefaultSite.tsx @@ -0,0 +1,269 @@ +import CodeEditor from "@uiw/react-textarea-code-editor"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import { Button, Loading } from "src/components"; +import { useSetSetting, useSetting } from "src/hooks"; +import { intl, T } from "src/locale"; +import { validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +export default function DefaultSite() { + const { data, isLoading, error } = useSetting("default-site"); + const { mutate: setSetting } = useSetSetting(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const payload = { + id: "default-site", + value: values.value, + meta: { + redirect: values.redirect, + html: values.html, + }, + }; + + setSetting(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("setting", "saved"); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + if (!isLoading && error) { + return ( +
+
+ + {error.message} + +
+
+ ); + } + + if (isLoading) { + return ( +
+
+ +
+
+ ); + } + + return ( + + {({ values }) => ( +
+
+ setErrorMsg(null)} dismissible> + {errorMsg} + + + {({ field, form }: any) => ( +
+ +
+ + + + + +
+
+ )} +
+ {values.value === "redirect" && ( + + {({ field, form }: any) => ( +
+ +
+ + {form.errors.redirect ? ( +
+ {form.errors.redirect && form.touched.redirect + ? form.errors.redirect + : null} +
+ ) : null} +
+
+ )} +
+ )} + {values.value === "html" && ( + + {({ field, form }: any) => ( +
+ +
+ + {form.errors.html ? ( +
+ {form.errors.html && form.touched.html ? form.errors.html : null} +
+ ) : null} +
+
+ )} +
+ )} +
+
+
+ +
+
+
+ )} +
+ ); +} diff --git a/frontend/src/pages/Settings/Layout.tsx b/frontend/src/pages/Settings/Layout.tsx new file mode 100644 index 00000000..a0a77db2 --- /dev/null +++ b/frontend/src/pages/Settings/Layout.tsx @@ -0,0 +1,40 @@ +import { T } from "src/locale"; +import DefaultSite from "./DefaultSite"; + +export default function Layout() { + // Taken from https://preview.tabler.io/settings.html + // Refer to that when updating this content + + return ( +
+
+
+
+
+

+ +

+
+
+
+ +
+ +
+
+
+
+ ); +} diff --git a/frontend/src/pages/Settings/SettingTable.tsx b/frontend/src/pages/Settings/SettingTable.tsx deleted file mode 100644 index 1cf921db..00000000 --- a/frontend/src/pages/Settings/SettingTable.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { T } from "src/locale"; - -export default function SettingTable() { - return ( -
-
-
-
-
-

- -

-
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - -
-
- -
-
-
-
- blog.jc21.com -
-
Created: 20th September 2024
-
-
http://172.17.0.1:3001Let's EncryptPublic - Online - - - -
- Proxy Host #2 - - - Edit - - - - Disable - - - -
-
-
-
-
- ); -} diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx index 2774c795..dccfb910 100644 --- a/frontend/src/pages/Settings/index.tsx +++ b/frontend/src/pages/Settings/index.tsx @@ -1,10 +1,10 @@ import { HasPermission } from "src/components"; -import SettingTable from "./SettingTable"; +import Layout from "./Layout"; const Settings = () => { return ( - + ); };