mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 15:53:33 +00:00 
			
		
		
		
	Settings polish
This commit is contained in:
		| @@ -196,10 +196,10 @@ export interface Stream { | |||||||
|  |  | ||||||
| export interface Setting { | export interface Setting { | ||||||
| 	id: string; | 	id: string; | ||||||
| 	name: string; | 	name?: string; | ||||||
| 	description: string; | 	description?: string; | ||||||
| 	value: string; | 	value: string; | ||||||
| 	meta: Record<string, any>; | 	meta?: Record<string, any>; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface DNSProvider { | export interface DNSProvider { | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ export * from "./useProxyHost"; | |||||||
| export * from "./useProxyHosts"; | export * from "./useProxyHosts"; | ||||||
| export * from "./useRedirectionHost"; | export * from "./useRedirectionHost"; | ||||||
| export * from "./useRedirectionHosts"; | export * from "./useRedirectionHosts"; | ||||||
|  | export * from "./useSetting"; | ||||||
| export * from "./useStream"; | export * from "./useStream"; | ||||||
| export * from "./useStreams"; | export * from "./useStreams"; | ||||||
| export * from "./useTheme"; | 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.admin": "Administrator", | ||||||
|   "role.standard-user": "Standard User", |   "role.standard-user": "Standard User", | ||||||
|   "save": "Save", |   "save": "Save", | ||||||
|  |   "setting": "Setting", | ||||||
|   "settings": "Settings", |   "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.preamble": "Get started by creating your admin account.", | ||||||
|   "setup.title": "Welcome!", |   "setup.title": "Welcome!", | ||||||
|   "sign-in": "Sign in", |   "sign-in": "Sign in", | ||||||
|   | |||||||
| @@ -506,9 +506,36 @@ | |||||||
| 	"save": { | 	"save": { | ||||||
| 		"defaultMessage": "Save" | 		"defaultMessage": "Save" | ||||||
| 	}, | 	}, | ||||||
|  | 	"setting": { | ||||||
|  | 		"defaultMessage": "Setting" | ||||||
|  | 	}, | ||||||
| 	"settings": { | 	"settings": { | ||||||
| 		"defaultMessage": "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": { | 	"setup.preamble": { | ||||||
| 		"defaultMessage": "Get started by creating your admin account." | 		"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 { HasPermission } from "src/components"; | ||||||
| import SettingTable from "./SettingTable"; | import Layout from "./Layout"; | ||||||
|  |  | ||||||
| const Settings = () => { | const Settings = () => { | ||||||
| 	return ( | 	return ( | ||||||
| 		<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo> | 		<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo> | ||||||
| 			<SettingTable /> | 			<Layout /> | ||||||
| 		</HasPermission> | 		</HasPermission> | ||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user