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