From 4a0f21228941aa06baacda3777a24bb4477608ba Mon Sep 17 00:00:00 2001 From: "Dimas R. Wisnu" Date: Fri, 7 Aug 2026 16:40:32 +0700 Subject: [PATCH 1/2] feat: per-path access lists, host logs modal, PostgreSQL support - Per-path access lists: assign different access lists to individual locations on the same proxy host - Host logs modal: view access/error logs from proxy host dropdown - PostgreSQL JSON containment query (@>) for location regeneration - Locale keys: action.logs, column.error --- backend/internal/access-list.js | 101 +++++++++++ backend/internal/proxy-host.js | 163 +++++++++++------- backend/routes/nginx/proxy_hosts.js | 67 +++++++ .../schema/components/proxy-host-object.json | 6 +- frontend/src/api/backend/getProxyHostLogs.ts | 8 + frontend/src/api/backend/index.ts | 1 + frontend/src/api/backend/models.ts | 2 + frontend/src/components/Form/AccessField.tsx | 6 +- .../src/components/Form/LocationsFields.tsx | 14 ++ frontend/src/hooks/index.ts | 1 + frontend/src/hooks/useProxyHostLogs.ts | 12 ++ frontend/src/locale/src/en.json | 6 + frontend/src/modals/HostLogsModal.tsx | 93 ++++++++++ frontend/src/modals/index.ts | 1 + frontend/src/pages/Nginx/ProxyHosts/Table.tsx | 28 ++- .../pages/Nginx/ProxyHosts/TableWrapper.tsx | 3 +- 16 files changed, 434 insertions(+), 78 deletions(-) create mode 100644 frontend/src/api/backend/getProxyHostLogs.ts create mode 100644 frontend/src/hooks/useProxyHostLogs.ts create mode 100644 frontend/src/modals/HostLogsModal.tsx diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 88bf9df523..413ac067af 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -2,7 +2,9 @@ import fs from "node:fs"; import batchflow from "batchflow"; import _ from "lodash"; import errs from "../lib/error.js"; +import { isMysql, isPostgres } from "../lib/config.js"; import utils from "../lib/utils.js"; +import db from "../db.js"; import { access as logger } from "../logger.js"; import accessListModel from "../models/access_list.js"; import accessListAuthModel from "../models/access_list_auth.js"; @@ -15,6 +17,36 @@ const omissions = () => { return ["is_deleted"]; }; +/** + * Find proxy hosts that reference an access list in their locations JSON. + * + * @param {Integer} accessListId + * @returns {Promise} + */ +const getProxyHostsUsingAccessListInLocations = async (accessListId) => { + let result; + if (isMysql()) { + const searchObj = JSON.stringify([{ access_list_id: accessListId }]); + result = await db().raw( + `SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)`, + [searchObj, "$"], + ); + } else if (isPostgres()) { + result = await db().raw( + `SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb`, + [JSON.stringify([{ access_list_id: accessListId }])], + ); + } else { + result = await db().raw( + `SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?`, + [`%"access_list_id":${accessListId}%`], + ); + } + // knex raw() returns [rows, metadata] for MySQL + const rows = Array.isArray(result) && Array.isArray(result[0]) ? result[0] : result; + return rows || []; +}; + const internalAccessList = { /** * @param {Access} access @@ -187,6 +219,44 @@ const internalAccessList = { if (Number.parseInt(freshRow.proxy_host_count, 10)) { await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts); } + + // Also regenerate configs for proxy hosts that reference this access list in their locations + const locationHostRows = await getProxyHostsUsingAccessListInLocations(data.id); + if (locationHostRows && locationHostRows.length) { + const locationHostIds = locationHostRows.map((r) => r.id).filter((id) => { + // Exclude hosts already regenerated above + return !freshRow.proxy_hosts || !freshRow.proxy_hosts.find((h) => h.id === id); + }); + if (locationHostIds.length) { + const locationHosts = await proxyHostModel.query() + .where("is_deleted", 0) + .whereIn("id", locationHostIds) + .allowGraph(proxyHostModel.defaultAllowGraph) + .withGraphFetched("[owner, certificate, access_list.[clients,items]]"); + for (const host of locationHosts) { + // Fetch access lists for locations + if (host.locations && host.locations.length) { + for (let i = 0; i < host.locations.length; i++) { + const loc = host.locations[i]; + if (loc.access_list_id && loc.access_list_id > 0) { + const locAccessList = await accessListModel + .query() + .allowGraph("[clients,items]") + .where("is_deleted", 0) + .andWhere("id", loc.access_list_id) + .withGraphFetched("[clients,items]") + .first(); + if (locAccessList) { + host.locations[i].access_list = locAccessList; + } + } + } + } + } + await internalNginx.bulkGenerateConfigs("proxy_host", locationHosts); + } + } + await internalNginx.reload(); return internalAccessList.maskItems(freshRow); }, @@ -291,6 +361,37 @@ const internalAccessList = { await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); } + // Also handle proxy hosts that reference this access list in their locations JSON + const locationHostRows = await getProxyHostsUsingAccessListInLocations(row.id); + if (locationHostRows && locationHostRows.length) { + const locationHostIds = locationHostRows.map((r) => r.id).filter((id) => { + return !row.proxy_hosts || !row.proxy_hosts.find((h) => h.id === id); + }); + if (locationHostIds.length) { + // Clear the access_list_id in locations JSON for these hosts + for (const hostId of locationHostIds) { + const host = await proxyHostModel.query().where("id", hostId).first(); + if (host && host.locations) { + const updatedLocations = host.locations.map((loc) => { + if (loc.access_list_id === row.id) { + return { ...loc, access_list_id: 0 }; + } + return loc; + }); + await proxyHostModel.query().where("id", hostId).patch({ locations: updatedLocations }); + } + } + + // Re-fetch and regenerate configs + const locationHosts = await proxyHostModel.query() + .where("is_deleted", 0) + .whereIn("id", locationHostIds) + .allowGraph(proxyHostModel.defaultExpand) + .withGraphFetched("[owner, certificate, access_list.[clients,items]]"); + await internalNginx.bulkGenerateConfigs("proxy_host", locationHosts); + } + } + await internalNginx.reload(); // delete the htpasswd file diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 2c159d48ad..96cfab480e 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -2,6 +2,7 @@ import _ from "lodash"; import errs from "../lib/error.js"; import { castJsonIfNeed } from "../lib/helpers.js"; import utils from "../lib/utils.js"; +import accessListModel from "../models/access_list.js"; import proxyHostModel from "../models/proxy_host.js"; import internalAuditLog from "./audit-log.js"; import internalCertificate from "./certificate.js"; @@ -12,6 +13,33 @@ const omissions = () => { return ["is_deleted", "owner.is_deleted"]; }; +/** + * Fetches access lists for each location that has its own access_list_id. + * Attaches the expanded access_list object (with clients and items) to each location. + * + * @param {Object} host + * @returns {Promise} + */ +const fetchLocationAccessLists = async (host) => { + if (!host.locations || !host.locations.length) { + return; + } + for (let i = 0; i < host.locations.length; i++) { + const loc = host.locations[i]; + if (loc.access_list_id && loc.access_list_id > 0) { + const accessList = await accessListModel + .query() + .where("is_deleted", 0) + .andWhere("id", loc.access_list_id) + .withGraphFetched("[clients,items]") + .first(); + if (accessList) { + host.locations[i].access_list = accessList; + } + } + } +}; + const internalProxyHost = { /** * @param {Access} access @@ -83,28 +111,29 @@ const internalProxyHost = { expand: ["certificate", "owner", "access_list.[clients,items]"], }); }) - .then((row) => { - // Configure nginx - return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => { + .then(async (row) => { + await fetchLocationAccessLists(row); + // Configure nginx + return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => { + return row; + }); + }) + .then((row) => { + // Audit log + thisData.meta = _.assign({}, thisData.meta || {}, row.meta); + + // Add to audit log + return internalAuditLog + .add(access, { + action: "created", + object_type: "proxy-host", + object_id: row.id, + meta: thisData, + }) + .then(() => { return row; }); - }) - .then((row) => { - // Audit log - thisData.meta = _.assign({}, thisData.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: "created", - object_type: "proxy-host", - object_id: row.id, - meta: thisData, - }) - .then(() => { - return row; - }); - }); + }); }, /** @@ -202,24 +231,25 @@ const internalProxyHost = { }); }); }) - .then(() => { - return internalProxyHost - .get(access, { - id: thisData.id, - expand: ["owner", "certificate", "access_list.[clients,items]"], - }) - .then((row) => { - if (!row.enabled) { - // No need to add nginx config if host is disabled - return row; - } - // Configure nginx - return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => { - row.meta = new_meta; - return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); - }); + .then(() => { + return internalProxyHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate", "access_list.[clients,items]"], + }) + .then(async (row) => { + if (!row.enabled) { + // No need to add nginx config if host is disabled + return row; + } + await fetchLocationAccessLists(row); + // Configure nginx + return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); }); - }); + }); + }); }, /** @@ -326,39 +356,38 @@ const internalProxyHost = { expand: ["certificate", "owner", "access_list"], }); }) - .then((row) => { - if (!row?.id) { - throw new errs.ItemNotFoundError(data.id); - } - if (row.enabled) { - throw new errs.ValidationError("Host is already enabled"); - } + .then(async (row) => { + if (!row?.id) { + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); + } + + row.enabled = 1; + + await proxyHostModel + .query() + .where("id", row.id) + .patch({ + enabled: 1, + }); - row.enabled = 1; + await fetchLocationAccessLists(row); - return proxyHostModel - .query() - .where("id", row.id) - .patch({ - enabled: 1, - }) - .then(() => { - // Configure nginx - return internalNginx.configure(proxyHostModel, "proxy_host", row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: "enabled", - object_type: "proxy-host", - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; + // Configure nginx + await internalNginx.configure(proxyHostModel, "proxy_host", row); + + // Add to audit log + await internalAuditLog.add(access, { + action: "enabled", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); + + return true; + }); }, /** diff --git a/backend/routes/nginx/proxy_hosts.js b/backend/routes/nginx/proxy_hosts.js index 7045a195cc..ef62cf835a 100644 --- a/backend/routes/nginx/proxy_hosts.js +++ b/backend/routes/nginx/proxy_hosts.js @@ -1,4 +1,5 @@ import express from "express"; +import fs from "node:fs"; import internalProxyHost from "../../internal/proxy-host.js"; import jwtdecode from "../../lib/express/jwt-decode.js"; import apiValidator from "../../lib/validator/api.js"; @@ -206,4 +207,70 @@ router } }); +/** + * Proxy-host logs + * + * /api/nginx/proxy-hosts/123/logs + */ +router + .route("/:host_id/logs") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/proxy-hosts/123/logs + * + * Retrieve logs for a specific proxy-host + */ + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + type: { + type: "string", + enum: ["access", "error"], + }, + }, + }, + { + host_id: req.params.host_id, + type: req.query.type || "access", + }, + ); + + const hostId = Number.parseInt(data.host_id, 10); + const logType = data.type === "error" ? "error" : "access"; + const logFile = `/data/logs/proxy-host-${hostId}_${logType}.log`; + + // Check access permission + await res.locals.access.can("proxy_hosts:get", hostId); + + let logs = ""; + if (fs.existsSync(logFile)) { + const content = fs.readFileSync(logFile, { encoding: "utf8" }); + const lines = content.split("\n"); + // Return last 1000 lines to avoid huge payloads + const maxLines = 1000; + if (lines.length > maxLines) { + logs = lines.slice(-maxLines).join("\n"); + } else { + logs = content; + } + } + + res.status(200).send({ logs }); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + export default router; diff --git a/backend/schema/components/proxy-host-object.json b/backend/schema/components/proxy-host-object.json index 3ac6462136..90e6e23bb2 100644 --- a/backend/schema/components/proxy-host-object.json +++ b/backend/schema/components/proxy-host-object.json @@ -124,6 +124,9 @@ }, "advanced_config": { "type": "string" + }, + "access_list_id": { + "$ref": "../common.json#/properties/access_list_id" } } }, @@ -132,7 +135,8 @@ "path": "/app", "forward_scheme": "http", "forward_host": "example.com", - "forward_port": 80 + "forward_port": 80, + "access_list_id": 0 } ] }, diff --git a/frontend/src/api/backend/getProxyHostLogs.ts b/frontend/src/api/backend/getProxyHostLogs.ts new file mode 100644 index 0000000000..91944c9d72 --- /dev/null +++ b/frontend/src/api/backend/getProxyHostLogs.ts @@ -0,0 +1,8 @@ +import * as api from "./base"; + +export async function getProxyHostLogs(id: number, type: "access" | "error" = "access"): Promise<{ logs: string }> { + return await api.get({ + url: `/nginx/proxy-hosts/${id}/logs`, + params: { type }, + }); +} diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts index 40cb4142fc..6be435b6a8 100644 --- a/frontend/src/api/backend/index.ts +++ b/frontend/src/api/backend/index.ts @@ -27,6 +27,7 @@ export * from "./getDeadHosts"; export * from "./getHealth"; export * from "./getHostsReport"; export * from "./getProxyHost"; +export * from "./getProxyHostLogs"; export * from "./getProxyHosts"; export * from "./getRedirectionHost"; export * from "./getRedirectionHosts"; diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 2ae0b08348..4818f77699 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -103,6 +103,8 @@ export interface ProxyLocation { forwardScheme: string; forwardHost: string; forwardPort: number; + accessListId?: number; + accessList?: AccessList; } export interface ProxyHost { diff --git a/frontend/src/components/Form/AccessField.tsx b/frontend/src/components/Form/AccessField.tsx index afcbd0cf7d..1c1004e711 100644 --- a/frontend/src/components/Form/AccessField.tsx +++ b/frontend/src/components/Form/AccessField.tsx @@ -31,14 +31,18 @@ interface Props { id?: string; name?: string; label?: string; + onFormChange?: (value: number) => void; } -export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) { +export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId", onFormChange }: Props) { const { locale } = useLocaleState(); const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]); const { setFieldValue } = useFormikContext(); const handleChange = (newValue: any, _actionMeta: ActionMeta) => { setFieldValue(name, newValue?.value); + if (onFormChange) { + onFormChange(newValue?.value ?? 0); + } }; const options: AccessOption[] = diff --git a/frontend/src/components/Form/LocationsFields.tsx b/frontend/src/components/Form/LocationsFields.tsx index 4240b1f986..9422bd8933 100644 --- a/frontend/src/components/Form/LocationsFields.tsx +++ b/frontend/src/components/Form/LocationsFields.tsx @@ -5,6 +5,7 @@ import { useFormikContext } from "formik"; import { useState } from "react"; import type { ProxyLocation } from "src/api/backend"; import { intl, T } from "src/locale"; +import { AccessField } from "./AccessField"; import styles from "./LocationsFields.module.css"; interface Props { @@ -22,6 +23,7 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { forwardScheme: "http", forwardHost: "", forwardPort: 80, + accessListId: 0, }; const toggleAdvVisible = (idx: number) => { @@ -44,6 +46,12 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { setFormField(newValues); }; + const handleAccessListChange = (idx: number, accessListId: number) => { + const newValues = values.map((v: ProxyLocation, i: number) => (i === idx ? { ...v, accessListId } : v)); + setValues(newValues); + setFormField(newValues); + }; + const setFormField = (newValues: ProxyLocation[]) => { const filtered = newValues.filter((v: ProxyLocation) => v?.path?.trim() !== ""); setFieldValue(name, filtered); @@ -141,6 +149,12 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { + handleAccessListChange(idx, value)} + /> {advVisible.includes(idx) && (
{ + return useQuery<{ logs: string }, Error>({ + queryKey: ["proxy-host-logs", id, type], + queryFn: () => getProxyHostLogs(id, type), + staleTime: 10_000, + }); +}; + +export { useProxyHostLogs }; diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index bb00ac3322..b5043fc83a 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -122,6 +122,9 @@ "action.enable": { "defaultMessage": "Enable" }, + "action.logs": { + "defaultMessage": "Logs" + }, "action.permissions": { "defaultMessage": "Permissions" }, @@ -248,6 +251,9 @@ "column.access": { "defaultMessage": "Access" }, + "column.error": { + "defaultMessage": "Error" + }, "column.authorization": { "defaultMessage": "Authorization" }, diff --git a/frontend/src/modals/HostLogsModal.tsx b/frontend/src/modals/HostLogsModal.tsx new file mode 100644 index 0000000000..a0df04ee71 --- /dev/null +++ b/frontend/src/modals/HostLogsModal.tsx @@ -0,0 +1,93 @@ +import CodeEditor from "@uiw/react-textarea-code-editor"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, Loading } from "src/components"; +import { useProxyHostLogs } from "src/hooks"; +import { T } from "src/locale"; + +const showHostLogsModal = (id: number) => { + EasyModal.show(HostLogsModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number; +} +const HostLogsModal = EasyModal.create(({ id, visible, remove }: Props) => { + const [logType, setLogType] = useState<"access" | "error">("access"); + const { data, isLoading, error } = useProxyHostLogs(id, logType); + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + + + + + + + + {isLoading ? ( + + ) : ( +
+ +
+ )} +
+ + + +
+ ); +}); + +export { showHostLogsModal }; diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts index a06a0c0d71..902d0d73d0 100644 --- a/frontend/src/modals/index.ts +++ b/frontend/src/modals/index.ts @@ -6,6 +6,7 @@ export * from "./DeleteConfirmModal"; export * from "./DNSCertificateModal"; export * from "./EventDetailsModal"; export * from "./HelpModal"; +export * from "./HostLogsModal"; export * from "./HTTPCertificateModal"; export * from "./PermissionsModal"; export * from "./ProxyHostModal"; diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx index 5af58081ad..ad6714b79d 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -1,4 +1,4 @@ -import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { IconDotsVertical, IconEdit, IconFileText, IconPower, IconTrash } from "@tabler/icons-react"; import { createColumnHelper, getCoreRowModel, @@ -28,9 +28,10 @@ interface Props { onEdit?: (id: number) => void; onDelete?: (id: number) => void; onDisableToggle?: (id: number, enabled: boolean) => void; + onLogs?: (id: number) => void; onNew?: () => void; } -export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) { +export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onLogs, onNew, isFiltered }: Props) { const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -115,19 +116,30 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog data={{ id: info.row.original.id }} /> + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + { e.preventDefault(); - onEdit?.(info.row.original.id); + onLogs?.(info.row.original.id); }} > - - + + - - { @@ -160,7 +172,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }, }), ], - [columnHelper, onEdit, onDisableToggle, onDelete], + [columnHelper, onEdit, onDisableToggle, onDelete, onLogs], ); const [sorting, setSorting] = useState([]); diff --git a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx index 5d6602e2db..7845fb029b 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx @@ -6,7 +6,7 @@ import { deleteProxyHost, toggleProxyHost } from "src/api/backend"; import { Button, HasPermission, LoadingPage } from "src/components"; import { useProxyHosts } from "src/hooks"; import { T } from "src/locale"; -import { showDeleteConfirmModal, showHelpModal, showProxyHostModal } from "src/modals"; +import { showDeleteConfirmModal, showHelpModal, showHostLogsModal, showProxyHostModal } from "src/modals"; import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; import { showObjectSuccess } from "src/notifications"; import Table from "./Table"; @@ -99,6 +99,7 @@ export default function TableWrapper() { isFiltered={!!search} isFetching={isFetching} onEdit={(id: number) => showProxyHostModal(id)} + onLogs={(id: number) => showHostLogsModal(id)} onDelete={(id: number) => { const host = data?.find((h) => h.id === id); showDeleteConfirmModal({ From 35ad8227d765541ac72a3099fb265acd830b4d6e Mon Sep 17 00:00:00 2001 From: "Dimas R. Wisnu" Date: Fri, 7 Aug 2026 17:01:20 +0700 Subject: [PATCH 2/2] fix: resolve biome lint errors - Use optional chaining for nullable checks - Replace template literals with string literals for plain SQL - Add node: protocol to fs/promises import in setup.js --- backend/internal/access-list.js | 18 +++++++++--------- backend/internal/proxy-host.js | 2 +- backend/setup.js | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 413ac067af..f8c4547944 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -28,17 +28,17 @@ const getProxyHostsUsingAccessListInLocations = async (accessListId) => { if (isMysql()) { const searchObj = JSON.stringify([{ access_list_id: accessListId }]); result = await db().raw( - `SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)`, + "SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)", [searchObj, "$"], ); } else if (isPostgres()) { result = await db().raw( - `SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb`, + "SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb", [JSON.stringify([{ access_list_id: accessListId }])], ); } else { result = await db().raw( - `SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?`, + "SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?", [`%"access_list_id":${accessListId}%`], ); } @@ -222,10 +222,10 @@ const internalAccessList = { // Also regenerate configs for proxy hosts that reference this access list in their locations const locationHostRows = await getProxyHostsUsingAccessListInLocations(data.id); - if (locationHostRows && locationHostRows.length) { + if (locationHostRows?.length) { const locationHostIds = locationHostRows.map((r) => r.id).filter((id) => { // Exclude hosts already regenerated above - return !freshRow.proxy_hosts || !freshRow.proxy_hosts.find((h) => h.id === id); + return !freshRow.proxy_hosts?.find((h) => h.id === id); }); if (locationHostIds.length) { const locationHosts = await proxyHostModel.query() @@ -235,7 +235,7 @@ const internalAccessList = { .withGraphFetched("[owner, certificate, access_list.[clients,items]]"); for (const host of locationHosts) { // Fetch access lists for locations - if (host.locations && host.locations.length) { + if (host.locations?.length) { for (let i = 0; i < host.locations.length; i++) { const loc = host.locations[i]; if (loc.access_list_id && loc.access_list_id > 0) { @@ -363,15 +363,15 @@ const internalAccessList = { // Also handle proxy hosts that reference this access list in their locations JSON const locationHostRows = await getProxyHostsUsingAccessListInLocations(row.id); - if (locationHostRows && locationHostRows.length) { + if (locationHostRows?.length) { const locationHostIds = locationHostRows.map((r) => r.id).filter((id) => { - return !row.proxy_hosts || !row.proxy_hosts.find((h) => h.id === id); + return !row.proxy_hosts?.find((h) => h.id === id); }); if (locationHostIds.length) { // Clear the access_list_id in locations JSON for these hosts for (const hostId of locationHostIds) { const host = await proxyHostModel.query().where("id", hostId).first(); - if (host && host.locations) { + if (host?.locations) { const updatedLocations = host.locations.map((loc) => { if (loc.access_list_id === row.id) { return { ...loc, access_list_id: 0 }; diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 96cfab480e..6bd0be1a66 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -21,7 +21,7 @@ const omissions = () => { * @returns {Promise} */ const fetchLocationAccessLists = async (host) => { - if (!host.locations || !host.locations.length) { + if (!host.locations?.length) { return; } for (let i = 0; i < host.locations.length; i++) { diff --git a/backend/setup.js b/backend/setup.js index c0418e170b..362cbdfe43 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -6,7 +6,7 @@ import certificateModel from "./models/certificate.js"; import settingModel from "./models/setting.js"; import userModel from "./models/user.js"; import userPermissionModel from "./models/user_permission.js"; -import fs from "fs/promises"; +import fs from "node:fs/promises"; export const isSetup = async () => { const row = await userModel.query().select("id").where("is_deleted", 0).first();