mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-11-04 09:25:15 +00:00 
			
		
		
		
	Settings polish
This commit is contained in:
		@@ -196,10 +196,10 @@ export interface Stream {
 | 
			
		||||
 | 
			
		||||
export interface Setting {
 | 
			
		||||
	id: string;
 | 
			
		||||
	name: string;
 | 
			
		||||
	description: string;
 | 
			
		||||
	name?: string;
 | 
			
		||||
	description?: string;
 | 
			
		||||
	value: string;
 | 
			
		||||
	meta: Record<string, any>;
 | 
			
		||||
	meta?: Record<string, any>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface DNSProvider {
 | 
			
		||||
 
 | 
			
		||||
@@ -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";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								frontend/src/hooks/useSetting.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								frontend/src/hooks/useSetting.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<Setting, Error>({
 | 
			
		||||
		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 };
 | 
			
		||||
@@ -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": "<!-- Enter your custom HTML content here -->",
 | 
			
		||||
  "settings.default-site.redirect": "Redirect",
 | 
			
		||||
  "setup.preamble": "Get started by creating your admin account.",
 | 
			
		||||
  "setup.title": "Welcome!",
 | 
			
		||||
  "sign-in": "Sign in",
 | 
			
		||||
 
 | 
			
		||||
@@ -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": "<!-- Enter your custom HTML content here -->"
 | 
			
		||||
	},
 | 
			
		||||
	"settings.default-site.redirect": {
 | 
			
		||||
		"defaultMessage": "Redirect"
 | 
			
		||||
	},
 | 
			
		||||
	"setup.preamble": {
 | 
			
		||||
		"defaultMessage": "Get started by creating your admin account."
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										269
									
								
								frontend/src/pages/Settings/DefaultSite.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								frontend/src/pages/Settings/DefaultSite.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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<ReactNode | null>(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(<T id={err.message} />),
 | 
			
		||||
			onSuccess: () => {
 | 
			
		||||
				showObjectSuccess("setting", "saved");
 | 
			
		||||
			},
 | 
			
		||||
			onSettled: () => {
 | 
			
		||||
				setIsSubmitting(false);
 | 
			
		||||
				setSubmitting(false);
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (!isLoading && error) {
 | 
			
		||||
		return (
 | 
			
		||||
			<div className="card-body">
 | 
			
		||||
				<div className="mb-3">
 | 
			
		||||
					<Alert variant="danger" show>
 | 
			
		||||
						{error.message}
 | 
			
		||||
					</Alert>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (isLoading) {
 | 
			
		||||
		return (
 | 
			
		||||
			<div className="card-body">
 | 
			
		||||
				<div className="mb-3">
 | 
			
		||||
					<Loading noLogo />
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<Formik
 | 
			
		||||
			initialValues={
 | 
			
		||||
				{
 | 
			
		||||
					value: data?.value || "congratulations",
 | 
			
		||||
					redirect: data?.meta?.redirect || "",
 | 
			
		||||
					html: data?.meta?.html || "",
 | 
			
		||||
				} as any
 | 
			
		||||
			}
 | 
			
		||||
			onSubmit={onSubmit}
 | 
			
		||||
		>
 | 
			
		||||
			{({ values }) => (
 | 
			
		||||
				<Form>
 | 
			
		||||
					<div className="card-body">
 | 
			
		||||
						<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible>
 | 
			
		||||
							{errorMsg}
 | 
			
		||||
						</Alert>
 | 
			
		||||
						<Field name="value">
 | 
			
		||||
							{({ field, form }: any) => (
 | 
			
		||||
								<div className="mb-3">
 | 
			
		||||
									<label className="form-label" htmlFor="setting-host-unknown">
 | 
			
		||||
										<T id="settings.default-site.description" />
 | 
			
		||||
									</label>
 | 
			
		||||
									<div className="form-selectgroup form-selectgroup-boxes d-flex flex-column">
 | 
			
		||||
										<label className="form-selectgroup-item flex-fill">
 | 
			
		||||
											<input
 | 
			
		||||
												type="radio"
 | 
			
		||||
												name={field.name}
 | 
			
		||||
												value="congratulations"
 | 
			
		||||
												className="form-selectgroup-input"
 | 
			
		||||
												checked={field.value === "congratulations"}
 | 
			
		||||
												onChange={(e) => form.setFieldValue(field.name, e.target.value)}
 | 
			
		||||
											/>
 | 
			
		||||
											<div className="form-selectgroup-label d-flex align-items-center p-3">
 | 
			
		||||
												<div className="me-3">
 | 
			
		||||
													<span className="form-selectgroup-check" />
 | 
			
		||||
												</div>
 | 
			
		||||
												<div>
 | 
			
		||||
													<T id="settings.default-site.congratulations" />
 | 
			
		||||
												</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</label>
 | 
			
		||||
										<label className="form-selectgroup-item flex-fill">
 | 
			
		||||
											<input
 | 
			
		||||
												type="radio"
 | 
			
		||||
												name={field.name}
 | 
			
		||||
												value="404"
 | 
			
		||||
												className="form-selectgroup-input"
 | 
			
		||||
												checked={field.value === "404"}
 | 
			
		||||
												onChange={(e) => form.setFieldValue(field.name, e.target.value)}
 | 
			
		||||
											/>
 | 
			
		||||
											<div className="form-selectgroup-label d-flex align-items-center p-3">
 | 
			
		||||
												<div className="me-3">
 | 
			
		||||
													<span className="form-selectgroup-check" />
 | 
			
		||||
												</div>
 | 
			
		||||
												<div>
 | 
			
		||||
													<T id="settings.default-site.404" />
 | 
			
		||||
												</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</label>
 | 
			
		||||
										<label className="form-selectgroup-item flex-fill">
 | 
			
		||||
											<input
 | 
			
		||||
												type="radio"
 | 
			
		||||
												name={field.name}
 | 
			
		||||
												value="444"
 | 
			
		||||
												className="form-selectgroup-input"
 | 
			
		||||
												checked={field.value === "444"}
 | 
			
		||||
												onChange={(e) => form.setFieldValue(field.name, e.target.value)}
 | 
			
		||||
											/>
 | 
			
		||||
											<div className="form-selectgroup-label d-flex align-items-center p-3">
 | 
			
		||||
												<div className="me-3">
 | 
			
		||||
													<span className="form-selectgroup-check" />
 | 
			
		||||
												</div>
 | 
			
		||||
												<div>
 | 
			
		||||
													<T id="settings.default-site.444" />
 | 
			
		||||
												</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</label>
 | 
			
		||||
										<label className="form-selectgroup-item flex-fill">
 | 
			
		||||
											<input
 | 
			
		||||
												type="radio"
 | 
			
		||||
												name={field.name}
 | 
			
		||||
												value="redirect"
 | 
			
		||||
												className="form-selectgroup-input"
 | 
			
		||||
												checked={field.value === "redirect"}
 | 
			
		||||
												onChange={(e) => form.setFieldValue(field.name, e.target.value)}
 | 
			
		||||
											/>
 | 
			
		||||
											<div className="form-selectgroup-label d-flex align-items-center p-3">
 | 
			
		||||
												<div className="me-3">
 | 
			
		||||
													<span className="form-selectgroup-check" />
 | 
			
		||||
												</div>
 | 
			
		||||
												<div>
 | 
			
		||||
													<T id="settings.default-site.redirect" />
 | 
			
		||||
												</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</label>
 | 
			
		||||
										<label className="form-selectgroup-item flex-fill">
 | 
			
		||||
											<input
 | 
			
		||||
												type="radio"
 | 
			
		||||
												name={field.name}
 | 
			
		||||
												value="html"
 | 
			
		||||
												className="form-selectgroup-input"
 | 
			
		||||
												checked={field.value === "html"}
 | 
			
		||||
												onChange={(e) => form.setFieldValue(field.name, e.target.value)}
 | 
			
		||||
											/>
 | 
			
		||||
											<div className="form-selectgroup-label d-flex align-items-center p-3">
 | 
			
		||||
												<div className="me-3">
 | 
			
		||||
													<span className="form-selectgroup-check" />
 | 
			
		||||
												</div>
 | 
			
		||||
												<div>
 | 
			
		||||
													<T id="settings.default-site.redirect" />
 | 
			
		||||
												</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</label>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							)}
 | 
			
		||||
						</Field>
 | 
			
		||||
						{values.value === "redirect" && (
 | 
			
		||||
							<Field name="redirect" validate={validateString(1, 255)}>
 | 
			
		||||
								{({ field, form }: any) => (
 | 
			
		||||
									<div className="mt-5 mb-3">
 | 
			
		||||
										<label className="form-label" htmlFor="setting-host-unknown">
 | 
			
		||||
											<T id="settings.default-site.redirect" />
 | 
			
		||||
										</label>
 | 
			
		||||
										<div>
 | 
			
		||||
											<input
 | 
			
		||||
												id="redirect"
 | 
			
		||||
												type="text"
 | 
			
		||||
												placeholder="https://"
 | 
			
		||||
												required
 | 
			
		||||
												autoComplete="off"
 | 
			
		||||
												className="form-control"
 | 
			
		||||
												{...field}
 | 
			
		||||
											/>
 | 
			
		||||
											{form.errors.redirect ? (
 | 
			
		||||
												<div className="invalid-feedback">
 | 
			
		||||
													{form.errors.redirect && form.touched.redirect
 | 
			
		||||
														? form.errors.redirect
 | 
			
		||||
														: null}
 | 
			
		||||
												</div>
 | 
			
		||||
											) : null}
 | 
			
		||||
										</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								)}
 | 
			
		||||
							</Field>
 | 
			
		||||
						)}
 | 
			
		||||
						{values.value === "html" && (
 | 
			
		||||
							<Field name="html" validate={validateString(1)}>
 | 
			
		||||
								{({ field, form }: any) => (
 | 
			
		||||
									<div className="mt-5 mb-3">
 | 
			
		||||
										<label className="form-label" htmlFor="setting-host-unknown">
 | 
			
		||||
											<T id="settings.default-site.html" />
 | 
			
		||||
										</label>
 | 
			
		||||
										<div>
 | 
			
		||||
											<CodeEditor
 | 
			
		||||
												// Believe it or not, 'html' sucks yet 'php' renders the html
 | 
			
		||||
												// content much nicer.
 | 
			
		||||
												language="php"
 | 
			
		||||
												placeholder={intl.formatMessage({
 | 
			
		||||
													id: "settings.default-site.html.placeholder",
 | 
			
		||||
												})}
 | 
			
		||||
												padding={15}
 | 
			
		||||
												data-color-mode="dark"
 | 
			
		||||
												minHeight={300}
 | 
			
		||||
												indentWidth={2}
 | 
			
		||||
												style={{
 | 
			
		||||
													fontFamily:
 | 
			
		||||
														"ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
 | 
			
		||||
													borderRadius: "0.3rem",
 | 
			
		||||
													minHeight: "300px",
 | 
			
		||||
													backgroundColor: "var(--tblr-bg-surface-dark)",
 | 
			
		||||
												}}
 | 
			
		||||
												{...field}
 | 
			
		||||
											/>
 | 
			
		||||
											{form.errors.html ? (
 | 
			
		||||
												<div className="invalid-feedback">
 | 
			
		||||
													{form.errors.html && form.touched.html ? form.errors.html : null}
 | 
			
		||||
												</div>
 | 
			
		||||
											) : null}
 | 
			
		||||
										</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								)}
 | 
			
		||||
							</Field>
 | 
			
		||||
						)}
 | 
			
		||||
					</div>
 | 
			
		||||
					<div className="card-footer bg-transparent mt-auto">
 | 
			
		||||
						<div className="btn-list justify-content-end">
 | 
			
		||||
							<Button
 | 
			
		||||
								type="submit"
 | 
			
		||||
								actionType="primary"
 | 
			
		||||
								className="ms-auto bg-teal"
 | 
			
		||||
								data-bs-dismiss="modal"
 | 
			
		||||
								isLoading={isSubmitting}
 | 
			
		||||
								disabled={isSubmitting}
 | 
			
		||||
							>
 | 
			
		||||
								<T id="save" />
 | 
			
		||||
							</Button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</Form>
 | 
			
		||||
			)}
 | 
			
		||||
		</Formik>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								frontend/src/pages/Settings/Layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								frontend/src/pages/Settings/Layout.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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 (
 | 
			
		||||
		<div className="card mt-4">
 | 
			
		||||
			<div className="card-status-top bg-teal" />
 | 
			
		||||
			<div className="card-table">
 | 
			
		||||
				<div className="card-header">
 | 
			
		||||
					<div className="row w-full">
 | 
			
		||||
						<h2 className="mt-1 mb-0">
 | 
			
		||||
							<T id="settings" />
 | 
			
		||||
						</h2>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="row g-0">
 | 
			
		||||
					<div className="col-12 col-md-3 border-end">
 | 
			
		||||
						<div className="card-body mt-0 pt-0">
 | 
			
		||||
							<div className="list-group list-group-transparent">
 | 
			
		||||
								<a
 | 
			
		||||
									href="#"
 | 
			
		||||
									className="list-group-item list-group-item-action d-flex align-items-center active"
 | 
			
		||||
									onClick={(e) => e.preventDefault()}
 | 
			
		||||
								>
 | 
			
		||||
									<T id="settings.default-site" />
 | 
			
		||||
								</a>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div className="col-12 col-md-9 d-flex flex-column">
 | 
			
		||||
						<DefaultSite />
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react";
 | 
			
		||||
import { T } from "src/locale";
 | 
			
		||||
 | 
			
		||||
export default function SettingTable() {
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="card mt-4">
 | 
			
		||||
			<div className="card-status-top bg-teal" />
 | 
			
		||||
			<div className="card-table">
 | 
			
		||||
				<div className="card-header">
 | 
			
		||||
					<div className="row w-full">
 | 
			
		||||
						<h2 className="mt-1 mb-0">
 | 
			
		||||
							<T id="settings" />
 | 
			
		||||
						</h2>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div id="advanced-table">
 | 
			
		||||
					<div className="table-responsive">
 | 
			
		||||
						<table className="table table-vcenter table-selectable">
 | 
			
		||||
							<thead>
 | 
			
		||||
								<tr>
 | 
			
		||||
									<th className="w-1" />
 | 
			
		||||
									<th>
 | 
			
		||||
										<button type="button" className="table-sort d-flex justify-content-between">
 | 
			
		||||
											Source
 | 
			
		||||
										</button>
 | 
			
		||||
									</th>
 | 
			
		||||
									<th>
 | 
			
		||||
										<button type="button" className="table-sort d-flex justify-content-between">
 | 
			
		||||
											Destination
 | 
			
		||||
										</button>
 | 
			
		||||
									</th>
 | 
			
		||||
									<th>
 | 
			
		||||
										<button type="button" className="table-sort d-flex justify-content-between">
 | 
			
		||||
											SSL
 | 
			
		||||
										</button>
 | 
			
		||||
									</th>
 | 
			
		||||
									<th>
 | 
			
		||||
										<button type="button" className="table-sort d-flex justify-content-between">
 | 
			
		||||
											Access
 | 
			
		||||
										</button>
 | 
			
		||||
									</th>
 | 
			
		||||
									<th>
 | 
			
		||||
										<button type="button" className="table-sort d-flex justify-content-between">
 | 
			
		||||
											Status
 | 
			
		||||
										</button>
 | 
			
		||||
									</th>
 | 
			
		||||
									<th className="w-1" />
 | 
			
		||||
								</tr>
 | 
			
		||||
							</thead>
 | 
			
		||||
							<tbody className="table-tbody">
 | 
			
		||||
								<tr>
 | 
			
		||||
									<td data-label="Owner">
 | 
			
		||||
										<div className="d-flex py-1 align-items-center">
 | 
			
		||||
											<span
 | 
			
		||||
												className="avatar avatar-2 me-2"
 | 
			
		||||
												style={{
 | 
			
		||||
													backgroundImage:
 | 
			
		||||
														"url(//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm)",
 | 
			
		||||
												}}
 | 
			
		||||
											/>
 | 
			
		||||
										</div>
 | 
			
		||||
									</td>
 | 
			
		||||
									<td data-label="Destination">
 | 
			
		||||
										<div className="flex-fill">
 | 
			
		||||
											<div className="font-weight-medium">
 | 
			
		||||
												<span className="badge badge-lg domain-name">blog.jc21.com</span>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div className="text-secondary mt-1">Created: 20th September 2024</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									</td>
 | 
			
		||||
									<td data-label="Source">http://172.17.0.1:3001</td>
 | 
			
		||||
									<td data-label="SSL">Let's Encrypt</td>
 | 
			
		||||
									<td data-label="Access">Public</td>
 | 
			
		||||
									<td data-label="Status">
 | 
			
		||||
										<span className="badge bg-lime-lt">Online</span>
 | 
			
		||||
									</td>
 | 
			
		||||
									<td data-label="Status" className="text-end">
 | 
			
		||||
										<span className="dropdown">
 | 
			
		||||
											<button
 | 
			
		||||
												type="button"
 | 
			
		||||
												className="btn dropdown-toggle btn-action btn-sm px-1"
 | 
			
		||||
												data-bs-boundary="viewport"
 | 
			
		||||
												data-bs-toggle="dropdown"
 | 
			
		||||
											>
 | 
			
		||||
												<IconDotsVertical />
 | 
			
		||||
											</button>
 | 
			
		||||
											<div className="dropdown-menu dropdown-menu-end">
 | 
			
		||||
												<span className="dropdown-header">Proxy Host #2</span>
 | 
			
		||||
												<a className="dropdown-item" href="#">
 | 
			
		||||
													<IconEdit size={16} />
 | 
			
		||||
													Edit
 | 
			
		||||
												</a>
 | 
			
		||||
												<a className="dropdown-item" href="#">
 | 
			
		||||
													<IconPower size={16} />
 | 
			
		||||
													Disable
 | 
			
		||||
												</a>
 | 
			
		||||
												<div className="dropdown-divider" />
 | 
			
		||||
												<a className="dropdown-item" href="#">
 | 
			
		||||
													<IconTrash size={16} />
 | 
			
		||||
													Delete
 | 
			
		||||
												</a>
 | 
			
		||||
											</div>
 | 
			
		||||
										</span>
 | 
			
		||||
									</td>
 | 
			
		||||
								</tr>
 | 
			
		||||
							</tbody>
 | 
			
		||||
						</table>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import { HasPermission } from "src/components";
 | 
			
		||||
import SettingTable from "./SettingTable";
 | 
			
		||||
import Layout from "./Layout";
 | 
			
		||||
 | 
			
		||||
const Settings = () => {
 | 
			
		||||
	return (
 | 
			
		||||
		<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo>
 | 
			
		||||
			<SettingTable />
 | 
			
		||||
			<Layout />
 | 
			
		||||
		</HasPermission>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user