mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 07:43:33 +00:00 
			
		
		
		
	Various tweaks and backend improvements
This commit is contained in:
		| @@ -24,7 +24,7 @@ const certbotLogsDir = "/data/logs"; | ||||
| const certbotWorkDir = "/tmp/letsencrypt-lib"; | ||||
|  | ||||
| const omissions = () => { | ||||
| 	return ["is_deleted", "owner.is_deleted"]; | ||||
| 	return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials"]; | ||||
| }; | ||||
|  | ||||
| const internalCertificate = { | ||||
| @@ -122,7 +122,7 @@ const internalCertificate = { | ||||
| 		} | ||||
|  | ||||
| 		// this command really should clean up and delete the cert if it can't fully succeed | ||||
| 		const certificate = await certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); | ||||
| 		const certificate = await certificateModel.query().insertAndFetch(data); | ||||
|  | ||||
| 		try { | ||||
| 			if (certificate.provider === "letsencrypt") { | ||||
| @@ -202,6 +202,9 @@ const internalCertificate = { | ||||
| 					savedRow.meta = _.assign({}, savedRow.meta, { | ||||
| 						letsencrypt_certificate: certInfo, | ||||
| 					}); | ||||
|  | ||||
| 					await internalCertificate.addCreatedAuditLog(access, certificate.id, savedRow); | ||||
|  | ||||
| 					return savedRow; | ||||
| 				} catch (err) { | ||||
| 					// Delete the certificate from the database if it was not created successfully | ||||
| @@ -218,14 +221,18 @@ const internalCertificate = { | ||||
| 		data.meta = _.assign({}, data.meta || {}, certificate.meta); | ||||
|  | ||||
| 		// Add to audit log | ||||
| 		await internalCertificate.addCreatedAuditLog(access, certificate.id, utils.omitRow(omissions())(data)); | ||||
|  | ||||
| 		return utils.omitRow(omissions())(certificate); | ||||
| 	}, | ||||
|  | ||||
| 	addCreatedAuditLog: async (access, certificate_id, meta) => { | ||||
| 		await internalAuditLog.add(access, { | ||||
| 			action: "created", | ||||
| 			object_type: "certificate", | ||||
| 			object_id: certificate.id, | ||||
| 			meta: data, | ||||
| 			object_id: certificate_id, | ||||
| 			meta: meta, | ||||
| 		}); | ||||
|  | ||||
| 		return certificate; | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| @@ -285,10 +292,7 @@ const internalCertificate = { | ||||
| 			.query() | ||||
| 			.where("is_deleted", 0) | ||||
| 			.andWhere("id", data.id) | ||||
| 			.allowGraph("[owner]") | ||||
| 			.allowGraph("[proxy_hosts]") | ||||
| 			.allowGraph("[redirection_hosts]") | ||||
| 			.allowGraph("[dead_hosts]") | ||||
| 			.allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") | ||||
| 			.first(); | ||||
|  | ||||
| 		if (accessData.permission_visibility !== "all") { | ||||
| @@ -305,7 +309,24 @@ const internalCertificate = { | ||||
| 		} | ||||
| 		// Custom omissions | ||||
| 		if (typeof data.omit !== "undefined" && data.omit !== null) { | ||||
| 			return _.omit(row, data.omit); | ||||
| 			return _.omit(row, [...data.omit]); | ||||
| 		} | ||||
|  | ||||
| 		return internalCertificate.cleanExpansions(row); | ||||
| 	}, | ||||
|  | ||||
| 	cleanExpansions: (row) => { | ||||
| 		if (typeof row.proxy_hosts !== "undefined") { | ||||
| 			row.proxy_hosts = utils.omitRows(["is_deleted"])(row.proxy_hosts); | ||||
| 		} | ||||
| 		if (typeof row.redirection_hosts !== "undefined") { | ||||
| 			row.redirection_hosts = utils.omitRows(["is_deleted"])(row.redirection_hosts); | ||||
| 		} | ||||
| 		if (typeof row.dead_hosts !== "undefined") { | ||||
| 			row.dead_hosts = utils.omitRows(["is_deleted"])(row.dead_hosts); | ||||
| 		} | ||||
| 		if (typeof row.streams !== "undefined") { | ||||
| 			row.streams = utils.omitRows(["is_deleted"])(row.streams); | ||||
| 		} | ||||
| 		return row; | ||||
| 	}, | ||||
| @@ -415,7 +436,7 @@ const internalCertificate = { | ||||
| 			.query() | ||||
| 			.where("is_deleted", 0) | ||||
| 			.groupBy("id") | ||||
| 			.allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts]") | ||||
| 			.allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") | ||||
| 			.orderBy("nice_name", "ASC"); | ||||
|  | ||||
| 		if (accessData.permission_visibility !== "all") { | ||||
| @@ -433,7 +454,11 @@ const internalCertificate = { | ||||
| 			query.withGraphFetched(`[${expand.join(", ")}]`); | ||||
| 		} | ||||
|  | ||||
| 		return await query.then(utils.omitRows(omissions())); | ||||
| 		const r = await query.then(utils.omitRows(omissions())); | ||||
| 		for (let i = 0; i < r.length; i++) { | ||||
| 			r[i] = internalCertificate.cleanExpansions(r[i]); | ||||
| 		} | ||||
| 		return r; | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import deadHostModel from "./dead_host.js"; | ||||
| import now from "./now_helper.js"; | ||||
| import proxyHostModel from "./proxy_host.js"; | ||||
| import redirectionHostModel from "./redirection_host.js"; | ||||
| import streamModel from "./stream.js"; | ||||
| import userModel from "./user.js"; | ||||
|  | ||||
| Model.knex(db); | ||||
| @@ -114,6 +115,17 @@ class Certificate extends Model { | ||||
| 					qb.where("redirection_host.is_deleted", 0); | ||||
| 				}, | ||||
| 			}, | ||||
| 			streams: { | ||||
| 				relation: Model.HasManyRelation, | ||||
| 				modelClass: streamModel, | ||||
| 				join: { | ||||
| 					from: "certificate.id", | ||||
| 					to: "stream.certificate_id", | ||||
| 				}, | ||||
| 				modify: (qb) => { | ||||
| 					qb.where("stream.is_deleted", 0); | ||||
| 				}, | ||||
| 			}, | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| export type AccessListExpansion = "owner" | "items" | "clients"; | ||||
| export type AuditLogExpansion = "user"; | ||||
| export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts"; | ||||
| export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts" | "streams"; | ||||
| export type HostExpansion = "owner" | "certificate"; | ||||
| export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; | ||||
| export type UserExpansion = "permissions"; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import OverlayTrigger from "react-bootstrap/OverlayTrigger"; | ||||
| import Popover from "react-bootstrap/Popover"; | ||||
| import type { DeadHost, ProxyHost, RedirectionHost } from "src/api/backend"; | ||||
| import type { DeadHost, ProxyHost, RedirectionHost, Stream } from "src/api/backend"; | ||||
| import { T } from "src/locale"; | ||||
|  | ||||
| const getSection = (title: string, items: ProxyHost[] | RedirectionHost[] | DeadHost[]) => { | ||||
| @@ -23,13 +23,34 @@ const getSection = (title: string, items: ProxyHost[] | RedirectionHost[] | Dead | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| const getSectionStream = (items: Stream[]) => { | ||||
| 	if (items.length === 0) { | ||||
| 		return null; | ||||
| 	} | ||||
| 	return ( | ||||
| 		<> | ||||
| 			<div> | ||||
| 				<strong> | ||||
| 					<T id="streams" /> | ||||
| 				</strong> | ||||
| 			</div> | ||||
| 			{items.map((stream) => ( | ||||
| 				<div key={stream.id} className="ms-1"> | ||||
| 					{stream.forwardingHost}:{stream.forwardingPort} | ||||
| 				</div> | ||||
| 			))} | ||||
| 		</> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| interface Props { | ||||
| 	proxyHosts: ProxyHost[]; | ||||
| 	redirectionHosts: RedirectionHost[]; | ||||
| 	deadHosts: DeadHost[]; | ||||
| 	streams: Stream[]; | ||||
| } | ||||
| export function CertificateInUseFormatter({ proxyHosts, redirectionHosts, deadHosts }: Props) { | ||||
| 	const totalCount = proxyHosts?.length + redirectionHosts?.length + deadHosts?.length; | ||||
| export function CertificateInUseFormatter({ proxyHosts, redirectionHosts, deadHosts, streams }: Props) { | ||||
| 	const totalCount = proxyHosts?.length + redirectionHosts?.length + deadHosts?.length + streams?.length; | ||||
| 	if (totalCount === 0) { | ||||
| 		return ( | ||||
| 			<span className="badge bg-red-lt"> | ||||
| @@ -41,6 +62,7 @@ export function CertificateInUseFormatter({ proxyHosts, redirectionHosts, deadHo | ||||
| 	proxyHosts.sort(); | ||||
| 	redirectionHosts.sort(); | ||||
| 	deadHosts.sort(); | ||||
| 	streams.sort(); | ||||
|  | ||||
| 	const popover = ( | ||||
| 		<Popover id="popover-basic"> | ||||
| @@ -48,6 +70,7 @@ export function CertificateInUseFormatter({ proxyHosts, redirectionHosts, deadHo | ||||
| 				{getSection("proxy-hosts", proxyHosts)} | ||||
| 				{getSection("redirection-hosts", redirectionHosts)} | ||||
| 				{getSection("dead-hosts", deadHosts)} | ||||
| 				{getSectionStream(streams)} | ||||
| 			</Popover.Body> | ||||
| 		</Popover> | ||||
| 	); | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react"; | ||||
| import cn from "classnames"; | ||||
| import type { AuditLog } from "src/api/backend"; | ||||
| import { DateTimeFormat, T } from "src/locale"; | ||||
|  | ||||
| @@ -32,7 +33,7 @@ const getColorForAction = (action: string) => { | ||||
| }; | ||||
|  | ||||
| const getIcon = (row: AuditLog) => { | ||||
| 	const c = getColorForAction(row.action); | ||||
| 	const c = cn(getColorForAction(row.action), "me-1"); | ||||
| 	let ico = null; | ||||
| 	switch (row.objectType) { | ||||
| 		case "user": | ||||
|   | ||||
| @@ -52,6 +52,7 @@ const useSetDeadHost = () => { | ||||
| 			queryClient.invalidateQueries({ queryKey: ["dead-hosts"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["host-report"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["certificates"] }); | ||||
| 		}, | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -59,6 +59,7 @@ const useSetProxyHost = () => { | ||||
| 			queryClient.invalidateQueries({ queryKey: ["proxy-hosts"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["host-report"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["certificates"] }); | ||||
| 		}, | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -63,6 +63,7 @@ const useSetRedirectionHost = () => { | ||||
| 			queryClient.invalidateQueries({ queryKey: ["redirection-hosts"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["host-report"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["certificates"] }); | ||||
| 		}, | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -48,6 +48,7 @@ const useSetStream = () => { | ||||
| 			queryClient.invalidateQueries({ queryKey: ["streams"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["host-report"] }); | ||||
| 			queryClient.invalidateQueries({ queryKey: ["certificates"] }); | ||||
| 		}, | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -79,6 +79,7 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload, | ||||
| 							proxyHosts={r.proxyHosts} | ||||
| 							redirectionHosts={r.redirectionHosts} | ||||
| 							deadHosts={r.deadHosts} | ||||
| 							streams={r.streams} | ||||
| 						/> | ||||
| 					); | ||||
| 				}, | ||||
|   | ||||
| @@ -22,6 +22,7 @@ export default function TableWrapper() { | ||||
| 		"dead_hosts", | ||||
| 		"proxy_hosts", | ||||
| 		"redirection_hosts", | ||||
| 		"streams", | ||||
| 	]); | ||||
|  | ||||
| 	if (isLoading) { | ||||
|   | ||||
| @@ -124,7 +124,6 @@ const Dashboard = () => { | ||||
| - check permissions in all places | ||||
|  | ||||
| More for api, then implement here: | ||||
| - Properly implement refresh tokens | ||||
| - Add error message_18n for all backend errors | ||||
| - minor: certificates expand with hosts needs to omit 'is_deleted' | ||||
| - properly wrap all logger.debug called in isDebug check | ||||
|   | ||||
		Reference in New Issue
	
	Block a user