From 213e9954d602ed5db59efa0ebf12f06944f748c4 Mon Sep 17 00:00:00 2001 From: Mees Muller Date: Fri, 20 Feb 2026 14:41:06 +0100 Subject: [PATCH 1/9] feat(access-list): add optional note field to access list clients and widen modal - Add optional note property to access list clients in backend model and schema - Extend OpenAPI schemas (common + access-list endpoints) to support note - Created necessary migration to add the note row to access_list_client table - Persist note in access_list_client inserts/updates - Render note in nginx config as inline comment for access rules (when present) - Extend frontend AccessListClient model with optional note - Add note input field to Access List modal (Rules tab) - Only include note in payload when non-empty - Add i18n placeholder for note field - Increase Access List modal width for improved usability --- backend/internal/access-list.js | 2 ++ backend/lib/utils.js | 16 ++++++--- .../20260220111310_access_list_client_note.js | 36 +++++++++++++++++++ backend/schema/common.json | 15 ++++++-- .../paths/nginx/access-lists/listID/put.json | 3 +- .../schema/paths/nginx/access-lists/post.json | 4 ++- frontend/src/App.css | 4 +++ frontend/src/api/backend/models.ts | 1 + .../components/Form/AccessClientFields.tsx | 10 ++++++ frontend/src/locale/src/en.json | 3 ++ frontend/src/modals/AccessListModal.tsx | 3 +- 11 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 backend/migrations/20260220111310_access_list_client_note.js diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 60a7105dbe..41ef8d45ad 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -54,6 +54,7 @@ const internalAccessList = { accessListClientModel.query().insert({ access_list_id: row.id, address: client.address, + note: client.note || null, directive: client.directive, }), ); @@ -161,6 +162,7 @@ const internalAccessList = { accessListClientModel.query().insert({ access_list_id: data.id, address: client.address, + note: client.note || null, directive: client.directive, }), ); diff --git a/backend/lib/utils.js b/backend/lib/utils.js index af7ad3c952..f87fd2fae7 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -92,18 +92,26 @@ const getRenderEngine = () => { }); /** - * nginxAccessRule expects the object given to have 2 properties: + * nginxAccessRule expects the object given to have 3 properties: * * directive string * address string + * note string */ renderEngine.registerFilter("nginxAccessRule", (v) => { - if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { - return `${v.directive} ${v.address};`; + if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { + const note = typeof v.note === "string" ? v.note.trim() : ""; + if (note) { + // prevent newline / comment-breaking characters + const safe = note.replace(/[\r\n#;]/g, " ").trim(); + return `${v.directive} ${v.address}; # ${safe}`; } - return ""; + return `${v.directive} ${v.address};`; + } + return ""; }); + return renderEngine; }; diff --git a/backend/migrations/20260220111310_access_list_client_note.js b/backend/migrations/20260220111310_access_list_client_note.js new file mode 100644 index 0000000000..d136b1832e --- /dev/null +++ b/backend/migrations/20260220111310_access_list_client_note.js @@ -0,0 +1,36 @@ +import { migrate as logger } from "../logger.js"; + +const migrateName = "access_list_client_note"; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); + + return knex.schema + .table("access_list_client", (access_list) => { + access_list.string("note", 200).nullable() + }) + .then(() => { + logger.info(`[${migrateName}] access_list_client Table altered`); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); + return Promise.resolve(true); +}; + +export { up, down }; diff --git a/backend/schema/common.json b/backend/schema/common.json index 00b06e005f..bc546a48d9 100644 --- a/backend/schema/common.json +++ b/backend/schema/common.json @@ -156,6 +156,12 @@ ], "example": "192.168.0.11" }, + "note": { + "description": "Note", + "type": "string", + "example": "Home", + "maxLength": 200 + }, "access_items": { "type": "array", "items": { @@ -191,19 +197,24 @@ "address": { "$ref": "#/properties/address" }, + "note": { + "$ref": "#/properties/note" + }, "directive": { "$ref": "#/properties/directive" } }, "example": { "directive": "allow", - "address": "192.168.0.0/24" + "address": "192.168.0.0/24", + "note": "Home" } }, "example": [ { "directive": "allow", - "address": "192.168.0.0/24" + "address": "192.168.0.0/24", + "note": "Home" } ] }, diff --git a/backend/schema/paths/nginx/access-lists/listID/put.json b/backend/schema/paths/nginx/access-lists/listID/put.json index 61e8044013..c06688b0cf 100644 --- a/backend/schema/paths/nginx/access-lists/listID/put.json +++ b/backend/schema/paths/nginx/access-lists/listID/put.json @@ -60,7 +60,8 @@ "clients": [ { "directive": "allow", - "address": "192.168.0.0/24" + "address": "192.168.0.0/24", + "note": "note" } ] } diff --git a/backend/schema/paths/nginx/access-lists/post.json b/backend/schema/paths/nginx/access-lists/post.json index 38b7003a1e..5dd8f71414 100644 --- a/backend/schema/paths/nginx/access-lists/post.json +++ b/backend/schema/paths/nginx/access-lists/post.json @@ -51,7 +51,8 @@ "clients": [ { "directive": "allow", - "address": "192.168.0.0/24" + "address": "192.168.0.0/24", + "note": "note" } ] } @@ -118,6 +119,7 @@ "modified_on": "2024-10-08T22:15:40.000Z", "access_list_id": 1, "address": "127.0.0.1", + "note": "note", "directive": "allow", "meta": {} } diff --git a/frontend/src/App.css b/frontend/src/App.css index ad335caeab..0586cb1e9e 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -13,6 +13,10 @@ --tblr-backdrop-opacity: 0.8 !important; } +.modal-dialog { + max-width: 700px !important; +} + [data-bs-theme="dark"] .modal-content { --tblr-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 2ae0b08348..06894875f9 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -77,6 +77,7 @@ export type AccessListClient = { modifiedOn?: string; accessListId?: number; address: string; + note?: string; directive: "allow" | "deny"; meta?: Record; }; diff --git a/frontend/src/components/Form/AccessClientFields.tsx b/frontend/src/components/Form/AccessClientFields.tsx index 9dda8c3de0..f067f76988 100644 --- a/frontend/src/components/Form/AccessClientFields.tsx +++ b/frontend/src/components/Form/AccessClientFields.tsx @@ -78,6 +78,16 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) { onChange={(e) => handleChange(idx, "address", e.target.value)} placeholder={intl.formatMessage({ id: "access-list.rule-source.placeholder" })} /> + handleChange(idx, "note", e.target.value)} + placeholder={intl.formatMessage({ id: "access-list.rule-source.placeholder-note" })} + />
diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index bb00ac3322..f1f6bb028e 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -83,6 +83,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "E.g. \"Home\" or \"Office\"" + }, "access-list.satisfy-any": { "defaultMessage": "Satisfy Any" }, diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx index 79537f5cd5..65c508db77 100644 --- a/frontend/src/modals/AccessListModal.tsx +++ b/frontend/src/modals/AccessListModal.tsx @@ -63,10 +63,11 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { password: i.password, })); - // Filter out "clients" to only use the "directive" and "address" fields + // Filter out "clients" to only use the "directive", "address" and "note" fields payload.clients = (values.clients || []).map((i: AccessListClient) => ({ directive: i.directive, address: i.address, + ...(i.note && i.note.trim() ? { note: i.note.trim() } : {}), })); setAccessList(payload, { From e85d5a732fba6c786e32b48cde4a08c6a3310587 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:57:04 +0100 Subject: [PATCH 2/9] Added localizations for the address & note placeholders --- frontend/src/locale/src/bg.json | 5 ++++- frontend/src/locale/src/cs.json | 5 ++++- frontend/src/locale/src/de.json | 8 +++++++- frontend/src/locale/src/es.json | 5 ++++- frontend/src/locale/src/fr.json | 8 +++++++- frontend/src/locale/src/ga.json | 5 ++++- frontend/src/locale/src/hu.json | 5 ++++- frontend/src/locale/src/id.json | 5 ++++- frontend/src/locale/src/it.json | 8 +++++++- frontend/src/locale/src/ja.json | 8 +++++++- frontend/src/locale/src/ko.json | 5 ++++- frontend/src/locale/src/nl.json | 8 +++++++- frontend/src/locale/src/no.json | 5 ++++- frontend/src/locale/src/pl.json | 8 +++++++- frontend/src/locale/src/pt.json | 5 ++++- frontend/src/locale/src/ru.json | 8 +++++++- frontend/src/locale/src/sk.json | 5 ++++- frontend/src/locale/src/tr.json | 5 ++++- frontend/src/locale/src/vi.json | 8 +++++++- frontend/src/locale/src/zh.json | 8 +++++++- 20 files changed, 107 insertions(+), 20 deletions(-) diff --git a/frontend/src/locale/src/bg.json b/frontend/src/locale/src/bg.json index 5183fe315b..23c1621348 100644 --- a/frontend/src/locale/src/bg.json +++ b/frontend/src/locale/src/bg.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 или 192.168.1.0/24 или 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Напр. „Дом“ или „Офис“" + }, "access-list.satisfy-any": { "defaultMessage": "Удовлетворяване на което и да е" }, @@ -692,4 +695,4 @@ "users": { "defaultMessage": "Потребители" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/cs.json b/frontend/src/locale/src/cs.json index cd86b678dc..336ca90bde 100644 --- a/frontend/src/locale/src/cs.json +++ b/frontend/src/locale/src/cs.json @@ -83,6 +83,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 nebo 192.168.1.0/24 nebo 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Např. „Domov“ nebo „Kancelář“" + }, "access-list.satisfy-any": { "defaultMessage": "Splnit kterékoliv" }, @@ -767,4 +770,4 @@ "users": { "defaultMessage": "Uživatelé" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json index f654e10858..a196b8ea0b 100644 --- a/frontend/src/locale/src/de.json +++ b/frontend/src/locale/src/de.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Keine Authentifizierung erforderlich" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 oder 192.168.1.0/24 oder 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Z. B. „Zuhause“ oder „Büro“" + }, "access-list.satisfy-any": { "defaultMessage": "Satisfy Any" }, @@ -653,4 +659,4 @@ "users": { "defaultMessage": "Benutzer" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/es.json b/frontend/src/locale/src/es.json index c8b1edb075..44175ce45f 100644 --- a/frontend/src/locale/src/es.json +++ b/frontend/src/locale/src/es.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 o 192.168.1.0/24 o 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "P. ej., «Casa» u «Oficina»" + }, "access-list.satisfy-any": { "defaultMessage": "Satisfacer Cualquiera" }, @@ -689,4 +692,4 @@ "users": { "defaultMessage": "Usuarios" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json index c715c028a6..748444327b 100644 --- a/frontend/src/locale/src/fr.json +++ b/frontend/src/locale/src/fr.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Aucune authentification de base requise" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 ou 192.168.1.0/24 ou 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Par ex. « Domicile » ou « Bureau »" + }, "access-list.satisfy-any": { "defaultMessage": "Valide n'importe quelle règle" }, @@ -644,4 +650,4 @@ "users": { "defaultMessage": "Utilisateurs" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/ga.json b/frontend/src/locale/src/ga.json index 719b863bf0..021857bdfd 100644 --- a/frontend/src/locale/src/ga.json +++ b/frontend/src/locale/src/ga.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 nó 192.168.1.0/24 nó 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "M.sh. \"Baile\" nó \"Oifig\"" + }, "access-list.satisfy-any": { "defaultMessage": "Sásaigh Aon" }, @@ -680,4 +683,4 @@ "users": { "defaultMessage": "Úsáideoirí" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/hu.json b/frontend/src/locale/src/hu.json index 4caf058344..d8e740b144 100644 --- a/frontend/src/locale/src/hu.json +++ b/frontend/src/locale/src/hu.json @@ -83,6 +83,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 vagy 192.168.1.0/24 vagy 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Pl. „Otthon” vagy „Iroda”" + }, "access-list.satisfy-any": { "defaultMessage": "Bármely teljesítése" }, @@ -767,4 +770,4 @@ "users": { "defaultMessage": "Felhasználók" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/id.json b/frontend/src/locale/src/id.json index cb498f0d88..77a1ed8a1b 100644 --- a/frontend/src/locale/src/id.json +++ b/frontend/src/locale/src/id.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 atau 192.168.1.0/24 atau 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Mis. \"Rumah\" atau \"Kantor\"" + }, "access-list.satisfy-any": { "defaultMessage": "Penuhi Salah Satu" }, @@ -680,4 +683,4 @@ "users": { "defaultMessage": "Pengguna" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/it.json b/frontend/src/locale/src/it.json index 7e5ca77113..464791ccb1 100644 --- a/frontend/src/locale/src/it.json +++ b/frontend/src/locale/src/it.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Nessuna autenticazione base richiesta" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 o 192.168.1.0/24 o 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Es. \"Casa\" o \"Ufficio\"" + }, "access-list.satisfy-any": { "defaultMessage": "Soddisfa Qualsiasi" }, @@ -656,4 +662,4 @@ "users": { "defaultMessage": "Utenti" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/ja.json b/frontend/src/locale/src/ja.json index 438dc218d3..4525987b4b 100644 --- a/frontend/src/locale/src/ja.json +++ b/frontend/src/locale/src/ja.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "ベーシック認証を使用しません" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 または 192.168.1.0/24 または 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "例:「自宅」や「オフィス」" + }, "access-list.satisfy-any": { "defaultMessage": "いずれかを満たす" }, @@ -650,4 +656,4 @@ "users": { "defaultMessage": "ユーザー" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/ko.json b/frontend/src/locale/src/ko.json index 9c0093591b..c36d98c563 100644 --- a/frontend/src/locale/src/ko.json +++ b/frontend/src/locale/src/ko.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 / 192.168.1.0/24 / IPv6" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "예: \"집\" 또는 \"사무실\"" + }, "access-list.satisfy-any": { "defaultMessage": "조건 중 하나라도 충족" }, @@ -692,4 +695,4 @@ "users": { "defaultMessage": "사용자" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json index 81c37054ba..c0f06925f1 100644 --- a/frontend/src/locale/src/nl.json +++ b/frontend/src/locale/src/nl.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Geen basisautentificatie vereist" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 of 192.168.1.0/24 of 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Bijv. \"Thuis\" of \"Kantoor\"" + }, "access-list.satisfy-any": { "defaultMessage": "Voldoe aan elke" }, @@ -656,4 +662,4 @@ "users": { "defaultMessage": "Gebruikers" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/no.json b/frontend/src/locale/src/no.json index f14ea54b11..ddeb73dce4 100644 --- a/frontend/src/locale/src/no.json +++ b/frontend/src/locale/src/no.json @@ -83,6 +83,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 eller 192.168.1.0/24 eller 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "F.eks. \"Hjem\" eller \"Kontor\"" + }, "access-list.satisfy-any": { "defaultMessage": "Oppfyll en av kravene" }, @@ -773,4 +776,4 @@ "users": { "defaultMessage": "Brukere" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/pl.json b/frontend/src/locale/src/pl.json index a5fb2ad0be..f8860651cb 100644 --- a/frontend/src/locale/src/pl.json +++ b/frontend/src/locale/src/pl.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Nie wymaga uwierzytelnienia podstawowego" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 lub 192.168.1.0/24 lub 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Np. „Dom” lub „Biuro”" + }, "access-list.satisfy-any": { "defaultMessage": "Spełnij dowolny warunek" }, @@ -659,4 +665,4 @@ "users": { "defaultMessage": "Użytkownicy" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/pt.json b/frontend/src/locale/src/pt.json index 0a789f484e..676d6df8c0 100644 --- a/frontend/src/locale/src/pt.json +++ b/frontend/src/locale/src/pt.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 ou 192.168.1.0/24 ou 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Por ex., \"Casa\" ou \"Escritório\"" + }, "access-list.satisfy-any": { "defaultMessage": "Satisfazer Qualquer" }, @@ -680,4 +683,4 @@ "users": { "defaultMessage": "Utilizadores" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/ru.json b/frontend/src/locale/src/ru.json index 44dff1294c..dc5bfb05af 100644 --- a/frontend/src/locale/src/ru.json +++ b/frontend/src/locale/src/ru.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Без аутентификации" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 или 192.168.1.0/24 или 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Напр. «Дом» или «Офис»" + }, "access-list.satisfy-any": { "defaultMessage": "Любое совпадение" }, @@ -653,4 +659,4 @@ "users": { "defaultMessage": "Пользователи" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/sk.json b/frontend/src/locale/src/sk.json index 8d48cf811e..c42f4882b9 100644 --- a/frontend/src/locale/src/sk.json +++ b/frontend/src/locale/src/sk.json @@ -83,6 +83,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 alebo 192.168.1.0/24 alebo 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Napr. „Domov“ alebo „Kancelária“" + }, "access-list.satisfy-any": { "defaultMessage": "Splniť ktorékoľvek" }, @@ -767,4 +770,4 @@ "users": { "defaultMessage": "Používatelia" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/tr.json b/frontend/src/locale/src/tr.json index 972fa895ec..4509d6b60b 100644 --- a/frontend/src/locale/src/tr.json +++ b/frontend/src/locale/src/tr.json @@ -26,6 +26,9 @@ "access-list.rule-source.placeholder": { "defaultMessage": "192.168.1.100 veya 192.168.1.0/24 veya 2001:0db8::/32" }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Örn. \"Ev\" veya \"Ofis\"" + }, "access-list.satisfy-any": { "defaultMessage": "Herhangi Birini Karşıla" }, @@ -680,4 +683,4 @@ "users": { "defaultMessage": "Kullanıcılar" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/vi.json b/frontend/src/locale/src/vi.json index 32d26d5590..dd1b8dbb7b 100644 --- a/frontend/src/locale/src/vi.json +++ b/frontend/src/locale/src/vi.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "Không cần xác thực cơ bản" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 hoặc 192.168.1.0/24 hoặc 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "Ví dụ: \"Nhà\" hoặc \"Văn phòng\"" + }, "access-list.satisfy-any": { "defaultMessage": "Thỏa mãn điều kiện bất kỳ" }, @@ -656,4 +662,4 @@ "users": { "defaultMessage": "Danh sách người dùng" } -} +} \ No newline at end of file diff --git a/frontend/src/locale/src/zh.json b/frontend/src/locale/src/zh.json index 72494bb64f..c52b274a1b 100644 --- a/frontend/src/locale/src/zh.json +++ b/frontend/src/locale/src/zh.json @@ -23,6 +23,12 @@ "access-list.public.subtitle": { "defaultMessage": "无需基本认证" }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 或 192.168.1.0/24 或 2001:0db8::/32" + }, + "access-list.rule-source.placeholder-note": { + "defaultMessage": "例如:“家庭”或“办公室”" + }, "access-list.satisfy-any": { "defaultMessage": "满足任意条件" }, @@ -659,4 +665,4 @@ "users": { "defaultMessage": "用户列表" } -} +} \ No newline at end of file From 8a472ea1e6008e6297aac64cf60408bdcd7e79b7 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Thu, 14 May 2026 14:21:26 +0200 Subject: [PATCH 3/9] Match row if-statement with head-branch --- backend/internal/access-list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 41ef8d45ad..0b400e8c0f 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -243,7 +243,7 @@ const internalAccessList = { let row = await query.then(utils.omitRow(omissions())); - if (!row || !row.id) { + if (!row?.id) { throw new errs.ItemNotFoundError(thisData.id); } if (!skipMasking && typeof row.items !== "undefined" && row.items) { @@ -270,7 +270,7 @@ const internalAccessList = { expand: ["proxy_hosts", "items", "clients"], }); - if (!row || !row.id) { + if (!row?.id) { throw new errs.ItemNotFoundError(data.id); } From fa9a50b31beea6eabc465119d567d41b19c1781f Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Thu, 14 May 2026 14:45:25 +0200 Subject: [PATCH 4/9] Claude code review: feat/notes --- backend/internal/access-list.js | 8 +++----- backend/lib/utils.js | 19 +++++++++---------- .../20260220111310_access_list_client_note.js | 2 +- frontend/src/locale/src/bg.json | 2 +- frontend/src/locale/src/cs.json | 2 +- frontend/src/locale/src/de.json | 2 +- frontend/src/locale/src/es.json | 2 +- frontend/src/locale/src/fr.json | 2 +- frontend/src/locale/src/ga.json | 2 +- frontend/src/locale/src/hu.json | 2 +- frontend/src/locale/src/id.json | 2 +- frontend/src/locale/src/it.json | 2 +- frontend/src/locale/src/ja.json | 2 +- frontend/src/locale/src/ko.json | 2 +- frontend/src/locale/src/nl.json | 2 +- frontend/src/locale/src/no.json | 2 +- frontend/src/locale/src/pl.json | 2 +- frontend/src/locale/src/pt.json | 2 +- frontend/src/locale/src/ru.json | 2 +- frontend/src/locale/src/sk.json | 2 +- frontend/src/locale/src/tr.json | 2 +- frontend/src/locale/src/vi.json | 2 +- frontend/src/locale/src/zh.json | 2 +- 23 files changed, 33 insertions(+), 36 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 0b400e8c0f..6cfc09be96 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -49,7 +49,7 @@ const internalAccessList = { }); // Clients - data.clients?.map((client) => { + data.clients?.forEach((client) => { promises.push( accessListClientModel.query().insert({ access_list_id: row.id, @@ -58,7 +58,6 @@ const internalAccessList = { directive: client.directive, }), ); - return true; }); await Promise.all(promises); @@ -156,7 +155,7 @@ const internalAccessList = { // Check for clients and add/update/remove them if (typeof data.clients !== "undefined" && data.clients) { const clientPromises = []; - data.clients.map((client) => { + data.clients.forEach((client) => { if (client.address) { clientPromises.push( accessListClientModel.query().insert({ @@ -167,12 +166,11 @@ const internalAccessList = { }), ); } - return true; }); const query = accessListClientModel.query().delete().where("access_list_id", data.id); await query; - // Add new clitens + // Add new clients if (clientPromises.length) { await Promise.all(clientPromises); } diff --git a/backend/lib/utils.js b/backend/lib/utils.js index f87fd2fae7..50e2e86d30 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -99,19 +99,18 @@ const getRenderEngine = () => { * note string */ renderEngine.registerFilter("nginxAccessRule", (v) => { - if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { - const note = typeof v.note === "string" ? v.note.trim() : ""; - if (note) { - // prevent newline / comment-breaking characters - const safe = note.replace(/[\r\n#;]/g, " ").trim(); - return `${v.directive} ${v.address}; # ${safe}`; + if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { + const note = typeof v.note === "string" ? v.note.trim() : ""; + if (note) { + // prevent newline / comment-breaking characters + const safe = note.replace(/[\r\n#;]/g, " ").trim(); + return `${v.directive} ${v.address}; # ${safe}`; + } + return `${v.directive} ${v.address};`; } - return `${v.directive} ${v.address};`; - } - return ""; + return ""; }); - return renderEngine; }; diff --git a/backend/migrations/20260220111310_access_list_client_note.js b/backend/migrations/20260220111310_access_list_client_note.js index d136b1832e..12b93e3213 100644 --- a/backend/migrations/20260220111310_access_list_client_note.js +++ b/backend/migrations/20260220111310_access_list_client_note.js @@ -15,7 +15,7 @@ const up = (knex) => { return knex.schema .table("access_list_client", (access_list) => { - access_list.string("note", 200).nullable() + access_list.string("note", 200).nullable(); }) .then(() => { logger.info(`[${migrateName}] access_list_client Table altered`); diff --git a/frontend/src/locale/src/bg.json b/frontend/src/locale/src/bg.json index 23c1621348..0149bb51d1 100644 --- a/frontend/src/locale/src/bg.json +++ b/frontend/src/locale/src/bg.json @@ -695,4 +695,4 @@ "users": { "defaultMessage": "Потребители" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/cs.json b/frontend/src/locale/src/cs.json index 336ca90bde..146d03875c 100644 --- a/frontend/src/locale/src/cs.json +++ b/frontend/src/locale/src/cs.json @@ -770,4 +770,4 @@ "users": { "defaultMessage": "Uživatelé" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json index a196b8ea0b..7488b4ab38 100644 --- a/frontend/src/locale/src/de.json +++ b/frontend/src/locale/src/de.json @@ -659,4 +659,4 @@ "users": { "defaultMessage": "Benutzer" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/es.json b/frontend/src/locale/src/es.json index 44175ce45f..b3497e0ba5 100644 --- a/frontend/src/locale/src/es.json +++ b/frontend/src/locale/src/es.json @@ -692,4 +692,4 @@ "users": { "defaultMessage": "Usuarios" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json index 6b5a0dc038..d4ea12e49c 100644 --- a/frontend/src/locale/src/fr.json +++ b/frontend/src/locale/src/fr.json @@ -788,4 +788,4 @@ "users": { "defaultMessage": "Utilisateurs" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/ga.json b/frontend/src/locale/src/ga.json index 021857bdfd..1bce5e0df5 100644 --- a/frontend/src/locale/src/ga.json +++ b/frontend/src/locale/src/ga.json @@ -683,4 +683,4 @@ "users": { "defaultMessage": "Úsáideoirí" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/hu.json b/frontend/src/locale/src/hu.json index d8e740b144..29051d8ca2 100644 --- a/frontend/src/locale/src/hu.json +++ b/frontend/src/locale/src/hu.json @@ -770,4 +770,4 @@ "users": { "defaultMessage": "Felhasználók" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/id.json b/frontend/src/locale/src/id.json index 77a1ed8a1b..3575a0033d 100644 --- a/frontend/src/locale/src/id.json +++ b/frontend/src/locale/src/id.json @@ -683,4 +683,4 @@ "users": { "defaultMessage": "Pengguna" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/it.json b/frontend/src/locale/src/it.json index 464791ccb1..02ca037817 100644 --- a/frontend/src/locale/src/it.json +++ b/frontend/src/locale/src/it.json @@ -662,4 +662,4 @@ "users": { "defaultMessage": "Utenti" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/ja.json b/frontend/src/locale/src/ja.json index 4525987b4b..84cc9ab433 100644 --- a/frontend/src/locale/src/ja.json +++ b/frontend/src/locale/src/ja.json @@ -656,4 +656,4 @@ "users": { "defaultMessage": "ユーザー" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/ko.json b/frontend/src/locale/src/ko.json index c36d98c563..a56e654439 100644 --- a/frontend/src/locale/src/ko.json +++ b/frontend/src/locale/src/ko.json @@ -695,4 +695,4 @@ "users": { "defaultMessage": "사용자" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json index 5cfa6b98da..5a9f319010 100644 --- a/frontend/src/locale/src/nl.json +++ b/frontend/src/locale/src/nl.json @@ -776,4 +776,4 @@ "users": { "defaultMessage": "Gebruikers" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/no.json b/frontend/src/locale/src/no.json index ddeb73dce4..3e43293dc0 100644 --- a/frontend/src/locale/src/no.json +++ b/frontend/src/locale/src/no.json @@ -776,4 +776,4 @@ "users": { "defaultMessage": "Brukere" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/pl.json b/frontend/src/locale/src/pl.json index f8860651cb..d6ec523fcf 100644 --- a/frontend/src/locale/src/pl.json +++ b/frontend/src/locale/src/pl.json @@ -665,4 +665,4 @@ "users": { "defaultMessage": "Użytkownicy" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/pt.json b/frontend/src/locale/src/pt.json index 676d6df8c0..a94a7872df 100644 --- a/frontend/src/locale/src/pt.json +++ b/frontend/src/locale/src/pt.json @@ -683,4 +683,4 @@ "users": { "defaultMessage": "Utilizadores" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/ru.json b/frontend/src/locale/src/ru.json index 2cdcd2e8f3..523832919b 100644 --- a/frontend/src/locale/src/ru.json +++ b/frontend/src/locale/src/ru.json @@ -776,4 +776,4 @@ "users": { "defaultMessage": "Пользователи" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/sk.json b/frontend/src/locale/src/sk.json index c42f4882b9..874fa81b02 100644 --- a/frontend/src/locale/src/sk.json +++ b/frontend/src/locale/src/sk.json @@ -770,4 +770,4 @@ "users": { "defaultMessage": "Používatelia" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/tr.json b/frontend/src/locale/src/tr.json index 4509d6b60b..b9def934ae 100644 --- a/frontend/src/locale/src/tr.json +++ b/frontend/src/locale/src/tr.json @@ -683,4 +683,4 @@ "users": { "defaultMessage": "Kullanıcılar" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/vi.json b/frontend/src/locale/src/vi.json index dd1b8dbb7b..20279cc1b0 100644 --- a/frontend/src/locale/src/vi.json +++ b/frontend/src/locale/src/vi.json @@ -662,4 +662,4 @@ "users": { "defaultMessage": "Danh sách người dùng" } -} \ No newline at end of file +} diff --git a/frontend/src/locale/src/zh.json b/frontend/src/locale/src/zh.json index c52b274a1b..920605962c 100644 --- a/frontend/src/locale/src/zh.json +++ b/frontend/src/locale/src/zh.json @@ -665,4 +665,4 @@ "users": { "defaultMessage": "用户列表" } -} \ No newline at end of file +} From d1e69dde4b3c329ae570f3a309064b9df4bb67b5 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Thu, 14 May 2026 14:55:40 +0200 Subject: [PATCH 5/9] Fix AccessListModel for optional note --- frontend/src/modals/AccessListModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx index 65c508db77..68d499c85b 100644 --- a/frontend/src/modals/AccessListModal.tsx +++ b/frontend/src/modals/AccessListModal.tsx @@ -67,7 +67,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { payload.clients = (values.clients || []).map((i: AccessListClient) => ({ directive: i.directive, address: i.address, - ...(i.note && i.note.trim() ? { note: i.note.trim() } : {}), + ...(i.note?.trim() ? { note: i.note.trim() } : {}), })); setAccessList(payload, { From 7cebbdfe62e56c33d5be23b317bce3b882a985e5 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Thu, 14 May 2026 15:56:57 +0200 Subject: [PATCH 6/9] Rebased on PR #5421 --- backend/internal/access-list.js | 48 +++++++++++++-------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 6cfc09be96..100d34e513 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -48,20 +48,18 @@ const internalAccessList = { return true; }); - // Clients - data.clients?.forEach((client) => { - promises.push( - accessListClientModel.query().insert({ - access_list_id: row.id, - address: client.address, - note: client.note || null, - directive: client.directive, - }), - ); - }); - await Promise.all(promises); + // Clients + for (const client of data.clients ?? []) { + await accessListClientModel.query().insert({ + access_list_id: row.id, + address: client.address, + note: client.note || null, + directive: client.directive, + }); + } + // re-fetch with expansions const freshRow = await internalAccessList.get( access, @@ -154,25 +152,17 @@ const internalAccessList = { // Check for clients and add/update/remove them if (typeof data.clients !== "undefined" && data.clients) { - const clientPromises = []; - data.clients.forEach((client) => { + await accessListClientModel.query().delete().where("access_list_id", data.id); + + for (const client of data.clients) { if (client.address) { - clientPromises.push( - accessListClientModel.query().insert({ - access_list_id: data.id, - address: client.address, - note: client.note || null, - directive: client.directive, - }), - ); + await accessListClientModel.query().insert({ + access_list_id: data.id, + address: client.address, + note: client.note || null, + directive: client.directive, + }); } - }); - - const query = accessListClientModel.query().delete().where("access_list_id", data.id); - await query; - // Add new clients - if (clientPromises.length) { - await Promise.all(clientPromises); } } From 6006459ba5ebfb85a56d78322d08c411c07af3e2 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Thu, 14 May 2026 16:04:01 +0200 Subject: [PATCH 7/9] Rebase on main --- backend/internal/access-list.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 100d34e513..d1545710ef 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -152,8 +152,9 @@ const internalAccessList = { // Check for clients and add/update/remove them if (typeof data.clients !== "undefined" && data.clients) { - await accessListClientModel.query().delete().where("access_list_id", data.id); - + const query = accessListClientModel.query().delete().where("access_list_id", data.id); + await query; + for (const client of data.clients) { if (client.address) { await accessListClientModel.query().insert({ From ed241824de860a9ba88898931ec455d63dd15bf4 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Thu, 14 May 2026 16:05:09 +0200 Subject: [PATCH 8/9] RM whitespace --- backend/internal/access-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index d1545710ef..e9f3300f25 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -154,7 +154,7 @@ const internalAccessList = { if (typeof data.clients !== "undefined" && data.clients) { const query = accessListClientModel.query().delete().where("access_list_id", data.id); await query; - + for (const client of data.clients) { if (client.address) { await accessListClientModel.query().insert({ From 6fadcd084de2b7f1fb19d8d4b607a71985da8881 Mon Sep 17 00:00:00 2001 From: Mees Muller <89028475+TheMazeIsAmazing@users.noreply.github.com> Date: Fri, 15 May 2026 09:21:52 +0200 Subject: [PATCH 9/9] Set modal size to lg without affecting other modals --- frontend/src/App.css | 4 ---- frontend/src/modals/AccessListModal.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 0586cb1e9e..ad335caeab 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -13,10 +13,6 @@ --tblr-backdrop-opacity: 0.8 !important; } -.modal-dialog { - max-width: 700px !important; -} - [data-bs-theme="dark"] .modal-content { --tblr-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx index 68d499c85b..8114bfc74f 100644 --- a/frontend/src/modals/AccessListModal.tsx +++ b/frontend/src/modals/AccessListModal.tsx @@ -87,7 +87,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { const toggleEnabled = cn(toggleClasses, "bg-cyan"); return ( - + {!isLoading && error && ( {error?.message || "Unknown error"}