From 3489107a499ac1c772a1c1904f4db03203d52bc5 Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 6 Nov 2025 23:09:52 +0000 Subject: [PATCH 01/63] Fix typo in shareSeeOnce message --- messages/en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en-US.json b/messages/en-US.json index 97272c6f..0dbb9bc9 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -131,7 +131,7 @@ "expireIn": "Expire In", "neverExpire": "Never expire", "shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.", - "shareSeeOnce": "You will only be able to see this linkonce. Make sure to copy it.", + "shareSeeOnce": "You will only be able to see this link once. Make sure to copy it.", "shareAccessHint": "Anyone with this link can access the resource. Share it with care.", "shareTokenUsage": "See Access Token Usage", "createLink": "Create Link", From 2a7529c39e9baa7eb3fb7dfcdacc289903175175 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 6 Nov 2025 16:48:53 -0800 Subject: [PATCH 02/63] don't delete user --- server/routers/idp/validateOidcCallback.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index 376dd7bc..7d1da1c5 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -365,9 +365,9 @@ export async function validateOidcCallback( if (!existingUserOrgs.length) { // delete the user - await db - .delete(users) - .where(eq(users.userId, existingUser.userId)); + // await db + // .delete(users) + // .where(eq(users.userId, existingUser.userId)); return next( createHttpError( HttpCode.UNAUTHORIZED, From 749cea5a4d4dedfacd825f30d58c4d559bcc9082 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 01:23:55 +0000 Subject: [PATCH 03/63] Bump the dev-patch-updates group across 1 directory with 5 updates Bumps the dev-patch-updates group with 4 updates in the / directory: [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx), [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss), [esbuild](https://github.com/evanw/esbuild) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `@dotenvx/dotenvx` from 1.51.0 to 1.51.1 - [Release notes](https://github.com/dotenvx/dotenvx/releases) - [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md) - [Commits](https://github.com/dotenvx/dotenvx/compare/v1.51.0...v1.51.1) Updates `@tailwindcss/postcss` from 4.1.16 to 4.1.17 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.17/packages/@tailwindcss-postcss) Updates `esbuild` from 0.25.11 to 0.25.12 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.25.11...v0.25.12) Updates `tailwindcss` from 4.1.16 to 4.1.17 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.17/packages/tailwindcss) Updates `typescript-eslint` from 8.46.2 to 8.46.3 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.3/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@dotenvx/dotenvx" dependency-version: 1.51.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-patch-updates - dependency-name: "@tailwindcss/postcss" dependency-version: 4.1.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-patch-updates - dependency-name: esbuild dependency-version: 0.25.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-patch-updates - dependency-name: tailwindcss dependency-version: 4.1.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-patch-updates - dependency-name: typescript-eslint dependency-version: 8.46.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-patch-updates ... Signed-off-by: dependabot[bot] --- package-lock.json | 488 +++++++++++++++++++++++----------------------- package.json | 8 +- 2 files changed, 248 insertions(+), 248 deletions(-) diff --git a/package-lock.json b/package-lock.json index 448f9c18..c0caef8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -110,10 +110,10 @@ "zod-validation-error": "3.5.2" }, "devDependencies": { - "@dotenvx/dotenvx": "1.51.0", + "@dotenvx/dotenvx": "1.51.1", "@esbuild-plugins/tsconfig-paths": "0.1.2", "@react-email/preview-server": "4.3.2", - "@tailwindcss/postcss": "^4.1.16", + "@tailwindcss/postcss": "^4.1.17", "@types/better-sqlite3": "7.6.12", "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", @@ -134,7 +134,7 @@ "@types/ws": "8.18.1", "@types/yargs": "17.0.34", "drizzle-kit": "0.31.6", - "esbuild": "0.25.11", + "esbuild": "0.25.12", "esbuild-node-externals": "1.18.0", "postcss": "^8", "react-email": "4.3.2", @@ -142,7 +142,7 @@ "tsc-alias": "1.8.16", "tsx": "4.20.6", "typescript": "^5", - "typescript-eslint": "^8.46.2" + "typescript-eslint": "^8.46.3" } }, "node_modules/@alloc/quick-lru": { @@ -2003,9 +2003,9 @@ "license": "MIT" }, "node_modules/@dotenvx/dotenvx": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.0.tgz", - "integrity": "sha512-CbMGzyOYSyFF7d4uaeYwO9gpSBzLTnMmSmTVpCZjvpJFV69qYbjYPpzNnCz1mb2wIvEhjWjRwQWuBzTO0jITww==", + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.1.tgz", + "integrity": "sha512-fqcQxcxC4LOaUlW8IkyWw8x0yirlLUkbxohz9OnWvVWjf73J5yyw7jxWnkOJaUKXZotcGEScDox9MU6rSkcDgg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2532,9 +2532,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -2549,9 +2549,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -2566,9 +2566,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -2583,9 +2583,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -2600,9 +2600,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -2617,9 +2617,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -2634,9 +2634,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -2651,9 +2651,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -2668,9 +2668,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -2685,9 +2685,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -2702,9 +2702,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -2719,9 +2719,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -2736,9 +2736,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -2753,9 +2753,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -2770,9 +2770,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -2787,9 +2787,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -2804,9 +2804,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -2821,9 +2821,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -2838,9 +2838,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -2855,9 +2855,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -2872,9 +2872,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -2889,9 +2889,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "cpu": [ "arm64" ], @@ -2906,9 +2906,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -2923,9 +2923,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -2940,9 +2940,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -2957,9 +2957,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -8162,9 +8162,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz", - "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", + "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", "dev": true, "license": "MIT", "dependencies": { @@ -8172,39 +8172,39 @@ "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", - "magic-string": "^0.30.19", + "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.16" + "tailwindcss": "4.1.17" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", - "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", + "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", "dev": true, "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.16", - "@tailwindcss/oxide-darwin-arm64": "4.1.16", - "@tailwindcss/oxide-darwin-x64": "4.1.16", - "@tailwindcss/oxide-freebsd-x64": "4.1.16", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", - "@tailwindcss/oxide-linux-x64-musl": "4.1.16", - "@tailwindcss/oxide-wasm32-wasi": "4.1.16", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" + "@tailwindcss/oxide-android-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-x64": "4.1.17", + "@tailwindcss/oxide-freebsd-x64": "4.1.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-x64-musl": "4.1.17", + "@tailwindcss/oxide-wasm32-wasi": "4.1.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz", - "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", + "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", "cpu": [ "arm64" ], @@ -8219,9 +8219,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz", - "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", + "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", "cpu": [ "arm64" ], @@ -8236,9 +8236,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz", - "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", + "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", "cpu": [ "x64" ], @@ -8253,9 +8253,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz", - "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", + "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", "cpu": [ "x64" ], @@ -8270,9 +8270,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz", - "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", + "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", "cpu": [ "arm" ], @@ -8287,9 +8287,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz", - "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", + "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", "cpu": [ "arm64" ], @@ -8304,9 +8304,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz", - "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", + "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", "cpu": [ "arm64" ], @@ -8321,9 +8321,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz", - "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", + "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", "cpu": [ "x64" ], @@ -8338,9 +8338,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz", - "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", + "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", "cpu": [ "x64" ], @@ -8355,9 +8355,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz", - "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", + "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -8373,8 +8373,8 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", + "@emnapi/core": "^1.6.0", + "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", @@ -8385,7 +8385,7 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.5.0", + "version": "1.6.0", "dev": true, "inBundle": true, "license": "MIT", @@ -8396,7 +8396,7 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.5.0", + "version": "1.6.0", "dev": true, "inBundle": true, "license": "MIT", @@ -8445,9 +8445,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", - "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", + "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", "cpu": [ "arm64" ], @@ -8462,9 +8462,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz", - "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", + "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", "cpu": [ "x64" ], @@ -8479,17 +8479,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.16.tgz", - "integrity": "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz", + "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.16", - "@tailwindcss/oxide": "4.1.16", + "@tailwindcss/node": "4.1.17", + "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", - "tailwindcss": "4.1.16" + "tailwindcss": "4.1.17" } }, "node_modules/@tanstack/react-table": { @@ -8899,16 +8899,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", - "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", + "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/type-utils": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/type-utils": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -8922,7 +8922,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.2", + "@typescript-eslint/parser": "^8.46.3", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -8937,15 +8937,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", - "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", + "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4" }, "engines": { @@ -8961,13 +8961,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", - "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", + "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", + "@typescript-eslint/tsconfig-utils": "^8.46.3", + "@typescript-eslint/types": "^8.46.3", "debug": "^4.3.4" }, "engines": { @@ -8982,13 +8982,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", - "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", + "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2" + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8999,9 +8999,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", - "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", + "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9015,14 +9015,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", - "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", + "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -9039,9 +9039,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", - "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", + "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9052,15 +9052,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", - "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", + "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.2", - "@typescript-eslint/tsconfig-utils": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", + "@typescript-eslint/project-service": "8.46.3", + "@typescript-eslint/tsconfig-utils": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -9132,15 +9132,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", - "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", + "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2" + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9155,12 +9155,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", - "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", + "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/types": "8.46.3", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -11910,9 +11910,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -11923,32 +11923,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/esbuild-node-externals": { @@ -21288,9 +21288,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", - "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", + "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", "license": "MIT" }, "node_modules/tapable": { @@ -21830,16 +21830,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz", - "integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.3.tgz", + "integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2" + "@typescript-eslint/eslint-plugin": "8.46.3", + "@typescript-eslint/parser": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index be31b60f..18f3f4b6 100644 --- a/package.json +++ b/package.json @@ -133,10 +133,10 @@ "@faker-js/faker": "^10.1.0" }, "devDependencies": { - "@dotenvx/dotenvx": "1.51.0", + "@dotenvx/dotenvx": "1.51.1", "@esbuild-plugins/tsconfig-paths": "0.1.2", "@react-email/preview-server": "4.3.2", - "@tailwindcss/postcss": "^4.1.16", + "@tailwindcss/postcss": "^4.1.17", "@types/better-sqlite3": "7.6.12", "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", @@ -157,7 +157,7 @@ "@types/ws": "8.18.1", "@types/yargs": "17.0.34", "drizzle-kit": "0.31.6", - "esbuild": "0.25.11", + "esbuild": "0.25.12", "esbuild-node-externals": "1.18.0", "postcss": "^8", "react-email": "4.3.2", @@ -165,7 +165,7 @@ "tsc-alias": "1.8.16", "tsx": "4.20.6", "typescript": "^5", - "typescript-eslint": "^8.46.2" + "typescript-eslint": "^8.46.3" }, "overrides": { "emblor": { From 9f9aa07c2d8a2bc37edfadef2f7ba38d43bff4c2 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Fri, 24 Oct 2025 23:36:20 +0530 Subject: [PATCH 04/63] Option to regenerate remote-nodes keys --- server/private/routers/external.ts | 8 + .../private/routers/remoteExitNode/index.ts | 1 + .../remoteExitNode/updateRemoteExitNode.ts | 106 +++++++++++ server/routers/remoteExitNode/types.ts | 5 + .../remote-exit-nodes/ExitNodesTable.tsx | 9 + .../[remoteExitNodeId]/general/page.tsx | 170 +++++++++++++++++- .../[remoteExitNodeId]/layout.tsx | 14 +- src/components/ExitNodeInfoCard.tsx | 52 ++++++ src/hooks/useRemoteExitNodeContext.ts | 6 +- 9 files changed, 368 insertions(+), 3 deletions(-) create mode 100644 server/private/routers/remoteExitNode/updateRemoteExitNode.ts create mode 100644 src/components/ExitNodeInfoCard.tsx diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 00ad117f..8e2b2bbc 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -236,6 +236,14 @@ authenticated.put( remoteExitNode.createRemoteExitNode ); +authenticated.put( + "/org/:orgId/update-remote-exit-node", + verifyValidLicense, + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.updateRemoteExitNode), + remoteExitNode.updateRemoteExitNode +); + authenticated.get( "/org/:orgId/remote-exit-nodes", verifyValidLicense, diff --git a/server/private/routers/remoteExitNode/index.ts b/server/private/routers/remoteExitNode/index.ts index 2a04f9d9..a30e204c 100644 --- a/server/private/routers/remoteExitNode/index.ts +++ b/server/private/routers/remoteExitNode/index.ts @@ -21,3 +21,4 @@ export * from "./deleteRemoteExitNode"; export * from "./listRemoteExitNodes"; export * from "./pickRemoteExitNodeDefaults"; export * from "./quickStartRemoteExitNode"; +export * from "./updateRemoteExitNode" diff --git a/server/private/routers/remoteExitNode/updateRemoteExitNode.ts b/server/private/routers/remoteExitNode/updateRemoteExitNode.ts new file mode 100644 index 00000000..9de017f8 --- /dev/null +++ b/server/private/routers/remoteExitNode/updateRemoteExitNode.ts @@ -0,0 +1,106 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import { NextFunction, Request, Response } from "express"; +import { db, exitNodes, exitNodeOrgs, ExitNode, ExitNodeOrg } from "@server/db"; +import HttpCode from "@server/types/HttpCode"; +import { z } from "zod"; +import { remoteExitNodes } from "@server/db"; +import createHttpError from "http-errors"; +import response from "@server/lib/response"; +import { fromError } from "zod-validation-error"; +import { hashPassword } from "@server/auth/password"; +import logger from "@server/logger"; +import { and, eq } from "drizzle-orm"; +import { UpdateRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types"; +import { paramsSchema } from "./createRemoteExitNode"; + +const bodySchema = z + .object({ + remoteExitNodeId: z.string().length(15), + secret: z.string().length(48) + }) + .strict(); + +export async function updateRemoteExitNode( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const parsedBody = bodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { remoteExitNodeId, secret } = parsedBody.data; + + if (req.user && !req.userOrgRoleId) { + return next( + createHttpError(HttpCode.FORBIDDEN, "User does not have a role") + ); + } + + const [existingRemoteExitNode] = await db + .select() + .from(remoteExitNodes) + .where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId)); + + if (!existingRemoteExitNode) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Remote Exit Node does not exist") + ); + } + + const secretHash = await hashPassword(secret); + + await db + .update(remoteExitNodes) + .set({ secretHash }) + .where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId)); + + return response(res, { + data: { + remoteExitNodeId, + secret, + }, + success: true, + error: false, + message: "Remote Exit Node secret updated successfully", + status: HttpCode.OK, + }); + } catch (e) { + logger.error("Failed to update remoteExitNode", e); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to update remoteExitNode" + ) + ); + } +} diff --git a/server/routers/remoteExitNode/types.ts b/server/routers/remoteExitNode/types.ts index 55d0a286..ae0c2130 100644 --- a/server/routers/remoteExitNode/types.ts +++ b/server/routers/remoteExitNode/types.ts @@ -6,6 +6,11 @@ export type CreateRemoteExitNodeResponse = { secret: string; }; +export type UpdateRemoteExitNodeResponse = { + remoteExitNodeId: string; + secret: string; +} + export type PickRemoteExitNodeDefaultsResponse = { remoteExitNodeId: string; secret: string; diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx index 6e9ab237..06da3dc5 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx @@ -232,6 +232,7 @@ export default function ExitNodesTable({ id: "actions", cell: ({ row }) => { const nodeRow = row.original; + const remoteExitNodeId = nodeRow.id; return (
@@ -242,6 +243,14 @@ export default function ExitNodesTable({ + + + {t("viewSettings")} + + { setSelectedNode(nodeRow); diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx index 191ce3f3..6711177b 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx @@ -1,3 +1,171 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + SettingsContainer, + SettingsSection, + SettingsSectionBody, + SettingsSectionDescription, + SettingsSectionHeader, + SettingsSectionTitle +} from "@app/components/Settings"; +import { Button } from "@app/components/ui/button"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { InfoIcon } from "lucide-react"; +import CopyTextBox from "@app/components/CopyTextBox"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { useParams, useRouter } from "next/navigation"; +import { AxiosResponse } from "axios"; +import { useTranslations } from "next-intl"; +import { + PickRemoteExitNodeDefaultsResponse, + QuickStartRemoteExitNodeResponse +} from "@server/routers/remoteExitNode/types"; +import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext"; + export default function GeneralPage() { - return <>; + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const { orgId } = useParams(); + const router = useRouter(); + const t = useTranslations(); + const { remoteExitNode, updateRemoteExitNode } = useRemoteExitNodeContext(); + + const [credentials, setCredentials] = + useState(null); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + + // Clear credentials when user leaves/reloads + useEffect(() => { + const clearCreds = () => setCredentials(null); + window.addEventListener("beforeunload", clearCreds); + return () => window.removeEventListener("beforeunload", clearCreds); + }, []); + + const handleRegenerate = async () => { + try { + setLoading(true); + const response = await api.get< + AxiosResponse + >(`/org/${orgId}/pick-remote-exit-node-defaults`); + + setCredentials(response.data.data); + toast({ + title: t("success"), + description: t("Credentials generated successfully."), + }); + } catch (error) { + toast({ + title: t("error"), + description: formatAxiosError( + error, + t("Failed to generate credentials") + ), + variant: "destructive", + }); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + if (!credentials) return; + + try { + setSaving(true); + + const response = await api.put< + AxiosResponse + >(`/org/${orgId}/update-remote-exit-node`, { + remoteExitNodeId: remoteExitNode.remoteExitNodeId, + secret: credentials.secret, + }); + + toast({ + title: t("success"), + description: t("Credentials saved successfully."), + }); + + // For security, clear them from UI + setCredentials(null); + + } catch (error) { + toast({ + title: t("error"), + description: formatAxiosError( + error, + t("Failed to save credentials") + ), + variant: "destructive", + }); + } finally { + setSaving(false); + } + }; + + return ( + + + + + {t("Generated Credentials")} + + + {t("Regenerate and save your managed credentials")} + + + + + {!credentials ? ( + + ) : ( + <> + + + + + + {t("Copy and save these credentials")} + + + {t( + "These credentials will not be shown again after you leave this page. Save them securely now." + )} + + + +
+ + +
+ + )} +
+
+
+ ); } diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx index 7a7b3611..6473999d 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx @@ -6,6 +6,8 @@ import { authCookieHeader } from "@app/lib/api/cookies"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { getTranslations } from "next-intl/server"; import RemoteExitNodeProvider from "@app/providers/RemoteExitNodeProvider"; +import { HorizontalTabs } from "@app/components/HorizontalTabs"; +import ExitNodeInfoCard from "@app/components/ExitNodeInfoCard"; interface SettingsLayoutProps { children: React.ReactNode; @@ -31,6 +33,13 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const t = await getTranslations(); + const navItems = [ + { + title: t('general'), + href: "/{orgId}/settings/remote-exit-nodes/{remoteExitNodeId}/general" + } + ]; + return ( <> -
{children}
+
+ + {children} +
); diff --git a/src/components/ExitNodeInfoCard.tsx b/src/components/ExitNodeInfoCard.tsx new file mode 100644 index 00000000..49ae1b61 --- /dev/null +++ b/src/components/ExitNodeInfoCard.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { InfoIcon } from "lucide-react"; +import { + InfoSection, + InfoSectionContent, + InfoSections, + InfoSectionTitle +} from "@app/components/InfoSection"; +import { useTranslations } from "next-intl"; +import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext"; + +type ExitNodeInfoCardProps = {}; + +export default function ExitNodeInfoCard({}: ExitNodeInfoCardProps) { + const { remoteExitNode, updateRemoteExitNode } = useRemoteExitNodeContext(); + const t = useTranslations(); + + return ( + + + + <> + + {t("status")} + + {remoteExitNode.online ? ( +
+
+ {t("online")} +
+ ) : ( +
+
+ {t("offline")} +
+ )} +
+
+ + + {t("address")} + + {remoteExitNode.address} + + +
+
+
+ ); +} diff --git a/src/hooks/useRemoteExitNodeContext.ts b/src/hooks/useRemoteExitNodeContext.ts index 486147c4..6fe244c8 100644 --- a/src/hooks/useRemoteExitNodeContext.ts +++ b/src/hooks/useRemoteExitNodeContext.ts @@ -2,11 +2,15 @@ import RemoteExitNodeContext from "@app/contexts/remoteExitNodeContext"; import { build } from "@server/build"; +import { GetRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types"; import { useContext } from "react"; export function useRemoteExitNodeContext() { if (build == "oss") { - return null; + return { + remoteExitNode: {} as GetRemoteExitNodeResponse, + updateRemoteExitNode: () => {}, + }; } const context = useContext(RemoteExitNodeContext); if (context === undefined) { From 3f38080b46bdf56cb326936b1d9c215f45148dd2 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Fri, 24 Oct 2025 23:42:25 +0530 Subject: [PATCH 05/63] fix lint --- server/private/routers/remoteExitNode/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/private/routers/remoteExitNode/index.ts b/server/private/routers/remoteExitNode/index.ts index a30e204c..5677520d 100644 --- a/server/private/routers/remoteExitNode/index.ts +++ b/server/private/routers/remoteExitNode/index.ts @@ -21,4 +21,4 @@ export * from "./deleteRemoteExitNode"; export * from "./listRemoteExitNodes"; export * from "./pickRemoteExitNodeDefaults"; export * from "./quickStartRemoteExitNode"; -export * from "./updateRemoteExitNode" +export * from "./updateRemoteExitNode"; From c2f607bb9ad41adc62c5d7917c800478697fd543 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sat, 25 Oct 2025 22:42:51 +0530 Subject: [PATCH 06/63] Option to regenerate olm keys inside client --- messages/en-US.json | 8 +- server/routers/client/updateClient.ts | 32 ++- .../[remoteExitNodeId]/general/page.tsx | 14 +- .../clients/[clientId]/credentials/page.tsx | 208 ++++++++++++++++++ .../settings/clients/[clientId]/layout.tsx | 4 + 5 files changed, 255 insertions(+), 11 deletions(-) create mode 100644 src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx diff --git a/messages/en-US.json b/messages/en-US.json index 063d9efc..9b2c35f8 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2095,5 +2095,11 @@ "selectedResources": "Selected Resources", "enableSelected": "Enable Selected", "disableSelected": "Disable Selected", - "checkSelectedStatus": "Check Status of Selected" + "checkSelectedStatus": "Check Status of Selected", + "savecredentials": "Save Credentials", + "regeneratecredentials": "Regenerate Credentials", + "regenerateClientCredentials": "Regenerate and save your managed credentials", + "generatedcredentials": "Generated Credentials", + "copyandsavethesecredentials": "Copy and save these credentials", + "copyandsavethesecredentialsdescription": "These credentials will not be shown again after you leave this page. Save them securely now." } diff --git a/server/routers/client/updateClient.ts b/server/routers/client/updateClient.ts index 884a9864..84e5f619 100644 --- a/server/routers/client/updateClient.ts +++ b/server/routers/client/updateClient.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { Client, db, exitNodes, sites } from "@server/db"; +import { Client, db, exitNodes, olms, sites } from "@server/db"; import { clients, clientSites } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -18,6 +18,7 @@ import { deletePeer as olmDeletePeer } from "../olm/peers"; import { sendToExitNode } from "#dynamic/lib/exitNodes"; +import { hashPassword } from "@server/auth/password"; const updateClientParamsSchema = z .object({ @@ -30,7 +31,10 @@ const updateClientSchema = z name: z.string().min(1).max(255).optional(), siteIds: z .array(z.number().int().positive()) - .optional() + .optional(), + olmId: z.string().min(1).optional(), + secret: z.string().min(1).optional(), + }) .strict(); @@ -75,7 +79,7 @@ export async function updateClient( ); } - const { name, siteIds } = parsedBody.data; + const { name, siteIds, olmId, secret } = parsedBody.data; const parsedParams = updateClientParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -89,6 +93,12 @@ export async function updateClient( const { clientId } = parsedParams.data; + let secretHash = undefined; + if (secret) { + secretHash = await hashPassword(secret); + } + + // Fetch the client to make sure it exists and the user has access to it const [client] = await db .select() @@ -136,6 +146,22 @@ export async function updateClient( .where(eq(clients.clientId, clientId)); } + const [existingOlm] = await trx + .select() + .from(olms) + .where(eq(olms.clientId, clientId)) + .limit(1); + + if (existingOlm && olmId && secretHash) { + await trx + .update(olms) + .set({ + olmId, + secretHash + }) + .where(eq(olms.clientId, clientId)); + } + // Update site associations if provided // Remove sites that are no longer associated for (const siteId of sitesRemoved) { diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx index 6711177b..d207f0de 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx @@ -111,10 +111,10 @@ export default function GeneralPage() { - {t("Generated Credentials")} + {t("generatedcredentials")} - {t("Regenerate and save your managed credentials")} + {t("regenerateClientCredentials")} @@ -125,7 +125,7 @@ export default function GeneralPage() { loading={loading} disabled={loading} > - {t("Regenerate Credentials")} + {t("regeneratecredentials")} ) : ( <> @@ -138,11 +138,11 @@ export default function GeneralPage() { - {t("Copy and save these credentials")} + {t("copyandsavethesecredentials")} {t( - "These credentials will not be shown again after you leave this page. Save them securely now." + "copyandsavethesecredentialsdescription" )} @@ -152,14 +152,14 @@ export default function GeneralPage() { variant="outline" onClick={() => setCredentials(null)} > - {t("Cancel")} + {t("cancel")}
diff --git a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx new file mode 100644 index 00000000..fc86a93c --- /dev/null +++ b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx @@ -0,0 +1,208 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + SettingsContainer, + SettingsSection, + SettingsSectionBody, + SettingsSectionDescription, + SettingsSectionHeader, + SettingsSectionTitle +} from "@app/components/Settings"; +import { Button } from "@app/components/ui/button"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { InfoIcon } from "lucide-react"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { useParams, useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; +import CopyToClipboard from "@app/components/CopyToClipboard"; +import { PickClientDefaultsResponse } from "@server/routers/client"; +import { useClientContext } from "@app/hooks/useClientContext"; + +export default function GeneralPage() { + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const { orgId } = useParams(); + const router = useRouter(); + const t = useTranslations(); + const [olmId, setOlmId] = useState(""); + const [olmSecret, setOlmSecret] = useState(""); + const { client, updateClient } = useClientContext(); + + const [clientDefaults, setClientDefaults] = + useState(null); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + + // Clear credentials when user leaves/reloads + useEffect(() => { + const clearCreds = () => { + setOlmId(""); + setOlmSecret(""); + }; + window.addEventListener("beforeunload", clearCreds); + return () => window.removeEventListener("beforeunload", clearCreds); + }, []); + + const handleRegenerate = async () => { + try { + setLoading(true); + await api + .get(`/org/${orgId}/pick-client-defaults`) + .then((res) => { + if (res && res.status === 200) { + const data = res.data.data; + + setClientDefaults(data); + + const olmId = data.olmId; + const olmSecret = data.olmSecret; + setOlmId(olmId); + setOlmSecret(olmSecret); + + } + }); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + setLoading(true); + + try { + await api.post(`/client/${client?.clientId}`, { + olmId: clientDefaults?.olmId, + secret: clientDefaults?.olmSecret, + }); + + toast({ + title: t("clientUpdated"), + description: t("clientUpdatedDescription") + }); + + router.refresh(); + } catch (e) { + toast({ + variant: "destructive", + title: t("clientUpdateFailed"), + description: formatAxiosError( + e, + t("clientUpdateError") + ) + }); + } finally { + setLoading(false); + } + }; + + return ( + + + + + {t("generatedcredentials")} + + + {t("regenerateClientCredentials")} + + + + + {!clientDefaults ? ( + + ) : ( + <> + + + + {t("clientOlmCredentials")} + + + {t("clientOlmCredentialsDescription")} + + + + + + + {t("olmEndpoint")} + + + + + + + + {t("olmId")} + + + + + + + + {t("olmSecretKey")} + + + + + + + + + + + {t("copyandsavethesecredentials")} + + + {t( + "copyandsavethesecredentialsdescription" + )} + + + + + +
+ + +
+ + )} +
+
+
+ ); +} diff --git a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx index c9c9fd14..e597f90d 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx @@ -34,6 +34,10 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { { title: "General", href: `/{orgId}/settings/clients/{clientId}/general` + }, + { + title: "Credentials", + href: `/{orgId}/settings/clients/{clientId}/credentials` } ]; From 42091e88cb19c31ce4d541500cc84433221a71e1 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sat, 25 Oct 2025 23:25:18 +0530 Subject: [PATCH 07/63] rename exit node tab to credentials --- messages/en-US.json | 1 + .../[remoteExitNodeId]/{general => credentials}/page.tsx | 2 +- .../(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx | 4 ++-- .../(private)/remote-exit-nodes/[remoteExitNodeId]/page.tsx | 2 +- .../[orgId]/settings/clients/[clientId]/credentials/page.tsx | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) rename src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/{general => credentials}/page.tsx (99%) diff --git a/messages/en-US.json b/messages/en-US.json index 9b2c35f8..3f4083b1 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2096,6 +2096,7 @@ "enableSelected": "Enable Selected", "disableSelected": "Disable Selected", "checkSelectedStatus": "Check Status of Selected", + "credentials": "Credentials", "savecredentials": "Save Credentials", "regeneratecredentials": "Regenerate Credentials", "regenerateClientCredentials": "Regenerate and save your managed credentials", diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx similarity index 99% rename from src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx rename to src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index d207f0de..38ccc334 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/general/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -25,7 +25,7 @@ import { } from "@server/routers/remoteExitNode/types"; import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext"; -export default function GeneralPage() { +export default function CredentialsPage() { const { env } = useEnvContext(); const api = createApiClient({ env }); const { orgId } = useParams(); diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx index 6473999d..19357a7f 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx @@ -35,8 +35,8 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const navItems = [ { - title: t('general'), - href: "/{orgId}/settings/remote-exit-nodes/{remoteExitNodeId}/general" + title: t('credentials'), + href: "/{orgId}/settings/remote-exit-nodes/{remoteExitNodeId}/credentials" } ]; diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/page.tsx index 6b39c1de..5b9fd628 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/page.tsx @@ -5,6 +5,6 @@ export default async function RemoteExitNodePage(props: { }) { const params = await props.params; redirect( - `/${params.orgId}/settings/remote-exit-nodes/${params.remoteExitNodeId}/general` + `/${params.orgId}/settings/remote-exit-nodes/${params.remoteExitNodeId}/credentials` ); } diff --git a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx index fc86a93c..1de1e696 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx @@ -22,7 +22,7 @@ import CopyToClipboard from "@app/components/CopyToClipboard"; import { PickClientDefaultsResponse } from "@server/routers/client"; import { useClientContext } from "@app/hooks/useClientContext"; -export default function GeneralPage() { +export default function CredentialsPage() { const { env } = useEnvContext(); const api = createApiClient({ env }); const { orgId } = useParams(); From d32505a833253dc937781bde5d2bc67f9750fff9 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sun, 26 Oct 2025 21:00:46 +0530 Subject: [PATCH 08/63] Option to regenerate Newt keys --- messages/en-US.json | 8 +- server/auth/actions.ts | 1 + server/routers/client/index.ts | 3 +- .../routers/client/reGenerateClientSecret.ts | 130 +++++++++++ server/routers/client/updateClient.ts | 26 +-- server/routers/external.ts | 15 ++ server/routers/site/index.ts | 1 + server/routers/site/reGenerateSiteSecret.ts | 106 +++++++++ .../clients/[clientId]/credentials/page.tsx | 12 +- .../settings/clients/[clientId]/layout.tsx | 7 +- .../sites/[niceId]/credentials/page.tsx | 212 ++++++++++++++++++ .../settings/sites/[niceId]/layout.tsx | 4 + src/components/ClientInfoCard.tsx | 7 +- 13 files changed, 491 insertions(+), 41 deletions(-) create mode 100644 server/routers/client/reGenerateClientSecret.ts create mode 100644 server/routers/site/reGenerateSiteSecret.ts create mode 100644 src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx diff --git a/messages/en-US.json b/messages/en-US.json index 3f4083b1..a7d825f5 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2099,8 +2099,12 @@ "credentials": "Credentials", "savecredentials": "Save Credentials", "regeneratecredentials": "Regenerate Credentials", - "regenerateClientCredentials": "Regenerate and save your managed credentials", + "regenerateCredentials": "Regenerate and save your credentials", "generatedcredentials": "Generated Credentials", "copyandsavethesecredentials": "Copy and save these credentials", - "copyandsavethesecredentialsdescription": "These credentials will not be shown again after you leave this page. Save them securely now." + "copyandsavethesecredentialsdescription": "These credentials will not be shown again after you leave this page. Save them securely now.", + "credentialsSaved" : "Credentials Saved", + "credentialsSavedDescription": "Credentials have been regenerated and saved successfully.", + "credentialsSaveError": "Credentials Save Error", + "credentialsSaveErrorDescription": "An error occurred while regenerating and saving the credentials." } diff --git a/server/auth/actions.ts b/server/auth/actions.ts index d08457e5..4608757b 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -19,6 +19,7 @@ export enum ActionsEnum { getSite = "getSite", listSites = "listSites", updateSite = "updateSite", + reGenerateSecret = "reGenerateSecret", createResource = "createResource", deleteResource = "deleteResource", getResource = "getResource", diff --git a/server/routers/client/index.ts b/server/routers/client/index.ts index 385c7bed..9f97446e 100644 --- a/server/routers/client/index.ts +++ b/server/routers/client/index.ts @@ -3,4 +3,5 @@ export * from "./createClient"; export * from "./deleteClient"; export * from "./listClients"; export * from "./updateClient"; -export * from "./getClient"; \ No newline at end of file +export * from "./getClient"; +export * from "./reGenerateClientSecret"; \ No newline at end of file diff --git a/server/routers/client/reGenerateClientSecret.ts b/server/routers/client/reGenerateClientSecret.ts new file mode 100644 index 00000000..2bce396a --- /dev/null +++ b/server/routers/client/reGenerateClientSecret.ts @@ -0,0 +1,130 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, olms, } from "@server/db"; +import { clients } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { eq, and } from "drizzle-orm"; +import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; +import { hashPassword } from "@server/auth/password"; + +const reGenerateSecretParamsSchema = z + .object({ + clientId: z.string().transform(Number).pipe(z.number().int().positive()) + }) + .strict(); + +const reGenerateSecretBodySchema = z + .object({ + olmId: z.string().min(1).optional(), + secret: z.string().min(1).optional(), + + }) + .strict(); + +export type ReGenerateSecretBody = z.infer; + +registry.registerPath({ + method: "post", + path: "/client/{clientId}/regenerate-secret", + description: "Regenerate a client's OLM credentials by its client ID.", + tags: [OpenAPITags.Client], + request: { + params: reGenerateSecretParamsSchema, + body: { + content: { + "application/json": { + schema: reGenerateSecretBodySchema + } + } + } + }, + responses: {} +}); + + +export async function reGenerateClientSecret( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedBody = reGenerateSecretBodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { olmId, secret } = parsedBody.data; + + const parsedParams = reGenerateSecretParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { clientId } = parsedParams.data; + + let secretHash = undefined; + if (secret) { + secretHash = await hashPassword(secret); + } + + + // Fetch the client to make sure it exists and the user has access to it + const [client] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + + if (!client) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Client with ID ${clientId} not found` + ) + ); + } + + const [existingOlm] = await db + .select() + .from(olms) + .where(eq(olms.clientId, clientId)) + .limit(1); + + if (existingOlm && olmId && secretHash) { + await db + .update(olms) + .set({ + olmId, + secretHash + }) + .where(eq(olms.clientId, clientId)); + } + + return response(res, { + data: existingOlm, + success: true, + error: false, + message: "Credentials regenerated successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/client/updateClient.ts b/server/routers/client/updateClient.ts index 84e5f619..d458c4f8 100644 --- a/server/routers/client/updateClient.ts +++ b/server/routers/client/updateClient.ts @@ -32,9 +32,6 @@ const updateClientSchema = z siteIds: z .array(z.number().int().positive()) .optional(), - olmId: z.string().min(1).optional(), - secret: z.string().min(1).optional(), - }) .strict(); @@ -79,7 +76,7 @@ export async function updateClient( ); } - const { name, siteIds, olmId, secret } = parsedBody.data; + const { name, siteIds } = parsedBody.data; const parsedParams = updateClientParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -93,11 +90,6 @@ export async function updateClient( const { clientId } = parsedParams.data; - let secretHash = undefined; - if (secret) { - secretHash = await hashPassword(secret); - } - // Fetch the client to make sure it exists and the user has access to it const [client] = await db @@ -146,22 +138,6 @@ export async function updateClient( .where(eq(clients.clientId, clientId)); } - const [existingOlm] = await trx - .select() - .from(olms) - .where(eq(olms.clientId, clientId)) - .limit(1); - - if (existingOlm && olmId && secretHash) { - await trx - .update(olms) - .set({ - olmId, - secretHash - }) - .where(eq(olms.clientId, clientId)); - } - // Update site associations if provided // Remove sites that are no longer associated for (const siteId of sitesRemoved) { diff --git a/server/routers/external.ts b/server/routers/external.ts index 5c235902..c2c518fa 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -178,6 +178,14 @@ authenticated.post( client.updateClient, ); +authenticated.post( + "/client/:clientId/regenerate-secret", + verifyClientsEnabled, + verifyClientAccess, + verifyUserHasAction(ActionsEnum.reGenerateSecret), + client.reGenerateClientSecret +); + // authenticated.get( // "/site/:siteId/roles", // verifySiteAccess, @@ -191,6 +199,13 @@ authenticated.post( logActionAudit(ActionsEnum.updateSite), site.updateSite, ); + +authenticated.post( + "/site/:siteId/regenerate-secret", + verifySiteAccess, + verifyUserHasAction(ActionsEnum.reGenerateSecret), + site.reGenerateSiteSecret +); authenticated.delete( "/site/:siteId", verifySiteAccess, diff --git a/server/routers/site/index.ts b/server/routers/site/index.ts index 3edf67c1..9b8b89cb 100644 --- a/server/routers/site/index.ts +++ b/server/routers/site/index.ts @@ -6,3 +6,4 @@ export * from "./listSites"; export * from "./listSiteRoles"; export * from "./pickSiteDefaults"; export * from "./socketIntegration"; +export * from "./reGenerateSiteSecret"; \ No newline at end of file diff --git a/server/routers/site/reGenerateSiteSecret.ts b/server/routers/site/reGenerateSiteSecret.ts new file mode 100644 index 00000000..979212a4 --- /dev/null +++ b/server/routers/site/reGenerateSiteSecret.ts @@ -0,0 +1,106 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, newts } from "@server/db"; +import { eq } from "drizzle-orm"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; +import { hashPassword } from "@server/auth/password"; + +const updateSiteParamsSchema = z + .object({ + siteId: z.string().transform(Number).pipe(z.number().int().positive()) + }) + .strict(); + +const updateSiteBodySchema = z + .object({ + newtId: z.string().min(1).max(255).optional(), + newtSecret: z.string().min(1).max(255).optional(), + }) + .strict() + +registry.registerPath({ + method: "post", + path: "/site/{siteId}/regenerate-secret", + description: + "Regenerate a site's Newt credentials by its site ID.", + tags: [OpenAPITags.Site], + request: { + params: updateSiteParamsSchema, + body: { + content: { + "application/json": { + schema: updateSiteBodySchema + } + } + } + }, + responses: {} +}); + +export async function reGenerateSiteSecret( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = updateSiteParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const parsedBody = updateSiteBodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { siteId } = parsedParams.data; + const { newtId, newtSecret } = parsedBody.data; + + const secretHash = await hashPassword(newtSecret!); + const updatedSite = await db + .update(newts) + .set({ + newtId, + secretHash + }) + .where(eq(newts.siteId, siteId)) + .returning(); + + if (updatedSite.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${siteId} not found` + ) + ); + } + + return response(res, { + data: updatedSite[0], + success: true, + error: false, + message: "Credentials regenerated successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx index 1de1e696..7d34b5eb 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx @@ -74,24 +74,24 @@ export default function CredentialsPage() { setLoading(true); try { - await api.post(`/client/${client?.clientId}`, { + await api.post(`/client/${client?.clientId}/regenerate-secret`, { olmId: clientDefaults?.olmId, secret: clientDefaults?.olmSecret, }); toast({ - title: t("clientUpdated"), - description: t("clientUpdatedDescription") + title: t("credentialsSaved"), + description: t("credentialsSavedDescription") }); router.refresh(); } catch (e) { toast({ variant: "destructive", - title: t("clientUpdateFailed"), + title: t("credentialsSaveError"), description: formatAxiosError( e, - t("clientUpdateError") + t("credentialsSaveErrorDescription") ) }); } finally { @@ -107,7 +107,7 @@ export default function CredentialsPage() { {t("generatedcredentials")} - {t("regenerateClientCredentials")} + {t("regenerateCredentials")} diff --git a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx index e597f90d..dc4ef0b4 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx @@ -7,6 +7,7 @@ import ClientInfoCard from "../../../../../components/ClientInfoCard"; import ClientProvider from "@app/providers/ClientProvider"; import { redirect } from "next/navigation"; import { HorizontalTabs } from "@app/components/HorizontalTabs"; +import { getTranslations } from "next-intl/server"; type SettingsLayoutProps = { children: React.ReactNode; @@ -30,13 +31,15 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { redirect(`/${params.orgId}/settings/clients`); } + const t = await getTranslations(); + const navItems = [ { - title: "General", + title: t('general'), href: `/{orgId}/settings/clients/{clientId}/general` }, { - title: "Credentials", + title: t('credentials'), href: `/{orgId}/settings/clients/{clientId}/credentials` } ]; diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx new file mode 100644 index 00000000..14840b66 --- /dev/null +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -0,0 +1,212 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + SettingsContainer, + SettingsSection, + SettingsSectionBody, + SettingsSectionDescription, + SettingsSectionHeader, + SettingsSectionTitle +} from "@app/components/Settings"; +import { Button } from "@app/components/ui/button"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { InfoIcon } from "lucide-react"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { useParams, useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; +import CopyToClipboard from "@app/components/CopyToClipboard"; +import { PickSiteDefaultsResponse } from "@server/routers/site"; +import { useSiteContext } from "@app/hooks/useSiteContext"; + +export default function CredentialsPage() { + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const { orgId } = useParams(); + const router = useRouter(); + const t = useTranslations(); + const [newtId, setNewtId] = useState(""); + const [newtSecret, setNewtSecret] = useState(""); + const { site, updateSite } = useSiteContext(); + + const [siteDefaults, setSiteDefaults] = + useState(null); + + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + + // Clear credentials when user leaves/reloads + useEffect(() => { + const clearCreds = () => { + setNewtId(""); + setNewtSecret(""); + }; + window.addEventListener("beforeunload", clearCreds); + return () => window.removeEventListener("beforeunload", clearCreds); + }, []); + + const handleRegenerate = async () => { + try { + setLoading(true); + await api + .get(`/org/${orgId}/pick-site-defaults`) + .then((res) => { + if (res && res.status === 200) { + const data = res.data.data; + + setSiteDefaults(data); + + const newtId = data.newtId; + const newtSecret = data.newtSecret; + setNewtId(newtId); + setNewtSecret(newtSecret); + + } + }); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + setLoading(true); + + try { + await api.post(`/site/${site?.siteId}/regenerate-secret`, { + newtId: siteDefaults?.newtId, + newtSecret: siteDefaults?.newtSecret, + }); + + toast({ + title: t("credentialsSaved"), + description: t("credentialsSavedDescription") + }); + + router.refresh(); + } catch (e) { + toast({ + variant: "destructive", + title: t("credentialsSaveError"), + description: formatAxiosError( + e, + t("credentialsSaveErrorDescription") + ) + }); + } finally { + setLoading(false); + } + }; + + return ( + + + + + {t("generatedcredentials")} + + + {t("regenerateCredentials")} + + + + + {!siteDefaults ? ( + + ) : ( + <> + + + + {t("siteNewtCredentials")} + + + {t( + "siteNewtCredentialsDescription" + )} + + + + + + + {t("newtEndpoint")} + + + + + + + + {t("newtId")} + + + + + + + + {t("newtSecretKey")} + + + + + + + + + + + + {t("copyandsavethesecredentials")} + + + {t( + "copyandsavethesecredentialsdescription" + )} + + + + + +
+ + +
+ + )} +
+
+
+ ); +} diff --git a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx index 039deebb..abd9aefb 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx @@ -36,6 +36,10 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { { title: t('general'), href: "/{orgId}/settings/sites/{niceId}/general" + }, + { + title: t('credentials'), + href: "/{orgId}/settings/sites/{niceId}/credentials" } ]; diff --git a/src/components/ClientInfoCard.tsx b/src/components/ClientInfoCard.tsx index ec8ecacf..f8d96158 100644 --- a/src/components/ClientInfoCard.tsx +++ b/src/components/ClientInfoCard.tsx @@ -1,7 +1,6 @@ "use client"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { InfoIcon } from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { useClientContext } from "@app/hooks/useClientContext"; import { InfoSection, @@ -19,9 +18,7 @@ export default function SiteInfoCard({}: ClientInfoCardProps) { return ( - - {t("clientInformation")} - + <> From 58a13de0ff2e7a29100f0578ba7ca014c0a39885 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sun, 26 Oct 2025 21:09:00 +0530 Subject: [PATCH 09/63] fix lint --- server/routers/site/reGenerateSiteSecret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/site/reGenerateSiteSecret.ts b/server/routers/site/reGenerateSiteSecret.ts index 979212a4..73d89a48 100644 --- a/server/routers/site/reGenerateSiteSecret.ts +++ b/server/routers/site/reGenerateSiteSecret.ts @@ -21,7 +21,7 @@ const updateSiteBodySchema = z newtId: z.string().min(1).max(255).optional(), newtSecret: z.string().min(1).max(255).optional(), }) - .strict() + .strict(); registry.registerPath({ method: "post", From 3756aaecdab99b19aa930ac107180704c0d2a619 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sun, 26 Oct 2025 21:24:48 +0530 Subject: [PATCH 10/63] change file naming structure to reGenerate exit node keys --- server/private/routers/external.ts | 4 ++-- server/private/routers/remoteExitNode/index.ts | 2 +- .../{updateRemoteExitNode.ts => reGenerateExitNodeSecret.ts} | 2 +- .../remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename server/private/routers/remoteExitNode/{updateRemoteExitNode.ts => reGenerateExitNodeSecret.ts} (98%) diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 8e2b2bbc..493e9646 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -237,11 +237,11 @@ authenticated.put( ); authenticated.put( - "/org/:orgId/update-remote-exit-node", + "/org/:orgId/reGenerate-remote-exit-node-secret", verifyValidLicense, verifyOrgAccess, verifyUserHasAction(ActionsEnum.updateRemoteExitNode), - remoteExitNode.updateRemoteExitNode + remoteExitNode.reGenerateExitNodeSecret ); authenticated.get( diff --git a/server/private/routers/remoteExitNode/index.ts b/server/private/routers/remoteExitNode/index.ts index 5677520d..7c001098 100644 --- a/server/private/routers/remoteExitNode/index.ts +++ b/server/private/routers/remoteExitNode/index.ts @@ -21,4 +21,4 @@ export * from "./deleteRemoteExitNode"; export * from "./listRemoteExitNodes"; export * from "./pickRemoteExitNodeDefaults"; export * from "./quickStartRemoteExitNode"; -export * from "./updateRemoteExitNode"; +export * from "./reGenerateExitNodeSecret"; diff --git a/server/private/routers/remoteExitNode/updateRemoteExitNode.ts b/server/private/routers/remoteExitNode/reGenerateExitNodeSecret.ts similarity index 98% rename from server/private/routers/remoteExitNode/updateRemoteExitNode.ts rename to server/private/routers/remoteExitNode/reGenerateExitNodeSecret.ts index 9de017f8..b3785d2e 100644 --- a/server/private/routers/remoteExitNode/updateRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/reGenerateExitNodeSecret.ts @@ -32,7 +32,7 @@ const bodySchema = z }) .strict(); -export async function updateRemoteExitNode( +export async function reGenerateExitNodeSecret( req: Request, res: Response, next: NextFunction diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index 38ccc334..12a79dd6 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -79,7 +79,7 @@ export default function CredentialsPage() { const response = await api.put< AxiosResponse - >(`/org/${orgId}/update-remote-exit-node`, { + >(`/org/${orgId}/reGenerate-remote-exit-node-secret`, { remoteExitNodeId: remoteExitNode.remoteExitNodeId, secret: credentials.secret, }); From 563a5b3e7e594d2cbeee69d3e5ae0c61e2609b76 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Mon, 27 Oct 2025 19:16:11 +0530 Subject: [PATCH 11/63] disable credential regenerate button for local and wireguard --- src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx index 14840b66..cb88ecba 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -117,7 +117,7 @@ export default function CredentialsPage() { From 18cdf070c72e5bbdcfb3a5ae2bbe7912f7de0b20 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Mon, 27 Oct 2025 19:34:43 +0530 Subject: [PATCH 12/63] add view setting options --- .../remote-exit-nodes/ExitNodesTable.tsx | 8 ++++++++ src/components/ClientsTable.tsx | 16 ++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx index 06da3dc5..0834608d 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx @@ -263,6 +263,14 @@ export default function ExitNodesTable({ + + + ); } diff --git a/src/components/ClientsTable.tsx b/src/components/ClientsTable.tsx index 471cdf28..e5f6a006 100644 --- a/src/components/ClientsTable.tsx +++ b/src/components/ClientsTable.tsx @@ -278,14 +278,14 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) { - {/* */} - {/* */} - {/* View settings */} - {/* */} - {/* */} + + + View settings + + { setSelectedClient(clientRow); From f7e7993fd4dacd8a3e27a0aee88409670839059a Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Tue, 28 Oct 2025 19:26:58 +0530 Subject: [PATCH 13/63] regenerate secret for wireguard --- server/routers/site/reGenerateSiteSecret.ts | 123 ++++++--- .../sites/[niceId]/credentials/page.tsx | 251 +++++++++++++----- 2 files changed, 272 insertions(+), 102 deletions(-) diff --git a/server/routers/site/reGenerateSiteSecret.ts b/server/routers/site/reGenerateSiteSecret.ts index 73d89a48..7965b6f8 100644 --- a/server/routers/site/reGenerateSiteSecret.ts +++ b/server/routers/site/reGenerateSiteSecret.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, newts } from "@server/db"; +import { db, newts, sites } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -9,6 +9,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { hashPassword } from "@server/auth/password"; +import { addPeer } from "../gerbil/peers"; + const updateSiteParamsSchema = z .object({ @@ -18,28 +20,31 @@ const updateSiteParamsSchema = z const updateSiteBodySchema = z .object({ + type: z.enum(["newt", "wireguard"]), newtId: z.string().min(1).max(255).optional(), newtSecret: z.string().min(1).max(255).optional(), + exitNodeId: z.number().int().positive().optional(), + pubKey: z.string().optional(), + subnet: z.string().optional(), }) .strict(); registry.registerPath({ method: "post", path: "/site/{siteId}/regenerate-secret", - description: - "Regenerate a site's Newt credentials by its site ID.", + description: "Regenerate a site's Newt or WireGuard credentials by its site ID.", tags: [OpenAPITags.Site], request: { params: updateSiteParamsSchema, body: { content: { "application/json": { - schema: updateSiteBodySchema - } - } - } + schema: updateSiteBodySchema, + }, + }, + }, }, - responses: {} + responses: {}, }); export async function reGenerateSiteSecret( @@ -51,56 +56,100 @@ export async function reGenerateSiteSecret( const parsedParams = updateSiteParamsSchema.safeParse(req.params); if (!parsedParams.success) { return next( - createHttpError( - HttpCode.BAD_REQUEST, - fromError(parsedParams.error).toString() - ) + createHttpError(HttpCode.BAD_REQUEST, fromError(parsedParams.error).toString()) ); } const parsedBody = updateSiteBodySchema.safeParse(req.body); if (!parsedBody.success) { return next( - createHttpError( - HttpCode.BAD_REQUEST, - fromError(parsedBody.error).toString() - ) + createHttpError(HttpCode.BAD_REQUEST, fromError(parsedBody.error).toString()) ); } const { siteId } = parsedParams.data; - const { newtId, newtSecret } = parsedBody.data; + const { type, exitNodeId, pubKey, subnet, newtId, newtSecret } = parsedBody.data; - const secretHash = await hashPassword(newtSecret!); - const updatedSite = await db - .update(newts) - .set({ - newtId, - secretHash - }) - .where(eq(newts.siteId, siteId)) - .returning(); + let updatedSite = undefined; - if (updatedSite.length === 0) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - `Site with ID ${siteId} not found` - ) - ); + if (type === "newt") { + if (!newtSecret) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "newtSecret is required for newt sites") + ); + } + + const secretHash = await hashPassword(newtSecret); + + updatedSite = await db + .update(newts) + .set({ + newtId, + secretHash, + }) + .where(eq(newts.siteId, siteId)) + .returning(); + + logger.info(`Regenerated Newt credentials for site ${siteId}`); + + } else if (type === "wireguard") { + if (!pubKey) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Public key is required for wireguard sites") + ); + } + + if (!exitNodeId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Exit node ID is required for wireguard sites" + ) + ); + } + + try { + updatedSite = await db.transaction(async (tx) => { + await addPeer(exitNodeId, { + publicKey: pubKey, + allowedIps: subnet ? [subnet] : [], + }); + const result = await tx + .update(sites) + .set({ pubKey }) + .where(eq(sites.siteId, siteId)) + .returning(); + + return result; + }); + + logger.info(`Regenerated WireGuard credentials for site ${siteId}`); + } catch (err) { + logger.error( + `Transaction failed while regenerating WireGuard secret for site ${siteId}`, + err + ); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to regenerate WireGuard credentials. Rolled back transaction." + ) + ); + } } return response(res, { - data: updatedSite[0], + data: updatedSite, success: true, error: false, message: "Credentials regenerated successfully", - status: HttpCode.OK + status: HttpCode.OK, }); + } catch (error) { - logger.error(error); + logger.error("Unexpected error in reGenerateSiteSecret", error); return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An unexpected error occurred") ); } } diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx index cb88ecba..0526149d 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -21,6 +21,9 @@ import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from import CopyToClipboard from "@app/components/CopyToClipboard"; import { PickSiteDefaultsResponse } from "@server/routers/site"; import { useSiteContext } from "@app/hooks/useSiteContext"; +import CopyTextBox from "@app/components/CopyTextBox"; +import { QRCodeCanvas } from "qrcode.react"; +import { generateKeypair } from "../wireguardConfig"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -31,12 +34,36 @@ export default function CredentialsPage() { const [newtId, setNewtId] = useState(""); const [newtSecret, setNewtSecret] = useState(""); const { site, updateSite } = useSiteContext(); - + const [wgConfig, setWgConfig] = useState(""); const [siteDefaults, setSiteDefaults] = useState(null); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); + const [publicKey, setPublicKey] = useState(""); + const [privateKey, setPrivateKey] = useState(""); + + const hydrateWireGuardConfig = ( + privateKey: string, + publicKey: string, + subnet: string, + address: string, + endpoint: string, + listenPort: string + ) => { + const wgConfig = `[Interface] +Address = ${subnet} +ListenPort = 51820 +PrivateKey = ${privateKey} + +[Peer] +PublicKey = ${publicKey} +AllowedIPs = ${address.split("/")[0]}/32 +Endpoint = ${endpoint}:${listenPort} +PersistentKeepalive = 5`; + setWgConfig(wgConfig); + }; + // Clear credentials when user leaves/reloads useEffect(() => { @@ -49,6 +76,14 @@ export default function CredentialsPage() { }, []); const handleRegenerate = async () => { + + const generatedKeypair = generateKeypair(); + + const privateKey = generatedKeypair.privateKey; + const publicKey = generatedKeypair.publicKey; + + setPrivateKey(privateKey); + setPublicKey(publicKey); try { setLoading(true); await api @@ -64,6 +99,15 @@ export default function CredentialsPage() { setNewtId(newtId); setNewtSecret(newtSecret); + hydrateWireGuardConfig( + privateKey, + data.publicKey, + data.subnet, + data.address, + data.endpoint, + data.listenPort + ); + } }); } finally { @@ -74,11 +118,46 @@ export default function CredentialsPage() { const handleSave = async () => { setLoading(true); - try { - await api.post(`/site/${site?.siteId}/regenerate-secret`, { + let payload: any = {}; + + if (site?.type === "wireguard") { + if (!siteDefaults || !wgConfig) { + toast({ + variant: "destructive", + title: t("siteErrorCreate"), + description: t("siteErrorCreateKeyPair") + }); + setLoading(false); + return; + } + + payload = { + type: "wireguard", + subnet: siteDefaults.subnet, + exitNodeId: siteDefaults.exitNodeId, + pubKey: publicKey + }; + } + if (site?.type === "newt") { + if (!siteDefaults) { + toast({ + variant: "destructive", + title: t("siteErrorCreate"), + description: t("siteErrorCreateDefaults") + }); + setLoading(false); + return; + } + + payload = { + type: "newt", newtId: siteDefaults?.newtId, - newtSecret: siteDefaults?.newtSecret, - }); + newtSecret: siteDefaults?.newtSecret + }; + } + + try { + await api.post(`/site/${site?.siteId}/regenerate-secret`, payload); toast({ title: t("credentialsSaved"), @@ -100,6 +179,7 @@ export default function CredentialsPage() { } }; + return ( @@ -117,73 +197,114 @@ export default function CredentialsPage() { ) : ( <> - - - - {t("siteNewtCredentials")} - - - {t( - "siteNewtCredentialsDescription" - )} - - - - - - - {t("newtEndpoint")} - - - - - - - - {t("newtId")} - - - - - - - - {t("newtSecretKey")} - - - - - - - - - - - - {t("copyandsavethesecredentials")} - - + {site.type === "wireguard" && ( + + + + {t("WgConfiguration")} + + + {t("WgConfigurationDescription")} + + + +
+ +
+
+ +
+
+
+ + + + {t("siteCredentialsSave")} + + + {t( + "siteCredentialsSaveDescription" + )} + + +
+
+ )} + {site.type === "newt" && ( + + + + {t("siteNewtCredentials")} + + {t( - "copyandsavethesecredentialsdescription" + "siteNewtCredentialsDescription" )} -
-
-
-
+ + + + + + + {t("newtEndpoint")} + + + + + + + + {t("newtId")} + + + + + + + + {t("newtSecretKey")} + + + + + + + + + + + + {t("copyandsavethesecredentials")} + + + {t( + "copyandsavethesecredentialsdescription" + )} + + + +
+ )}
- + - {t("siteCredentialsSave")} + {t("copyandsavethesecredentials")} {t( - "siteCredentialsSaveDescription" + "copyandsavethesecredentialsdescription" )} From 90e72c6aca7098551b3bac7c5b61a3fc01fcf8b3 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Fri, 7 Nov 2025 00:59:03 +0530 Subject: [PATCH 15/63] hide credentials tab for local sites --- messages/en-US.json | 2 +- .../[orgId]/settings/sites/[niceId]/layout.tsx | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index a7d825f5..7928b69b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2098,7 +2098,7 @@ "checkSelectedStatus": "Check Status of Selected", "credentials": "Credentials", "savecredentials": "Save Credentials", - "regeneratecredentials": "Regenerate Credentials", + "regeneratecredentials": "Re-key", "regenerateCredentials": "Regenerate and save your credentials", "generatedcredentials": "Generated Credentials", "copyandsavethesecredentials": "Copy and save these credentials", diff --git a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx index abd9aefb..01008dab 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx @@ -35,18 +35,23 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const navItems = [ { title: t('general'), - href: "/{orgId}/settings/sites/{niceId}/general" + href: `/${params.orgId}/settings/sites/${params.niceId}/general`, }, - { - title: t('credentials'), - href: "/{orgId}/settings/sites/{niceId}/credentials" - } + ...(site.type !== 'local' + ? [ + { + title: t('credentials'), + href: `/${params.orgId}/settings/sites/${params.niceId}/credentials`, + }, + ] + : []), ]; + return ( <> From 2b8204fdc8b916546497b96f378d00b6173a69eb Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Fri, 7 Nov 2025 23:30:24 +0530 Subject: [PATCH 16/63] seperate credentials rekeying in modal for reuse --- messages/en-US.json | 8 +- .../[remoteExitNodeId]/credentials/page.tsx | 164 +++------ .../clients/[clientId]/credentials/page.tsx | 189 ++-------- .../sites/[niceId]/credentials/page.tsx | 337 +++++------------- src/components/RegenerateCredentialsModal.tsx | 216 +++++++++++ 5 files changed, 391 insertions(+), 523 deletions(-) create mode 100644 src/components/RegenerateCredentialsModal.tsx diff --git a/messages/en-US.json b/messages/en-US.json index 7928b69b..dc4b429a 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2106,5 +2106,11 @@ "credentialsSaved" : "Credentials Saved", "credentialsSavedDescription": "Credentials have been regenerated and saved successfully.", "credentialsSaveError": "Credentials Save Error", - "credentialsSaveErrorDescription": "An error occurred while regenerating and saving the credentials." + "credentialsSaveErrorDescription": "An error occurred while regenerating and saving the credentials.", + "regenerateCredentialsWarning": "Regenerating credentials will invalidate the previous ones. Make sure to update any configurations that use these credentials.", + "confirm": "Confirm", + "regenerateCredentialsConfirmation": "Are you sure you want to regenerate the credentials?", + "endpoint": "Endpoint", + "id": "Id", + "SecretKey": "Secret Key" } diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index 12a79dd6..b605ad35 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { SettingsContainer, SettingsSection, @@ -10,9 +10,6 @@ import { SettingsSectionTitle } from "@app/components/Settings"; import { Button } from "@app/components/ui/button"; -import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; -import { InfoIcon } from "lucide-react"; -import CopyTextBox from "@app/components/CopyTextBox"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; @@ -24,6 +21,7 @@ import { QuickStartRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types"; import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext"; +import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -31,79 +29,44 @@ export default function CredentialsPage() { const { orgId } = useParams(); const router = useRouter(); const t = useTranslations(); - const { remoteExitNode, updateRemoteExitNode } = useRemoteExitNodeContext(); + const { remoteExitNode } = useRemoteExitNodeContext(); - const [credentials, setCredentials] = - useState(null); - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + const [credentials, setCredentials] = useState(null); - // Clear credentials when user leaves/reloads - useEffect(() => { - const clearCreds = () => setCredentials(null); - window.addEventListener("beforeunload", clearCreds); - return () => window.removeEventListener("beforeunload", clearCreds); - }, []); + const handleConfirmRegenerate = async () => { + + const response = await api.get>( + `/org/${orgId}/pick-remote-exit-node-defaults` + ); - const handleRegenerate = async () => { - try { - setLoading(true); - const response = await api.get< - AxiosResponse - >(`/org/${orgId}/pick-remote-exit-node-defaults`); + const data = response.data.data; + setCredentials(data); - setCredentials(response.data.data); - toast({ - title: t("success"), - description: t("Credentials generated successfully."), - }); - } catch (error) { - toast({ - title: t("error"), - description: formatAxiosError( - error, - t("Failed to generate credentials") - ), - variant: "destructive", - }); - } finally { - setLoading(false); - } + await api.put>( + `/org/${orgId}/reGenerate-remote-exit-node-secret`, + { + remoteExitNodeId: remoteExitNode.remoteExitNodeId, + secret: data.secret, + } + ); + + toast({ + title: t("credentialsSaved"), + description: t("credentialsSavedDescription") + }); + + router.refresh(); }; - const handleSave = async () => { - if (!credentials) return; - - try { - setSaving(true); - - const response = await api.put< - AxiosResponse - >(`/org/${orgId}/reGenerate-remote-exit-node-secret`, { - remoteExitNodeId: remoteExitNode.remoteExitNodeId, - secret: credentials.secret, - }); - - toast({ - title: t("success"), - description: t("Credentials saved successfully."), - }); - - // For security, clear them from UI - setCredentials(null); - - } catch (error) { - toast({ - title: t("error"), - description: formatAxiosError( - error, - t("Failed to save credentials") - ), - variant: "destructive", - }); - } finally { - setSaving(false); + const getCredentials = () => { + if (credentials) { + return { + Id: remoteExitNode.remoteExitNodeId, + Secret: credentials.secret + }; } + return undefined; }; return ( @@ -114,58 +77,25 @@ export default function CredentialsPage() { {t("generatedcredentials")} - {t("regenerateClientCredentials")} + {t("regenerateCredentials")} - {!credentials ? ( - - ) : ( - <> - - - - - - {t("copyandsavethesecredentials")} - - - {t( - "copyandsavethesecredentialsdescription" - )} - - - -
- - -
- - )} +
+ +
); -} +} \ No newline at end of file diff --git a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx index 7d34b5eb..e4a67544 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { SettingsContainer, SettingsSection, @@ -10,17 +10,14 @@ import { SettingsSectionTitle } from "@app/components/Settings"; import { Button } from "@app/components/ui/button"; -import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; -import { InfoIcon } from "lucide-react"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; -import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; -import CopyToClipboard from "@app/components/CopyToClipboard"; import { PickClientDefaultsResponse } from "@server/routers/client"; import { useClientContext } from "@app/hooks/useClientContext"; +import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -28,55 +25,21 @@ export default function CredentialsPage() { const { orgId } = useParams(); const router = useRouter(); const t = useTranslations(); - const [olmId, setOlmId] = useState(""); - const [olmSecret, setOlmSecret] = useState(""); - const { client, updateClient } = useClientContext(); + const { client } = useClientContext(); + + const [modalOpen, setModalOpen] = useState(false); + const [clientDefaults, setClientDefaults] = useState(null); - const [clientDefaults, setClientDefaults] = - useState(null); - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); + const handleConfirmRegenerate = async () => { + + const res = await api.get(`/org/${orgId}/pick-client-defaults`); + if (res && res.status === 200) { + const data = res.data.data; + setClientDefaults(data); - // Clear credentials when user leaves/reloads - useEffect(() => { - const clearCreds = () => { - setOlmId(""); - setOlmSecret(""); - }; - window.addEventListener("beforeunload", clearCreds); - return () => window.removeEventListener("beforeunload", clearCreds); - }, []); - - const handleRegenerate = async () => { - try { - setLoading(true); - await api - .get(`/org/${orgId}/pick-client-defaults`) - .then((res) => { - if (res && res.status === 200) { - const data = res.data.data; - - setClientDefaults(data); - - const olmId = data.olmId; - const olmSecret = data.olmSecret; - setOlmId(olmId); - setOlmSecret(olmSecret); - - } - }); - } finally { - setLoading(false); - } - }; - - const handleSave = async () => { - setLoading(true); - - try { await api.post(`/client/${client?.clientId}/regenerate-secret`, { - olmId: clientDefaults?.olmId, - secret: clientDefaults?.olmSecret, + olmId: data.olmId, + secret: data.olmSecret, }); toast({ @@ -85,20 +48,19 @@ export default function CredentialsPage() { }); router.refresh(); - } catch (e) { - toast({ - variant: "destructive", - title: t("credentialsSaveError"), - description: formatAxiosError( - e, - t("credentialsSaveErrorDescription") - ) - }); - } finally { - setLoading(false); } }; + const getCredentials = () => { + if (clientDefaults) { + return { + Id: clientDefaults.olmId, + Secret: clientDefaults.olmSecret + }; + } + return undefined; + }; + return ( @@ -112,97 +74,20 @@ export default function CredentialsPage() { - {!clientDefaults ? ( - - ) : ( - <> - - - - {t("clientOlmCredentials")} - - - {t("clientOlmCredentialsDescription")} - - - - - - - {t("olmEndpoint")} - - - - - - - - {t("olmId")} - - - - - - - - {t("olmSecretKey")} - - - - - - - - - - - {t("copyandsavethesecredentials")} - - - {t( - "copyandsavethesecredentialsdescription" - )} - - - - - -
- - -
- - )} +
+ +
); -} +} \ No newline at end of file diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx index 1420680c..3942ef83 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { SettingsContainer, SettingsSection, @@ -10,20 +10,15 @@ import { SettingsSectionTitle } from "@app/components/Settings"; import { Button } from "@app/components/ui/button"; -import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; -import { InfoIcon } from "lucide-react"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; -import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; -import CopyToClipboard from "@app/components/CopyToClipboard"; import { PickSiteDefaultsResponse } from "@server/routers/site"; import { useSiteContext } from "@app/hooks/useSiteContext"; -import CopyTextBox from "@app/components/CopyTextBox"; -import { QRCodeCanvas } from "qrcode.react"; import { generateKeypair } from "../wireguardConfig"; +import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -31,17 +26,12 @@ export default function CredentialsPage() { const { orgId } = useParams(); const router = useRouter(); const t = useTranslations(); - const [newtId, setNewtId] = useState(""); - const [newtSecret, setNewtSecret] = useState(""); - const { site, updateSite } = useSiteContext(); + const { site } = useSiteContext(); + + const [modalOpen, setModalOpen] = useState(false); + const [siteDefaults, setSiteDefaults] = useState(null); const [wgConfig, setWgConfig] = useState(""); - const [siteDefaults, setSiteDefaults] = - useState(null); - - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); const [publicKey, setPublicKey] = useState(""); - const [privateKey, setPrivateKey] = useState(""); const hydrateWireGuardConfig = ( privateKey: string, @@ -51,7 +41,7 @@ export default function CredentialsPage() { endpoint: string, listenPort: string ) => { - const wgConfig = `[Interface] + const config = `[Interface] Address = ${subnet} ListenPort = 51820 PrivateKey = ${privateKey} @@ -61,124 +51,83 @@ PublicKey = ${publicKey} AllowedIPs = ${address.split("/")[0]}/32 Endpoint = ${endpoint}:${listenPort} PersistentKeepalive = 5`; - setWgConfig(wgConfig); + setWgConfig(config); + return config; }; - - // Clear credentials when user leaves/reloads - useEffect(() => { - const clearCreds = () => { - setNewtId(""); - setNewtSecret(""); - }; - window.addEventListener("beforeunload", clearCreds); - return () => window.removeEventListener("beforeunload", clearCreds); - }, []); - - const handleRegenerate = async () => { - - const generatedKeypair = generateKeypair(); - - const privateKey = generatedKeypair.privateKey; - const publicKey = generatedKeypair.publicKey; - - setPrivateKey(privateKey); - setPublicKey(publicKey); - try { - setLoading(true); - await api - .get(`/org/${orgId}/pick-site-defaults`) - .then((res) => { - if (res && res.status === 200) { - const data = res.data.data; - - setSiteDefaults(data); - - const newtId = data.newtId; - const newtSecret = data.newtSecret; - setNewtId(newtId); - setNewtSecret(newtSecret); - - hydrateWireGuardConfig( - privateKey, - data.publicKey, - data.subnet, - data.address, - data.endpoint, - data.listenPort - ); - - } - }); - } finally { - setLoading(false); - } - }; - - const handleSave = async () => { - setLoading(true); - - let payload: any = {}; + const handleConfirmRegenerate = async () => { + let generatedPublicKey = ""; + let generatedWgConfig = ""; if (site?.type === "wireguard") { - if (!siteDefaults || !wgConfig) { - toast({ - variant: "destructive", - title: t("siteErrorCreate"), - description: t("siteErrorCreateKeyPair") - }); - setLoading(false); - return; + const generatedKeypair = generateKeypair(); + generatedPublicKey = generatedKeypair.publicKey; + setPublicKey(generatedPublicKey); + + const res = await api.get(`/org/${orgId}/pick-site-defaults`); + if (res && res.status === 200) { + const data = res.data.data; + setSiteDefaults(data); + + // generate config with the fetched data + generatedWgConfig = hydrateWireGuardConfig( + generatedKeypair.privateKey, + data.publicKey, + data.subnet, + data.address, + data.endpoint, + data.listenPort + ); } - payload = { + await api.post(`/site/${site?.siteId}/regenerate-secret`, { type: "wireguard", - subnet: siteDefaults.subnet, - exitNodeId: siteDefaults.exitNodeId, - pubKey: publicKey - }; + subnet: res.data.data.subnet, + exitNodeId: res.data.data.exitNodeId, + pubKey: generatedPublicKey + }); } + if (site?.type === "newt") { - if (!siteDefaults) { - toast({ - variant: "destructive", - title: t("siteErrorCreate"), - description: t("siteErrorCreateDefaults") + const res = await api.get(`/org/${orgId}/pick-site-defaults`); + if (res && res.status === 200) { + const data = res.data.data; + setSiteDefaults(data); + + await api.post(`/site/${site?.siteId}/regenerate-secret`, { + type: "newt", + newtId: data.newtId, + newtSecret: data.newtSecret }); - setLoading(false); - return; } - - payload = { - type: "newt", - newtId: siteDefaults?.newtId, - newtSecret: siteDefaults?.newtSecret - }; } - try { - await api.post(`/site/${site?.siteId}/regenerate-secret`, payload); + toast({ + title: t("credentialsSaved"), + description: t("credentialsSavedDescription") + }); - toast({ - title: t("credentialsSaved"), - description: t("credentialsSavedDescription") - }); - - router.refresh(); - } catch (e) { - toast({ - variant: "destructive", - title: t("credentialsSaveError"), - description: formatAxiosError( - e, - t("credentialsSaveErrorDescription") - ) - }); - } finally { - setLoading(false); - } + router.refresh(); }; + const getCredentialType = () => { + if (site?.type === "wireguard") return "site-wireguard"; + if (site?.type === "newt") return "site-newt"; + return "site-newt"; + }; + + const getCredentials = () => { + if (site?.type === "wireguard" && wgConfig) { + return { wgConfig }; + } + if (site?.type === "newt" && siteDefaults) { + return { + Id: siteDefaults.newtId, + Secret: siteDefaults.newtSecret + }; + } + return undefined; + }; return ( @@ -193,141 +142,23 @@ PersistentKeepalive = 5`; - {!siteDefaults ? ( - - ) : ( - <> - {site.type === "wireguard" && ( - - - - {t("WgConfiguration")} - - - {t("WgConfigurationDescription")} - - - -
- -
-
- -
-
-
- - - - {t("copyandsavethesecredentials")} - - - {t( - "copyandsavethesecredentialsdescription" - )} - - -
-
- )} - {site.type === "newt" && ( - - - - {t("siteNewtCredentials")} - - - {t( - "siteNewtCredentialsDescription" - )} - - - - - - - {t("newtEndpoint")} - - - - - - - - {t("newtId")} - - - - - - - - {t("newtSecretKey")} - - - - - - - - - - - - {t("copyandsavethesecredentials")} - - - {t( - "copyandsavethesecredentialsdescription" - )} - - - - - )} - -
- - -
- - )} +
+ +
); -} +} \ No newline at end of file diff --git a/src/components/RegenerateCredentialsModal.tsx b/src/components/RegenerateCredentialsModal.tsx new file mode 100644 index 00000000..f485746b --- /dev/null +++ b/src/components/RegenerateCredentialsModal.tsx @@ -0,0 +1,216 @@ +"use client"; + +import { useState } from "react"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "@app/components/Credenza"; +import { Button } from "@app/components/ui/button"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { InfoIcon, AlertTriangle } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; +import CopyToClipboard from "@app/components/CopyToClipboard"; +import CopyTextBox from "@app/components/CopyTextBox"; +import { QRCodeCanvas } from "qrcode.react"; + +type CredentialType = "site-wireguard" | "site-newt" | "client-olm" | "remote-exit-node"; + +interface RegenerateCredentialsModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + type: CredentialType; + onConfirmRegenerate: () => Promise; + dashboardUrl: string; + credentials?: { + // For WireGuard sites + wgConfig?: string; + + Id?: string; + Secret?: string; + }; +} + +export default function RegenerateCredentialsModal({ + open, + onOpenChange, + type, + onConfirmRegenerate, + dashboardUrl, + credentials +}: RegenerateCredentialsModalProps) { + const t = useTranslations(); + const [stage, setStage] = useState<"confirm" | "show">("confirm"); + const [loading, setLoading] = useState(false); + + const handleConfirm = async () => { + try { + setLoading(true); + await onConfirmRegenerate(); + setStage("show"); + } catch (error) { + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + setStage("confirm"); + onOpenChange(false); + }; + + const getTitle = () => { + if (stage === "confirm") { + return t("regeneratecredentials"); + } + switch (type) { + case "site-wireguard": + return t("WgConfiguration"); + case "site-newt": + return t("siteNewtCredentials"); + case "client-olm": + return t("clientOlmCredentials"); + case "remote-exit-node": + return t("remoteExitNodeCreate.generate.title"); + } + }; + + const getDescription = () => { + if (stage === "confirm") { + return t("regenerateCredentialsWarning"); + } + switch (type) { + case "site-wireguard": + return t("WgConfigurationDescription"); + case "site-newt": + return t("siteNewtCredentialsDescription"); + case "client-olm": + return t("clientOlmCredentialsDescription"); + case "remote-exit-node": + return t("remoteExitNodeCreate.generate.description"); + } + }; + + return ( + + + + {getTitle()} + {getDescription()} + + + + {stage === "confirm" ? ( + + + + {t("warning")} + + + {t("regenerateCredentialsConfirmation")} + + + ) : ( + <> + {credentials?.wgConfig && ( +
+
+ +
+
+ +
+
+
+ + + + + {t("copyandsavethesecredentials")} + + + {t("copyandsavethesecredentialsdescription")} + + +
+ )} + + {credentials?.Id && credentials.Secret && ( +
+ + + + {t("endpoint")} + + + + + + + + {t("Id")} + + + + + + + + {t("SecretKey")} + + + + + + + + + + {t("copyandsavethesecredentials")} + + + {t("copyandsavethesecredentialsdescription")} + + +
+ + )} + + )} +
+ + + {stage === "confirm" ? ( + <> + + + + + + ) : ( + + )} + +
+
+ ); +} \ No newline at end of file From 669817818aedb1bb661b7b3333d4b03164f91710 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:07:29 +0000 Subject: [PATCH 17/63] Bump eslint-config-next from 15.5.6 to 16.0.1 Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.5.6 to 16.0.1. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/commits/v16.0.1/packages/eslint-config-next) --- updated-dependencies: - dependency-name: eslint-config-next dependency-version: 16.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 117 ++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 52 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0caef8d..2004db01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "date-fns": "4.1.0", "drizzle-orm": "0.44.7", "eslint": "9.39.0", - "eslint-config-next": "15.5.6", + "eslint-config-next": "16.0.1", "express": "5.1.0", "express-rate-limit": "8.2.1", "glob": "11.0.3", @@ -162,7 +162,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1618,7 +1617,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -1633,7 +1631,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1643,7 +1640,6 @@ "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -1674,7 +1670,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1684,7 +1679,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", @@ -1701,7 +1695,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -1717,7 +1710,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -1734,7 +1726,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1744,7 +1735,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1754,7 +1744,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -1768,7 +1757,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -1784,7 +1772,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1803,7 +1790,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -1821,7 +1807,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -1837,7 +1822,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1856,7 +1840,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1866,7 +1849,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1876,7 +1858,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1886,7 +1867,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -1900,7 +1880,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.0" @@ -1916,7 +1895,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1931,7 +1909,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -1947,7 +1924,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -1966,7 +1942,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -3797,7 +3772,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3819,7 +3793,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -3840,14 +3813,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3922,9 +3893,9 @@ "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.6.tgz", - "integrity": "sha512-YxDvsT2fwy1j5gMqk3ppXlsgDopHnkM4BoxSVASbvvgh5zgsK8lvWerDzPip8k3WVzsTZ1O7A7si1KNfN4OZfQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.1.tgz", + "integrity": "sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==", "license": "MIT", "dependencies": { "fast-glob": "3.3.1" @@ -7322,12 +7293,6 @@ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "license": "MIT" }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.14.0.tgz", - "integrity": "sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==", - "license": "MIT" - }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -10132,7 +10097,6 @@ "version": "2.8.16", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -10241,7 +10205,6 @@ "version": "4.26.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10761,7 +10724,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -11486,7 +11448,6 @@ "version": "1.5.235", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.235.tgz", "integrity": "sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==", - "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { @@ -12067,24 +12028,23 @@ } }, "node_modules/eslint-config-next": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.6.tgz", - "integrity": "sha512-cGr3VQlPsZBEv8rtYp4BpG1KNXDqGvPo9VC1iaCgIA11OfziC/vczng+TnAS3WpRIR3Q5ye/6yl+CRUuZ1fPGg==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.1.tgz", + "integrity": "sha512-wNuHw5gNOxwLUvpg0cu6IL0crrVC9hAwdS/7UwleNkwyaMiWIOAwf8yzXVqBBzL3c9A7jVRngJxjoSpPP1aEhg==", "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.5.6", - "@rushstack/eslint-patch": "^1.10.3", - "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@next/eslint-plugin-next": "16.0.1", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.31.0", + "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^5.0.0" + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" }, "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "peerDependenciesMeta": { @@ -12093,6 +12053,18 @@ } } }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -12286,12 +12258,19 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" @@ -13037,7 +13016,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -13219,7 +13197,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -13393,6 +13370,21 @@ "node": ">=18.0.0" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/html-to-text": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", @@ -14186,7 +14178,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -14224,7 +14215,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -14785,7 +14775,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -15407,7 +15396,6 @@ "version": "2.0.23", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", - "dev": true, "license": "MIT" }, "node_modules/nodemailer": { @@ -21833,7 +21821,6 @@ "version": "8.46.3", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.3.tgz", "integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.3", @@ -21925,7 +21912,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -22549,7 +22535,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" }, "node_modules/yaml": { diff --git a/package.json b/package.json index 18f3f4b6..480da7e4 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "date-fns": "4.1.0", "drizzle-orm": "0.44.7", "eslint": "9.39.0", - "eslint-config-next": "15.5.6", + "eslint-config-next": "16.0.1", "express": "5.1.0", "express-rate-limit": "8.2.1", "glob": "11.0.3", From 8a5f59cb9ffc014437777ce6ce5273050c8839fb Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sat, 8 Nov 2025 01:38:47 +0530 Subject: [PATCH 18/63] disable re-key button for non licensed --- messages/en-US.json | 3 +- .../[remoteExitNodeId]/credentials/page.tsx | 40 +++++++++++++++-- .../clients/[clientId]/credentials/page.tsx | 41 ++++++++++++++--- .../sites/[niceId]/credentials/page.tsx | 45 +++++++++++++++---- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index dc4b429a..c9d55062 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2112,5 +2112,6 @@ "regenerateCredentialsConfirmation": "Are you sure you want to regenerate the credentials?", "endpoint": "Endpoint", "id": "Id", - "SecretKey": "Secret Key" + "SecretKey": "Secret Key", + "featureDisabledTooltip": "This feature is only available in the enterprise plan and require a license to use it." } diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index b605ad35..0fcdcbbb 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -22,6 +22,10 @@ import { } from "@server/routers/remoteExitNode/types"; import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext"; import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; +import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; +import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; +import { build } from "@server/build"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -34,8 +38,19 @@ export default function CredentialsPage() { const [modalOpen, setModalOpen] = useState(false); const [credentials, setCredentials] = useState(null); + const { licenseStatus, isUnlocked } = useLicenseStatusContext(); + const subscription = useSubscriptionStatusContext(); + + const isSecurityFeatureDisabled = () => { + const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked(); + const isSaasNotSubscribed = + build === "saas" && !subscription?.isSubscribed(); + return isEnterpriseNotLicensed || isSaasNotSubscribed; + }; + + const handleConfirmRegenerate = async () => { - + const response = await api.get>( `/org/${orgId}/pick-remote-exit-node-defaults` ); @@ -82,9 +97,26 @@ export default function CredentialsPage() { - + + + +
+ +
+
+ + {isSecurityFeatureDisabled() && ( + + {t("featureDisabledTooltip")} + + )} +
+
diff --git a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx index e4a67544..024c539a 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx @@ -18,6 +18,10 @@ import { useTranslations } from "next-intl"; import { PickClientDefaultsResponse } from "@server/routers/client"; import { useClientContext } from "@app/hooks/useClientContext"; import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; +import { build } from "@server/build"; +import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; +import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -26,12 +30,23 @@ export default function CredentialsPage() { const router = useRouter(); const t = useTranslations(); const { client } = useClientContext(); - + const [modalOpen, setModalOpen] = useState(false); const [clientDefaults, setClientDefaults] = useState(null); + const { licenseStatus, isUnlocked } = useLicenseStatusContext(); + const subscription = useSubscriptionStatusContext(); + + const isSecurityFeatureDisabled = () => { + const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked(); + const isSaasNotSubscribed = + build === "saas" && !subscription?.isSubscribed(); + return isEnterpriseNotLicensed || isSaasNotSubscribed; + }; + + const handleConfirmRegenerate = async () => { - + const res = await api.get(`/org/${orgId}/pick-client-defaults`); if (res && res.status === 200) { const data = res.data.data; @@ -74,9 +89,25 @@ export default function CredentialsPage() { - + + + +
+ +
+
+ + {isSecurityFeatureDisabled() && ( + + {t("featureDisabledTooltip")} + + )} +
+
diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx index 3942ef83..8351c730 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -19,6 +19,10 @@ import { PickSiteDefaultsResponse } from "@server/routers/site"; import { useSiteContext } from "@app/hooks/useSiteContext"; import { generateKeypair } from "../wireguardConfig"; import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; +import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; +import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; +import { build } from "@server/build"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -27,12 +31,23 @@ export default function CredentialsPage() { const router = useRouter(); const t = useTranslations(); const { site } = useSiteContext(); - + const [modalOpen, setModalOpen] = useState(false); const [siteDefaults, setSiteDefaults] = useState(null); const [wgConfig, setWgConfig] = useState(""); const [publicKey, setPublicKey] = useState(""); + const { licenseStatus, isUnlocked } = useLicenseStatusContext(); + const subscription = useSubscriptionStatusContext(); + + const isSecurityFeatureDisabled = () => { + const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked(); + const isSaasNotSubscribed = + build === "saas" && !subscription?.isSubscribed(); + return isEnterpriseNotLicensed || isSaasNotSubscribed; + }; + + const hydrateWireGuardConfig = ( privateKey: string, publicKey: string, @@ -113,7 +128,7 @@ PersistentKeepalive = 5`; const getCredentialType = () => { if (site?.type === "wireguard") return "site-wireguard"; if (site?.type === "newt") return "site-newt"; - return "site-newt"; + return "site-newt"; }; const getCredentials = () => { @@ -142,12 +157,26 @@ PersistentKeepalive = 5`; - + + + +
+ +
+
+ + {isSecurityFeatureDisabled() && ( + + {t("featureDisabledTooltip")} + + )} +
+
From b6e98632b5efa5efefcae201f1ce7d1048204f3c Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sat, 8 Nov 2025 02:43:47 +0530 Subject: [PATCH 19/63] move re-key API routes to private api --- messages/en-US.json | 2 +- server/private/routers/external.ts | 37 ++++++++++++++----- server/private/routers/re-key/index.ts | 3 ++ .../routers/re-key}/reGenerateClientSecret.ts | 2 +- .../reGenerateExitNodeSecret.ts | 25 ++++++++++++- .../routers/re-key}/reGenerateSiteSecret.ts | 4 +- .../private/routers/remoteExitNode/index.ts | 1 - server/routers/client/index.ts | 3 +- server/routers/external.ts | 13 ------- server/routers/site/index.ts | 3 +- .../[remoteExitNodeId]/credentials/page.tsx | 2 +- .../clients/[clientId]/credentials/page.tsx | 2 +- .../settings/clients/[clientId]/layout.tsx | 12 ++++-- .../sites/[niceId]/credentials/page.tsx | 4 +- .../settings/sites/[niceId]/layout.tsx | 3 +- 15 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 server/private/routers/re-key/index.ts rename server/{routers/client => private/routers/re-key}/reGenerateClientSecret.ts (98%) rename server/private/routers/{remoteExitNode => re-key}/reGenerateExitNodeSecret.ts (85%) rename server/{routers/site => private/routers/re-key}/reGenerateSiteSecret.ts (97%) diff --git a/messages/en-US.json b/messages/en-US.json index c9d55062..e7efb66b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2111,7 +2111,7 @@ "confirm": "Confirm", "regenerateCredentialsConfirmation": "Are you sure you want to regenerate the credentials?", "endpoint": "Endpoint", - "id": "Id", + "Id": "Id", "SecretKey": "Secret Key", "featureDisabledTooltip": "This feature is only available in the enterprise plan and require a license to use it." } diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 493e9646..eefd175c 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -23,11 +23,15 @@ import * as license from "#private/routers/license"; import * as generateLicense from "./generatedLicense"; import * as logs from "#private/routers/auditLogs"; import * as misc from "#private/routers/misc"; +import * as reKey from "#private/routers/re-key"; import { verifyOrgAccess, verifyUserHasAction, - verifyUserIsServerAdmin + verifyUserIsServerAdmin, + verifySiteAccess, + verifyClientAccess, + verifyClientsEnabled, } from "@server/middlewares"; import { ActionsEnum } from "@server/auth/actions"; import { @@ -236,14 +240,6 @@ authenticated.put( remoteExitNode.createRemoteExitNode ); -authenticated.put( - "/org/:orgId/reGenerate-remote-exit-node-secret", - verifyValidLicense, - verifyOrgAccess, - verifyUserHasAction(ActionsEnum.updateRemoteExitNode), - remoteExitNode.reGenerateExitNodeSecret -); - authenticated.get( "/org/:orgId/remote-exit-nodes", verifyValidLicense, @@ -411,3 +407,26 @@ authenticated.get( logActionAudit(ActionsEnum.exportLogs), logs.exportAccessAuditLogs ); + +authenticated.post( + "/re-key/:clientId/regenerate-client-secret", + verifyClientsEnabled, + verifyClientAccess, + verifyUserHasAction(ActionsEnum.reGenerateSecret), + reKey.reGenerateClientSecret +); + +authenticated.post( + "/re-key/:siteId/regenerate-site-secret", + verifySiteAccess, + verifyUserHasAction(ActionsEnum.reGenerateSecret), + reKey.reGenerateSiteSecret +); + +authenticated.put( + "/re-key/:orgId/reGenerate-remote-exit-node-secret", + verifyValidLicense, + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.updateRemoteExitNode), + reKey.reGenerateExitNodeSecret +); diff --git a/server/private/routers/re-key/index.ts b/server/private/routers/re-key/index.ts new file mode 100644 index 00000000..7e04d9e4 --- /dev/null +++ b/server/private/routers/re-key/index.ts @@ -0,0 +1,3 @@ +export * from "./reGenerateClientSecret"; +export * from "./reGenerateSiteSecret"; +export * from "./reGenerateExitNodeSecret"; \ No newline at end of file diff --git a/server/routers/client/reGenerateClientSecret.ts b/server/private/routers/re-key/reGenerateClientSecret.ts similarity index 98% rename from server/routers/client/reGenerateClientSecret.ts rename to server/private/routers/re-key/reGenerateClientSecret.ts index 2bce396a..d16d433b 100644 --- a/server/routers/client/reGenerateClientSecret.ts +++ b/server/private/routers/re-key/reGenerateClientSecret.ts @@ -29,7 +29,7 @@ export type ReGenerateSecretBody = z.infer; registry.registerPath({ method: "post", - path: "/client/{clientId}/regenerate-secret", + path: "/re-key/{clientId}/regenerate-client-secret", description: "Regenerate a client's OLM credentials by its client ID.", tags: [OpenAPITags.Client], request: { diff --git a/server/private/routers/remoteExitNode/reGenerateExitNodeSecret.ts b/server/private/routers/re-key/reGenerateExitNodeSecret.ts similarity index 85% rename from server/private/routers/remoteExitNode/reGenerateExitNodeSecret.ts rename to server/private/routers/re-key/reGenerateExitNodeSecret.ts index b3785d2e..1503e75a 100644 --- a/server/private/routers/remoteExitNode/reGenerateExitNodeSecret.ts +++ b/server/private/routers/re-key/reGenerateExitNodeSecret.ts @@ -23,7 +23,11 @@ import { hashPassword } from "@server/auth/password"; import logger from "@server/logger"; import { and, eq } from "drizzle-orm"; import { UpdateRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types"; -import { paramsSchema } from "./createRemoteExitNode"; +import { OpenAPITags, registry } from "@server/openApi"; + +export const paramsSchema = z.object({ + orgId: z.string() +}); const bodySchema = z .object({ @@ -32,6 +36,25 @@ const bodySchema = z }) .strict(); + +registry.registerPath({ + method: "post", + path: "/re-key/{orgId}/regenerate-secret", + description: "Regenerate a exit node credentials by its org ID.", + tags: [OpenAPITags.Org], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: bodySchema + } + } + } + }, + responses: {} +}); + export async function reGenerateExitNodeSecret( req: Request, res: Response, diff --git a/server/routers/site/reGenerateSiteSecret.ts b/server/private/routers/re-key/reGenerateSiteSecret.ts similarity index 97% rename from server/routers/site/reGenerateSiteSecret.ts rename to server/private/routers/re-key/reGenerateSiteSecret.ts index 7965b6f8..1d046933 100644 --- a/server/routers/site/reGenerateSiteSecret.ts +++ b/server/private/routers/re-key/reGenerateSiteSecret.ts @@ -9,7 +9,7 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { hashPassword } from "@server/auth/password"; -import { addPeer } from "../gerbil/peers"; +import { addPeer } from "@server/routers/gerbil/peers"; const updateSiteParamsSchema = z @@ -31,7 +31,7 @@ const updateSiteBodySchema = z registry.registerPath({ method: "post", - path: "/site/{siteId}/regenerate-secret", + path: "/re-key/{siteId}/regenerate-site-secret", description: "Regenerate a site's Newt or WireGuard credentials by its site ID.", tags: [OpenAPITags.Site], request: { diff --git a/server/private/routers/remoteExitNode/index.ts b/server/private/routers/remoteExitNode/index.ts index 7c001098..2a04f9d9 100644 --- a/server/private/routers/remoteExitNode/index.ts +++ b/server/private/routers/remoteExitNode/index.ts @@ -21,4 +21,3 @@ export * from "./deleteRemoteExitNode"; export * from "./listRemoteExitNodes"; export * from "./pickRemoteExitNodeDefaults"; export * from "./quickStartRemoteExitNode"; -export * from "./reGenerateExitNodeSecret"; diff --git a/server/routers/client/index.ts b/server/routers/client/index.ts index 9f97446e..385c7bed 100644 --- a/server/routers/client/index.ts +++ b/server/routers/client/index.ts @@ -3,5 +3,4 @@ export * from "./createClient"; export * from "./deleteClient"; export * from "./listClients"; export * from "./updateClient"; -export * from "./getClient"; -export * from "./reGenerateClientSecret"; \ No newline at end of file +export * from "./getClient"; \ No newline at end of file diff --git a/server/routers/external.ts b/server/routers/external.ts index c2c518fa..f500f483 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -178,13 +178,6 @@ authenticated.post( client.updateClient, ); -authenticated.post( - "/client/:clientId/regenerate-secret", - verifyClientsEnabled, - verifyClientAccess, - verifyUserHasAction(ActionsEnum.reGenerateSecret), - client.reGenerateClientSecret -); // authenticated.get( // "/site/:siteId/roles", @@ -200,12 +193,6 @@ authenticated.post( site.updateSite, ); -authenticated.post( - "/site/:siteId/regenerate-secret", - verifySiteAccess, - verifyUserHasAction(ActionsEnum.reGenerateSecret), - site.reGenerateSiteSecret -); authenticated.delete( "/site/:siteId", verifySiteAccess, diff --git a/server/routers/site/index.ts b/server/routers/site/index.ts index 9b8b89cb..b97557a8 100644 --- a/server/routers/site/index.ts +++ b/server/routers/site/index.ts @@ -5,5 +5,4 @@ export * from "./updateSite"; export * from "./listSites"; export * from "./listSiteRoles"; export * from "./pickSiteDefaults"; -export * from "./socketIntegration"; -export * from "./reGenerateSiteSecret"; \ No newline at end of file +export * from "./socketIntegration"; \ No newline at end of file diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index 0fcdcbbb..115b1bd3 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -59,7 +59,7 @@ export default function CredentialsPage() { setCredentials(data); await api.put>( - `/org/${orgId}/reGenerate-remote-exit-node-secret`, + `/re-key/${orgId}/reGenerate-remote-exit-node-secret`, { remoteExitNodeId: remoteExitNode.remoteExitNodeId, secret: data.secret, diff --git a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx index 024c539a..f14d49e4 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/credentials/page.tsx @@ -52,7 +52,7 @@ export default function CredentialsPage() { const data = res.data.data; setClientDefaults(data); - await api.post(`/client/${client?.clientId}/regenerate-secret`, { + await api.post(`/re-key/${client?.clientId}/regenerate-client-secret`, { olmId: data.olmId, secret: data.olmSecret, }); diff --git a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx index dc4ef0b4..257cb20f 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx @@ -8,6 +8,7 @@ import ClientProvider from "@app/providers/ClientProvider"; import { redirect } from "next/navigation"; import { HorizontalTabs } from "@app/components/HorizontalTabs"; import { getTranslations } from "next-intl/server"; +import { build } from "@server/build"; type SettingsLayoutProps = { children: React.ReactNode; @@ -38,10 +39,13 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { title: t('general'), href: `/{orgId}/settings/clients/{clientId}/general` }, - { - title: t('credentials'), - href: `/{orgId}/settings/clients/{clientId}/credentials` - } + ...(build === 'enterprise' + ? [{ + title: t('credentials'), + href: `/{orgId}/settings/clients/{clientId}/credentials` + }, + ] + : []), ]; return ( diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx index 8351c730..6dcee413 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -95,7 +95,7 @@ PersistentKeepalive = 5`; ); } - await api.post(`/site/${site?.siteId}/regenerate-secret`, { + await api.post(`/re-key/${site?.siteId}/regenerate-site-secret`, { type: "wireguard", subnet: res.data.data.subnet, exitNodeId: res.data.data.exitNodeId, @@ -109,7 +109,7 @@ PersistentKeepalive = 5`; const data = res.data.data; setSiteDefaults(data); - await api.post(`/site/${site?.siteId}/regenerate-secret`, { + await api.post(`/re-key/${site?.siteId}/regenerate-site-secret`, { type: "newt", newtId: data.newtId, newtSecret: data.newtSecret diff --git a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx index 01008dab..8ef00410 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx @@ -8,6 +8,7 @@ import { HorizontalTabs } from "@app/components/HorizontalTabs"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SiteInfoCard from "../../../../../components/SiteInfoCard"; import { getTranslations } from "next-intl/server"; +import { build } from "@server/build"; interface SettingsLayoutProps { children: React.ReactNode; @@ -37,7 +38,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { title: t('general'), href: `/${params.orgId}/settings/sites/${params.niceId}/general`, }, - ...(site.type !== 'local' + ...(site.type !== 'local' && build === 'enterprise' ? [ { title: t('credentials'), From 66c14c2d091ce0c65e5c5c857a6656faab2513f4 Mon Sep 17 00:00:00 2001 From: hetlelid Date: Sat, 8 Nov 2025 13:24:51 +0100 Subject: [PATCH 20/63] Update resourceRawSettingsDescription with details Expanded the description for resourceRawSettings to include mapping details and a documentation link. --- messages/en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en-US.json b/messages/en-US.json index 0dbb9bc9..4e14a64d 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -179,7 +179,7 @@ "baseDomain": "Base Domain", "subdomnainDescription": "The subdomain where your resource will be accessible.", "resourceRawSettings": "TCP/UDP Settings", - "resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP. You map the resource to a port on the host Pangolin server, so you can access the resource from :. (https://docs.pangolin.net/manage/resources/tcp-udp-resources)", "protocol": "Protocol", "protocolSelect": "Select a protocol", "resourcePortNumber": "Port Number", From 8e1bb6a6fd462fd9838b917dc7ca4deeac8c3cab Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Tue, 28 Oct 2025 21:29:58 +0530 Subject: [PATCH 21/63] add niceId inside info box --- messages/en-US.json | 3 ++- src/components/SiteInfoCard.tsx | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 063d9efc..fa5d229c 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2095,5 +2095,6 @@ "selectedResources": "Selected Resources", "enableSelected": "Enable Selected", "disableSelected": "Disable Selected", - "checkSelectedStatus": "Check Status of Selected" + "checkSelectedStatus": "Check Status of Selected", + "niceId": "Nice ID" } diff --git a/src/components/SiteInfoCard.tsx b/src/components/SiteInfoCard.tsx index 5eed91c5..6d2ded82 100644 --- a/src/components/SiteInfoCard.tsx +++ b/src/components/SiteInfoCard.tsx @@ -14,7 +14,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; type SiteInfoCardProps = {}; -export default function SiteInfoCard({}: SiteInfoCardProps) { +export default function SiteInfoCard({ }: SiteInfoCardProps) { const { site, updateSite } = useSiteContext(); const t = useTranslations(); const { env } = useEnvContext(); @@ -34,7 +34,15 @@ export default function SiteInfoCard({}: SiteInfoCardProps) { return ( - + + + + {t("niceId")} + + + {site.niceId} + + {(site.type == "newt" || site.type == "wireguard") && ( <> From 84d24d9bf52b698913a6096938b2cba52fa2f38f Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Tue, 28 Oct 2025 21:54:56 +0530 Subject: [PATCH 22/63] niceId inside resource info --- src/components/ResourceInfoBox.tsx | 20 ++++++++++++++------ src/components/ResourcesTable.tsx | 16 ---------------- src/components/SitesTable.tsx | 24 ------------------------ 3 files changed, 14 insertions(+), 46 deletions(-) diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 50bb9af2..e7cf2c82 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -17,7 +17,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; type ResourceInfoBoxType = {}; -export default function ResourceInfoBox({}: ResourceInfoBoxType) { +export default function ResourceInfoBox({ }: ResourceInfoBoxType) { const { resource, authInfo } = useResourceContext(); const { env } = useEnvContext(); @@ -30,8 +30,16 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) { {/* 4 cols because of the certs */} + + + {t("niceId")} + + + {resource.niceId} + + {resource.http ? ( <> @@ -40,10 +48,10 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) { {authInfo.password || - authInfo.pincode || - authInfo.sso || - authInfo.whitelist || - authInfo.headerAuth ? ( + authInfo.pincode || + authInfo.sso || + authInfo.whitelist || + authInfo.headerAuth ? (
{t("protected")} diff --git a/src/components/ResourcesTable.tsx b/src/components/ResourcesTable.tsx index 0e613da8..252ac7d2 100644 --- a/src/components/ResourcesTable.tsx +++ b/src/components/ResourcesTable.tsx @@ -507,22 +507,6 @@ export default function ResourcesTable({ ); } }, - { - accessorKey: "nice", - header: ({ column }) => { - return ( - - ); - } - }, { accessorKey: "protocol", header: t("protocol"), diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 0f1b0536..0d8b161c 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -164,30 +164,6 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { } } }, - { - accessorKey: "nice", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - return ( -
- {row.original.nice} -
- ); - } - }, { accessorKey: "mbIn", header: ({ column }) => { From 32949127d28fc0aa2604a9f6c033d80ab2990733 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Wed, 29 Oct 2025 00:12:41 +0530 Subject: [PATCH 23/63] Make site niceId editable --- server/routers/site/updateSite.ts | 19 +++++ src/components/SiteInfoCard.tsx | 124 +++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/server/routers/site/updateSite.ts b/server/routers/site/updateSite.ts index e3724f36..de0c3f9c 100644 --- a/server/routers/site/updateSite.ts +++ b/server/routers/site/updateSite.ts @@ -20,6 +20,7 @@ const updateSiteParamsSchema = z const updateSiteBodySchema = z .object({ name: z.string().min(1).max(255).optional(), + niceId: z.string().min(1).max(255).optional(), dockerSocketEnabled: z.boolean().optional(), remoteSubnets: z .string() @@ -89,6 +90,24 @@ export async function updateSite( const { siteId } = parsedParams.data; const updateData = parsedBody.data; + // if niceId is provided, check if it's already in use by another site + if (updateData.niceId) { + const existingSite = await db + .select() + .from(sites) + .where(eq(sites.niceId, updateData.niceId)) + .limit(1); + + if (existingSite.length > 0 && existingSite[0].siteId !== siteId) { + return next( + createHttpError( + HttpCode.CONFLICT, + `A site with niceId "${updateData.niceId}" already exists` + ) + ); + } + } + // if remoteSubnets is provided, ensure it's a valid comma-separated list of cidrs if (updateData.remoteSubnets) { const subnets = updateData.remoteSubnets.split(",").map((s) => s.trim()); diff --git a/src/components/SiteInfoCard.tsx b/src/components/SiteInfoCard.tsx index 6d2ded82..5e3e1194 100644 --- a/src/components/SiteInfoCard.tsx +++ b/src/components/SiteInfoCard.tsx @@ -1,7 +1,7 @@ "use client"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { InfoIcon } from "lucide-react"; +import { Check, InfoIcon, Pencil, X } from "lucide-react"; import { useSiteContext } from "@app/hooks/useSiteContext"; import { InfoSection, @@ -11,6 +11,11 @@ import { } from "@app/components/InfoSection"; import { useTranslations } from "next-intl"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { Input } from "./ui/input"; +import { Button } from "./ui/button"; +import { useState } from "react"; +import { useToast } from "@app/hooks/useToast"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; type SiteInfoCardProps = {}; @@ -18,6 +23,13 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { const { site, updateSite } = useSiteContext(); const t = useTranslations(); const { env } = useEnvContext(); + const api = createApiClient(useEnvContext()); + + const [isEditing, setIsEditing] = useState(false); + const [niceId, setNiceId] = useState(site.niceId); + const [tempNiceId, setTempNiceId] = useState(site.niceId); + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); const getConnectionTypeString = (type: string) => { if (type === "newt") { @@ -31,6 +43,71 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { } }; + const handleEdit = () => { + setTempNiceId(niceId); + setIsEditing(true); + }; + + const handleCancel = () => { + setTempNiceId(niceId); + setIsEditing(false); + }; + + const handleSave = async () => { + if (tempNiceId.trim() === "") { + toast({ + variant: "destructive", + title: t("error"), + description: t("niceIdCannotBeEmpty") + }); + return; + } + + if (tempNiceId === niceId) { + setIsEditing(false); + return; + } + + setIsLoading(true); + + try { + const response = await api.post(`/site/${site.siteId}`, { + niceId: tempNiceId.trim() + }); + + setNiceId(tempNiceId.trim()); + setIsEditing(false); + + updateSite({ + niceId: tempNiceId.trim() + }); + + toast({ + title: t("niceIdUpdated"), + description: t("niceIdUpdatedSuccessfully") + }); + } catch (e: any) { + toast({ + variant: "destructive", + title: t("niceIdUpdateError"), + description: formatAxiosError( + e, + t("niceIdUpdateErrorDescription") + ) + }); + } finally { + setIsLoading(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleSave(); + } else if (e.key === "Escape") { + handleCancel(); + } + }; + return ( @@ -40,7 +117,50 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { {t("niceId")} - {site.niceId} +
+ {isEditing ? ( + <> + setTempNiceId(e.target.value)} + onKeyDown={handleKeyDown} + disabled={isLoading} + className="flex-1" + autoFocus + /> + + + + ) : ( + <> + {niceId} + + + )} +
{(site.type == "newt" || site.type == "wireguard") && ( From feb0bd58c809febd5c2c2f38b3a69b080faea8dc Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Wed, 29 Oct 2025 18:56:38 +0530 Subject: [PATCH 24/63] make resource niceid editable --- messages/en-US.json | 7 +- server/routers/resource/updateResource.ts | 40 +++++++ src/components/ResourceInfoBox.tsx | 136 +++++++++++++++++++++- 3 files changed, 178 insertions(+), 5 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index fa5d229c..936ef98c 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2096,5 +2096,10 @@ "enableSelected": "Enable Selected", "disableSelected": "Disable Selected", "checkSelectedStatus": "Check Status of Selected", - "niceId": "Nice ID" + "niceId": "Nice ID", + "niceIdUpdated": "Nice ID Updated", + "niceIdUpdatedSuccessfully": "Nice ID Updated Successfully", + "niceIdUpdateError": "Error updating Nice ID", + "niceIdUpdateErrorDescription": "An error occurred while updating the Nice ID.", + "niceIdCannotBeEmpty": "Nice ID cannot be empty" } diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 13c5220d..bfe6b2cf 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -37,6 +37,7 @@ const updateResourceParamsSchema = z const updateHttpResourceBodySchema = z .object({ name: z.string().min(1).max(255).optional(), + niceId: z.string().min(1).max(255).optional(), subdomain: subdomainSchema.nullable().optional(), ssl: z.boolean().optional(), sso: z.boolean().optional(), @@ -97,6 +98,7 @@ export type UpdateResourceResponse = Resource; const updateRawResourceBodySchema = z .object({ name: z.string().min(1).max(255).optional(), + niceId: z.string().min(1).max(255).optional(), proxyPort: z.number().int().min(1).max(65535).optional(), stickySession: z.boolean().optional(), enabled: z.boolean().optional(), @@ -236,6 +238,25 @@ async function updateHttpResource( const updateData = parsedBody.data; + if (updateData.niceId) { + const [existingResource] = await db + .select() + .from(resources) + .where(eq(resources.niceId, updateData.niceId)); + + if ( + existingResource && + existingResource.resourceId !== resource.resourceId + ) { + return next( + createHttpError( + HttpCode.CONFLICT, + `A resource with niceId "${updateData.niceId}" already exists` + ) + ); + } + } + if (updateData.domainId) { const domainId = updateData.domainId; @@ -362,6 +383,25 @@ async function updateRawResource( const updateData = parsedBody.data; + if (updateData.niceId) { + const [existingResource] = await db + .select() + .from(resources) + .where(eq(resources.niceId, updateData.niceId)); + + if ( + existingResource && + existingResource.resourceId !== resource.resourceId + ) { + return next( + createHttpError( + HttpCode.CONFLICT, + `A resource with niceId "${updateData.niceId}" already exists` + ) + ); + } + } + const updatedResource = await db .update(resources) .set(updateData) diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index e7cf2c82..7997ccc7 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -1,7 +1,7 @@ "use client"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { InfoIcon, ShieldCheck, ShieldOff } from "lucide-react"; +import { Check, InfoIcon, Pencil, ShieldCheck, ShieldOff, X } from "lucide-react"; import { useResourceContext } from "@app/hooks/useResourceContext"; import CopyToClipboard from "@app/components/CopyToClipboard"; import { @@ -14,30 +14,158 @@ import { useTranslations } from "next-intl"; import CertificateStatus from "@app/components/private/CertificateStatus"; import { toUnicode } from "punycode"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useState } from "react"; +import { useToast } from "@app/hooks/useToast"; +import { UpdateResourceResponse } from "@server/routers/resource"; +import { AxiosResponse } from "axios"; type ResourceInfoBoxType = {}; export default function ResourceInfoBox({ }: ResourceInfoBoxType) { - const { resource, authInfo } = useResourceContext(); + const { resource, authInfo, updateResource } = useResourceContext(); const { env } = useEnvContext(); + const api = createApiClient(useEnvContext()); + + const [isEditing, setIsEditing] = useState(false); + const [niceId, setNiceId] = useState(resource.niceId); + const [tempNiceId, setTempNiceId] = useState(resource.niceId); + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); const t = useTranslations(); const fullUrl = `${resource.ssl ? "https" : "http"}://${toUnicode(resource.fullDomain || "")}`; + + const handleEdit = () => { + setTempNiceId(niceId); + setIsEditing(true); + }; + + const handleCancel = () => { + setTempNiceId(niceId); + setIsEditing(false); + }; + + const handleSave = async () => { + if (tempNiceId.trim() === "") { + toast({ + variant: "destructive", + title: t("error"), + description: t("niceIdCannotBeEmpty") + }); + return; + } + + if (tempNiceId === niceId) { + setIsEditing(false); + return; + } + + setIsLoading(true); + + try { + const res = await api + .post>( + `resource/${resource?.resourceId}`, + { + niceId: tempNiceId.trim() + } + ) + + setNiceId(tempNiceId.trim()); + setIsEditing(false); + + updateResource({ + niceId: tempNiceId.trim() + }); + + toast({ + title: t("niceIdUpdated"), + description: t("niceIdUpdatedSuccessfully") + }); + } catch (e: any) { + toast({ + variant: "destructive", + title: t("niceIdUpdateError"), + description: formatAxiosError( + e, + t("niceIdUpdateErrorDescription") + ) + }); + } finally { + setIsLoading(false); + } + }; + + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleSave(); + } else if (e.key === "Escape") { + handleCancel(); + } + }; + return ( {/* 4 cols because of the certs */} {t("niceId")} - {resource.niceId} +
+ {isEditing ? ( + <> + setTempNiceId(e.target.value)} + onKeyDown={handleKeyDown} + disabled={isLoading} + className="flex-1" + autoFocus + /> + + + + ) : ( + <> + {niceId} + + + )} +
{resource.http ? ( From f85d9f8b6e034945549dedcf0940c45bee9e3889 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Wed, 29 Oct 2025 18:58:47 +0530 Subject: [PATCH 25/63] fix col --- src/components/ResourceInfoBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 7997ccc7..545594eb 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -115,7 +115,7 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { {/* 4 cols because of the certs */} From 50ac52d3163916396cc8c0f71bcbfc56246d2b65 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Wed, 29 Oct 2025 19:10:21 +0530 Subject: [PATCH 26/63] fix lint --- src/components/ResourceInfoBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 545594eb..04f4f558 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -74,7 +74,7 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { { niceId: tempNiceId.trim() } - ) + ); setNiceId(tempNiceId.trim()); setIsEditing(false); From ac5fe1486a5dd4d0005e4317b968545cdb179abf Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Wed, 29 Oct 2025 20:10:37 +0530 Subject: [PATCH 27/63] update url to prevent page redirect --- src/components/ResourceInfoBox.tsx | 7 +++++++ src/components/SiteInfoCard.tsx | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 04f4f558..d16c4bae 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -21,6 +21,7 @@ import { useState } from "react"; import { useToast } from "@app/hooks/useToast"; import { UpdateResourceResponse } from "@server/routers/resource"; import { AxiosResponse } from "axios"; +import { useRouter, usePathname } from "next/navigation"; type ResourceInfoBoxType = {}; @@ -28,6 +29,8 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { const { resource, authInfo, updateResource } = useResourceContext(); const { env } = useEnvContext(); const api = createApiClient(useEnvContext()); + const router = useRouter(); + const pathname = usePathname(); const [isEditing, setIsEditing] = useState(false); const [niceId, setNiceId] = useState(resource.niceId); @@ -83,6 +86,10 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { niceId: tempNiceId.trim() }); + // update the URL to reflect the new niceId + const newPath = pathname.replace(`/resources/${niceId}`, `/resources/${tempNiceId.trim()}`); + router.replace(newPath); + toast({ title: t("niceIdUpdated"), description: t("niceIdUpdatedSuccessfully") diff --git a/src/components/SiteInfoCard.tsx b/src/components/SiteInfoCard.tsx index 5e3e1194..08519b22 100644 --- a/src/components/SiteInfoCard.tsx +++ b/src/components/SiteInfoCard.tsx @@ -16,6 +16,7 @@ import { Button } from "./ui/button"; import { useState } from "react"; import { useToast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useRouter, usePathname } from "next/navigation"; type SiteInfoCardProps = {}; @@ -24,6 +25,8 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { const t = useTranslations(); const { env } = useEnvContext(); const api = createApiClient(useEnvContext()); + const router = useRouter(); + const pathname = usePathname(); const [isEditing, setIsEditing] = useState(false); const [niceId, setNiceId] = useState(site.niceId); @@ -82,6 +85,10 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { niceId: tempNiceId.trim() }); + // update the URL to reflect the new niceId + const newPath = pathname.replace(`/sites/${niceId}`, `/sites/${tempNiceId.trim()}`); + router.replace(newPath); + toast({ title: t("niceIdUpdated"), description: t("niceIdUpdatedSuccessfully") From 66124f09c49ec0b899e6e9c942720b2f9fa177b3 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Thu, 30 Oct 2025 19:16:20 +0530 Subject: [PATCH 28/63] move site niceId details to general setting page --- .../settings/sites/[niceId]/general/page.tsx | 86 ++++++----- src/components/SiteInfoCard.tsx | 138 +----------------- 2 files changed, 55 insertions(+), 169 deletions(-) diff --git a/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx index 75406f48..50a1faff 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx @@ -15,7 +15,7 @@ import { import { Input } from "@/components/ui/input"; import { useSiteContext } from "@app/hooks/useSiteContext"; import { useForm } from "react-hook-form"; -import { toast } from "@app/hooks/useToast"; +import { toast, useToast } from "@app/hooks/useToast"; import { useRouter } from "next/navigation"; import { SettingsContainer, @@ -37,6 +37,7 @@ import { Tag, TagInput } from "@app/components/tags/tag-input"; const GeneralFormSchema = z.object({ name: z.string().nonempty("Name is required"), + niceId: z.string().optional(), dockerSocketEnabled: z.boolean().optional(), remoteSubnets: z .array( @@ -55,19 +56,18 @@ export default function GeneralPage() { const { env } = useEnvContext(); const api = createApiClient(useEnvContext()); - - const [loading, setLoading] = useState(false); - const [activeCidrTagIndex, setActiveCidrTagIndex] = useState( - null - ); - const router = useRouter(); const t = useTranslations(); + const { toast } = useToast(); + + const [loading, setLoading] = useState(false); + const [activeCidrTagIndex, setActiveCidrTagIndex] = useState(null); const form = useForm({ resolver: zodResolver(GeneralFormSchema), defaultValues: { name: site?.name, + niceId: site?.niceId || "", dockerSocketEnabled: site?.dockerSocketEnabled ?? false, remoteSubnets: site?.remoteSubnets ? site.remoteSubnets.split(",").map((subnet, index) => ({ @@ -82,37 +82,40 @@ export default function GeneralPage() { async function onSubmit(data: GeneralFormValues) { setLoading(true); - await api - .post(`/site/${site?.siteId}`, { + try { + await api.post(`/site/${site?.siteId}`, { name: data.name, + niceId: data.niceId, dockerSocketEnabled: data.dockerSocketEnabled, remoteSubnets: data.remoteSubnets - ?.map((subnet) => subnet.text) - .join(",") || "" - }) - .catch((e) => { - toast({ - variant: "destructive", - title: t("siteErrorUpdate"), - description: formatAxiosError( - e, - t("siteErrorUpdateDescription") - ) - }); + ?.map((subnet) => subnet.text) + .join(",") || "" }); - updateSite({ - name: data.name, - dockerSocketEnabled: data.dockerSocketEnabled, - remoteSubnets: - data.remoteSubnets?.map((subnet) => subnet.text).join(",") || "" - }); + updateSite({ + name: data.name, + niceId: data.niceId, + dockerSocketEnabled: data.dockerSocketEnabled, + remoteSubnets: + data.remoteSubnets?.map((subnet) => subnet.text).join(",") || "" + }); - toast({ - title: t("siteUpdated"), - description: t("siteUpdatedDescription") - }); + if (data.niceId && data.niceId !== site?.niceId) { + router.replace(`/${site?.orgId}/settings/sites/${data.niceId}/general`); + } + + toast({ + title: t("siteUpdated"), + description: t("siteUpdatedDescription") + }); + } catch (e) { + toast({ + variant: "destructive", + title: t("siteErrorUpdate"), + description: formatAxiosError(e, t("siteErrorUpdateDescription")) + }); + } setLoading(false); @@ -153,8 +156,25 @@ export default function GeneralPage() { )} /> - {env.flags.enableClients && - site.type === "newt" ? ( + ( + + {t("niceId") || "Nice ID"} + + + + + + )} + /> + + {env.flags.enableClients && site.type === "newt" ? ( { if (type === "newt") { @@ -46,130 +31,11 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { } }; - const handleEdit = () => { - setTempNiceId(niceId); - setIsEditing(true); - }; - - const handleCancel = () => { - setTempNiceId(niceId); - setIsEditing(false); - }; - - const handleSave = async () => { - if (tempNiceId.trim() === "") { - toast({ - variant: "destructive", - title: t("error"), - description: t("niceIdCannotBeEmpty") - }); - return; - } - - if (tempNiceId === niceId) { - setIsEditing(false); - return; - } - - setIsLoading(true); - - try { - const response = await api.post(`/site/${site.siteId}`, { - niceId: tempNiceId.trim() - }); - - setNiceId(tempNiceId.trim()); - setIsEditing(false); - - updateSite({ - niceId: tempNiceId.trim() - }); - - // update the URL to reflect the new niceId - const newPath = pathname.replace(`/sites/${niceId}`, `/sites/${tempNiceId.trim()}`); - router.replace(newPath); - - toast({ - title: t("niceIdUpdated"), - description: t("niceIdUpdatedSuccessfully") - }); - } catch (e: any) { - toast({ - variant: "destructive", - title: t("niceIdUpdateError"), - description: formatAxiosError( - e, - t("niceIdUpdateErrorDescription") - ) - }); - } finally { - setIsLoading(false); - } - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - handleSave(); - } else if (e.key === "Escape") { - handleCancel(); - } - }; return ( - - - - {t("niceId")} - - -
- {isEditing ? ( - <> - setTempNiceId(e.target.value)} - onKeyDown={handleKeyDown} - disabled={isLoading} - className="flex-1" - autoFocus - /> - - - - ) : ( - <> - {niceId} - - - )} -
-
-
+ {(site.type == "newt" || site.type == "wireguard") && ( <> From aeda85fcfb5d8f1cb89e224a3d0925a2890b258d Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Thu, 30 Oct 2025 20:37:31 +0530 Subject: [PATCH 29/63] move resource niceid update to general page --- .../resources/[niceId]/general/page.tsx | 47 ++++-- .../settings/sites/[niceId]/general/page.tsx | 2 +- src/components/ResourceInfoBox.tsx | 146 +----------------- 3 files changed, 41 insertions(+), 154 deletions(-) diff --git a/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx index 28f7754b..dfd16ad0 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx @@ -68,9 +68,9 @@ export default function GeneralForm() { const router = useRouter(); const t = useTranslations(); const [editDomainOpen, setEditDomainOpen] = useState(false); - const {licenseStatus } = useLicenseStatusContext(); + const { licenseStatus } = useLicenseStatusContext(); const subscriptionStatus = useSubscriptionStatusContext(); - const {user} = useUserContext(); + const { user } = useUserContext(); const { env } = useEnvContext(); @@ -102,6 +102,7 @@ export default function GeneralForm() { enabled: z.boolean(), subdomain: z.string().optional(), name: z.string().min(1).max(255), + niceId: z.string().min(1).max(255).optional(), domainId: z.string().optional(), proxyPort: z.number().int().min(1).max(65535).optional(), // enableProxy: z.boolean().optional() @@ -130,6 +131,7 @@ export default function GeneralForm() { defaultValues: { enabled: resource.enabled, name: resource.name, + niceId: resource.niceId, subdomain: resource.subdomain ? resource.subdomain : undefined, domainId: resource.domainId || undefined, proxyPort: resource.proxyPort || undefined, @@ -192,6 +194,7 @@ export default function GeneralForm() { { enabled: data.enabled, name: data.name, + niceId: data.niceId, subdomain: data.subdomain ? toASCII(data.subdomain) : undefined, domainId: data.domainId, proxyPort: data.proxyPort, @@ -212,16 +215,12 @@ export default function GeneralForm() { }); if (res && res.status === 200) { - toast({ - title: t("resourceUpdated"), - description: t("resourceUpdatedDescription") - }); - - const resource = res.data.data; + const updated = res.data.data; updateResource({ enabled: data.enabled, name: data.name, + niceId: data.niceId, subdomain: data.subdomain, fullDomain: resource.fullDomain, proxyPort: data.proxyPort, @@ -230,8 +229,20 @@ export default function GeneralForm() { // }) }); - router.refresh(); + toast({ + title: t("resourceUpdated"), + description: t("resourceUpdatedDescription") + }); + + if (data.niceId && data.niceId !== resource?.niceId) { + router.replace(`/${updated.orgId}/settings/resources/${data.niceId}/general`); + } else { + router.refresh(); + } + + setSaveLoading(false); } + setSaveLoading(false); } @@ -304,6 +315,24 @@ export default function GeneralForm() { )} /> + ( + + {t("niceId") || "Nice ID"} + + + + + + )} + /> + {!resource.http && ( <> { - setTempNiceId(niceId); - setIsEditing(true); - }; - - const handleCancel = () => { - setTempNiceId(niceId); - setIsEditing(false); - }; - - const handleSave = async () => { - if (tempNiceId.trim() === "") { - toast({ - variant: "destructive", - title: t("error"), - description: t("niceIdCannotBeEmpty") - }); - return; - } - - if (tempNiceId === niceId) { - setIsEditing(false); - return; - } - - setIsLoading(true); - - try { - const res = await api - .post>( - `resource/${resource?.resourceId}`, - { - niceId: tempNiceId.trim() - } - ); - - setNiceId(tempNiceId.trim()); - setIsEditing(false); - - updateResource({ - niceId: tempNiceId.trim() - }); - - // update the URL to reflect the new niceId - const newPath = pathname.replace(`/resources/${niceId}`, `/resources/${tempNiceId.trim()}`); - router.replace(newPath); - - toast({ - title: t("niceIdUpdated"), - description: t("niceIdUpdatedSuccessfully") - }); - } catch (e: any) { - toast({ - variant: "destructive", - title: t("niceIdUpdateError"), - description: formatAxiosError( - e, - t("niceIdUpdateErrorDescription") - ) - }); - } finally { - setIsLoading(false); - } - }; - - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - handleSave(); - } else if (e.key === "Escape") { - handleCancel(); - } - }; - return ( {/* 4 cols because of the certs */} - - - {t("niceId")} - - -
- {isEditing ? ( - <> - setTempNiceId(e.target.value)} - onKeyDown={handleKeyDown} - disabled={isLoading} - className="flex-1" - autoFocus - /> - - - - ) : ( - <> - {niceId} - - - )} -
-
-
{resource.http ? ( <> From ddc14d164ee42457dc042f2bd62660a3a3f95145 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 4 Nov 2025 12:47:08 -0800 Subject: [PATCH 30/63] Rename nice id to Identifier in the ui --- messages/en-US.json | 4 +++- src/app/[orgId]/settings/resources/[niceId]/general/page.tsx | 4 ++-- src/app/[orgId]/settings/sites/[niceId]/general/page.tsx | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 936ef98c..486be02a 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2101,5 +2101,7 @@ "niceIdUpdatedSuccessfully": "Nice ID Updated Successfully", "niceIdUpdateError": "Error updating Nice ID", "niceIdUpdateErrorDescription": "An error occurred while updating the Nice ID.", - "niceIdCannotBeEmpty": "Nice ID cannot be empty" + "niceIdCannotBeEmpty": "Nice ID cannot be empty", + "enterIdentifier": "Enter identifier", + "identifier": "Identifier" } diff --git a/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx index dfd16ad0..50155b3e 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/general/page.tsx @@ -320,11 +320,11 @@ export default function GeneralForm() { name="niceId" render={({ field }) => ( - {t("niceId") || "Nice ID"} + {t("identifier")} diff --git a/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx index 07655957..eef981a8 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx @@ -161,11 +161,11 @@ export default function GeneralPage() { name="niceId" render={({ field }) => ( - {t("niceId") || "Nice ID"} + {t("identifier")} From abc5f8ec68d99f3169f8032cec57df17778486bf Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Thu, 6 Nov 2025 23:36:54 +0530 Subject: [PATCH 31/63] show the identifier in the info box --- src/components/ResourceInfoBox.tsx | 10 +++++++++- src/components/SiteInfoCard.tsx | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 7d6a171e..4d77a434 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -31,8 +31,16 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { {/* 4 cols because of the certs */} + + + {t("identifier")} + + + {resource.niceId} + + {resource.http ? ( <> diff --git a/src/components/SiteInfoCard.tsx b/src/components/SiteInfoCard.tsx index 35d7120c..6d35e145 100644 --- a/src/components/SiteInfoCard.tsx +++ b/src/components/SiteInfoCard.tsx @@ -35,7 +35,15 @@ export default function SiteInfoCard({ }: SiteInfoCardProps) { return ( - + + + + {t("identifier")} + + + {site.niceId} + + {(site.type == "newt" || site.type == "wireguard") && ( <> From 0af51cebbe75996a18c2d4a9a5cfa5d276b6c7e5 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Thu, 6 Nov 2025 23:50:57 +0530 Subject: [PATCH 32/63] scope niceid to the orgId --- server/routers/resource/updateResource.ts | 14 ++++++++++++-- server/routers/site/updateSite.ts | 9 +++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index bfe6b2cf..04a57ec1 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -242,7 +242,12 @@ async function updateHttpResource( const [existingResource] = await db .select() .from(resources) - .where(eq(resources.niceId, updateData.niceId)); + .where( + and( + eq(resources.niceId, updateData.niceId), + eq(resources.orgId, resource.orgId) + ) + ); if ( existingResource && @@ -387,7 +392,12 @@ async function updateRawResource( const [existingResource] = await db .select() .from(resources) - .where(eq(resources.niceId, updateData.niceId)); + .where( + and( + eq(resources.niceId, updateData.niceId), + eq(resources.orgId, resource.orgId) + ) + ); if ( existingResource && diff --git a/server/routers/site/updateSite.ts b/server/routers/site/updateSite.ts index de0c3f9c..2041420c 100644 --- a/server/routers/site/updateSite.ts +++ b/server/routers/site/updateSite.ts @@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; import { sites } from "@server/db"; -import { eq } from "drizzle-orm"; +import { eq, and } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -95,7 +95,12 @@ export async function updateSite( const existingSite = await db .select() .from(sites) - .where(eq(sites.niceId, updateData.niceId)) + .where( + and( + eq(sites.niceId, updateData.niceId), + eq(sites.orgId, sites.orgId) + ) + ) .limit(1); if (existingSite.length > 0 && existingSite[0].siteId !== siteId) { From 83bd5957cd39dfea3b263ccd112c2b6fdd0b9887 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 8 Nov 2025 12:18:36 -0800 Subject: [PATCH 33/63] Dont allow editing a config managed domain Ref #1816 --- src/app/[orgId]/settings/domains/[domainId]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[orgId]/settings/domains/[domainId]/page.tsx b/src/app/[orgId]/settings/domains/[domainId]/page.tsx index ce744c41..39ad02db 100644 --- a/src/app/[orgId]/settings/domains/[domainId]/page.tsx +++ b/src/app/[orgId]/settings/domains/[domainId]/page.tsx @@ -73,7 +73,7 @@ export default async function DomainSettingsPage({ - {domain.type == "wildcard" && ( + {domain.type == "wildcard" && !domain.configManaged && ( Date: Sat, 8 Nov 2025 13:57:00 -0800 Subject: [PATCH 34/63] remove target unique check --- server/routers/target/createTarget.ts | 8 +-- server/routers/target/updateTarget.ts | 8 +-- .../resources/[niceId]/proxy/page.tsx | 52 +++---------------- .../settings/resources/create/page.tsx | 18 ------- src/components/DNSRecordsDataTable.tsx | 1 - 5 files changed, 12 insertions(+), 75 deletions(-) diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 1aef3251..b35d8d2a 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -163,12 +163,8 @@ export async function createTarget( ); if (existingTarget) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - `Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}` - ) - ); + // log a warning + logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`); } let newTarget: Target[] = []; diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index d332609d..6e9a8fc9 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -170,12 +170,8 @@ export async function updateTarget( ); if (foundTarget) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - `Target with IP ${targetData.ip}, port ${targetData.port}, and method ${targetData.method} already exists on the same site.` - ) - ); + // log a warning + logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${target.resourceId}`); } const { internalPort, targetIps } = await pickPort(site.siteId!, db); diff --git a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx index 158726fe..aa268250 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -501,25 +501,6 @@ export default function ReverseProxyTargets(props: { return; } - // Check if target with same IP, port and method already exists - const isDuplicate = targets.some( - (t) => - t.targetId !== target.targetId && - t.ip === target.ip && - t.port === target.port && - t.method === target.method && - t.siteId === target.siteId - ); - - if (isDuplicate) { - toast({ - variant: "destructive", - title: t("targetErrorDuplicate"), - description: t("targetErrorDuplicateDescription") - }); - return; - } - try { setTargetsLoading(true); @@ -585,24 +566,6 @@ export default function ReverseProxyTargets(props: { } async function addTarget(data: z.infer) { - // Check if target with same IP, port and method already exists - const isDuplicate = targets.some( - (target) => - target.ip === data.ip && - target.port === data.port && - target.method === data.method && - target.siteId === data.siteId - ); - - if (isDuplicate) { - toast({ - variant: "destructive", - title: t("targetErrorDuplicate"), - description: t("targetErrorDuplicateDescription") - }); - return; - } - // if (site && site.type == "wireguard" && site.subnet) { // // make sure that the target IP is within the site subnet // const targetIp = data.ip; @@ -899,7 +862,7 @@ export default function ReverseProxyTargets(props: { const healthCheckColumn: ColumnDef = { accessorKey: "healthCheck", - header: t("healthCheck"), + header: () => ({t("healthCheck")}), cell: ({ row }) => { const status = row.original.hcHealth || "unknown"; const isEnabled = row.original.hcEnabled; @@ -971,7 +934,7 @@ export default function ReverseProxyTargets(props: { const matchPathColumn: ColumnDef = { accessorKey: "path", - header: t("matchPath"), + header: () => ({t("matchPath")}), cell: ({ row }) => { const hasPathMatch = !!( row.original.path || row.original.pathMatchType @@ -1033,7 +996,7 @@ export default function ReverseProxyTargets(props: { const addressColumn: ColumnDef = { accessorKey: "address", - header: t("address"), + header: () => ({t("address")}), cell: ({ row }) => { const selectedSite = sites.find( (site) => site.siteId === row.original.siteId @@ -1052,7 +1015,7 @@ export default function ReverseProxyTargets(props: { return (
-
+
{selectedSite && selectedSite.type === "newt" && (() => { @@ -1247,7 +1210,7 @@ export default function ReverseProxyTargets(props: { const rewritePathColumn: ColumnDef = { accessorKey: "rewritePath", - header: t("rewritePath"), + header: () => ({t("rewritePath")}), cell: ({ row }) => { const hasRewritePath = !!( row.original.rewritePath || row.original.rewritePathType @@ -1317,7 +1280,7 @@ export default function ReverseProxyTargets(props: { const enabledColumn: ColumnDef = { accessorKey: "enabled", - header: t("enabled"), + header: () => ({t("enabled")}), cell: ({ row }) => (
= { id: "actions", + header: () => ({t("actions")}), cell: ({ row }) => ( -
+
+ +
); } From 564b290244a2ec7dbfb494badeae9634cfd8a00d Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 8 Nov 2025 14:24:28 -0800 Subject: [PATCH 37/63] Fix #1830 --- messages/en-US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index b25c7d08..178a9bb9 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -179,7 +179,7 @@ "baseDomain": "Base Domain", "subdomnainDescription": "The subdomain where your resource will be accessible.", "resourceRawSettings": "TCP/UDP Settings", - "resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP. You map the resource to a port on the host Pangolin server, so you can access the resource from :. (https://docs.pangolin.net/manage/resources/tcp-udp-resources)", + "resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP. You map the resource to a port on the host Pangolin server, so you can access the resource from server-public-ip:mapped-port.", "protocol": "Protocol", "protocolSelect": "Select a protocol", "resourcePortNumber": "Port Number", @@ -2096,4 +2096,4 @@ "enableSelected": "Enable Selected", "disableSelected": "Disable Selected", "checkSelectedStatus": "Check Status of Selected" -} +} \ No newline at end of file From a717ca2675bb2e47e9dd3c0ca13edd949b6af450 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 8 Nov 2025 15:42:46 -0800 Subject: [PATCH 38/63] Only uppercase the value if its a country Fixes #1813 --- server/lib/blueprints/proxyResources.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index 323e6a6a..d85befed 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -30,6 +30,7 @@ import { pickPort } from "@server/routers/target/helpers"; import { resourcePassword } from "@server/db"; import { hashPassword } from "@server/auth/password"; import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators"; +import { get } from "http"; export type ProxyResourcesResults = { proxyResource: Resource; @@ -544,7 +545,7 @@ export async function updateProxyResources( if ( existingRule.action !== getRuleAction(rule.action) || existingRule.match !== rule.match.toUpperCase() || - existingRule.value !== rule.value.toUpperCase() + existingRule.value !== getRuleValue(rule.match.toUpperCase(), rule.value) ) { validateRule(rule); await trx @@ -552,7 +553,7 @@ export async function updateProxyResources( .set({ action: getRuleAction(rule.action), match: rule.match.toUpperCase(), - value: rule.value.toUpperCase() + value: getRuleValue(rule.match.toUpperCase(), rule.value), }) .where( eq(resourceRules.ruleId, existingRule.ruleId) @@ -564,7 +565,7 @@ export async function updateProxyResources( resourceId: existingResource.resourceId, action: getRuleAction(rule.action), match: rule.match.toUpperCase(), - value: rule.value.toUpperCase(), + value: getRuleValue(rule.match.toUpperCase(), rule.value), priority: index + 1 // start priorities at 1 }); } @@ -722,7 +723,7 @@ export async function updateProxyResources( resourceId: newResource.resourceId, action: getRuleAction(rule.action), match: rule.match.toUpperCase(), - value: rule.value.toUpperCase(), + value: getRuleValue(rule.match.toUpperCase(), rule.value), priority: index + 1 // start priorities at 1 }); } @@ -752,6 +753,14 @@ function getRuleAction(input: string) { return action; } +function getRuleValue(match: string, value: string) { + // if the match is a country, uppercase the value + if (match == "COUNTRY") { + return value.toUpperCase(); + } + return value; +} + function validateRule(rule: any) { if (rule.match === "cidr") { if (!isValidCIDR(rule.value)) { From da15e5e77b02147825bab996eb864b2371e2e23b Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 8 Nov 2025 16:13:42 -0800 Subject: [PATCH 39/63] Remove software-properties-common Fixes #1828 --- install/containers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/containers.go b/install/containers.go index cea3a6ef..9993e117 100644 --- a/install/containers.go +++ b/install/containers.go @@ -73,7 +73,7 @@ func installDocker() error { case strings.Contains(osRelease, "ID=ubuntu"): installCmd = exec.Command("bash", "-c", fmt.Sprintf(` apt-get update && - apt-get install -y apt-transport-https ca-certificates curl software-properties-common && + apt-get install -y apt-transport-https ca-certificates curl && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list && apt-get update && @@ -82,7 +82,7 @@ func installDocker() error { case strings.Contains(osRelease, "ID=debian"): installCmd = exec.Command("bash", "-c", fmt.Sprintf(` apt-get update && - apt-get install -y apt-transport-https ca-certificates curl software-properties-common && + apt-get install -y apt-transport-https ca-certificates curl && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list && apt-get update && From 265cab5b64e93194038488d6ab2f189e62248635 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:24 -0800 Subject: [PATCH 40/63] New translations en-us.json (French) --- messages/fr-FR.json | 79 +++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 1eba2b7d..c688b9a2 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -10,20 +10,20 @@ "setupErrorIdentifier": "L'ID de l'organisation est déjà pris. Veuillez en choisir un autre.", "componentsErrorNoMemberCreate": "Vous n'êtes actuellement membre d'aucune organisation. Créez une organisation pour commencer.", "componentsErrorNoMember": "Vous n'êtes actuellement membre d'aucune organisation.", - "welcome": "Bienvenue à Pangolin", + "welcome": "Bienvenue sur Pangolin !", "welcomeTo": "Bienvenue chez", "componentsCreateOrg": "Créer une organisation", "componentsMember": "Vous êtes membre de {count, plural, =0 {aucune organisation} one {une organisation} other {# organisations}}.", - "componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Suivez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.", - "dismiss": "Refuser", - "componentsLicenseViolation": "Violation de licence : Ce serveur utilise des sites {usedSites} qui dépassent la limite autorisée des sites {maxSites} . Suivez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.", + "componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Veuillez respecter les conditions de licence pour continuer à utiliser toutes les fonctionnalités.", + "dismiss": "Rejeter", + "componentsLicenseViolation": "Violation de licence : ce serveur utilise {usedSites} sites, ce qui dépasse la limite autorisée de {maxSites} sites. Respectez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.", "componentsSupporterMessage": "Merci de soutenir Pangolin en tant que {tier}!", - "inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder n'ait pas été acceptée ou n'est plus valide.", - "inviteErrorUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour cet utilisateur.", - "inviteLoginUser": "Assurez-vous que vous êtes bien connecté en tant qu'utilisateur correct.", - "inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour un utilisateur qui existe.", + "inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder n'ait pas été acceptée ou ne soit plus valide.", + "inviteErrorUser": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder ne soit pas pour cet utilisateur.", + "inviteLoginUser": "Veuillez vous assurer que vous êtes connecté avec le bon utilisateur.", + "inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder ne concerne pas un utilisateur existant.", "inviteCreateUser": "Veuillez d'abord créer un compte.", - "goHome": "Retour à la maison", + "goHome": "Retour à l'accueil", "inviteLogInOtherUser": "Se connecter en tant qu'utilisateur différent", "createAnAccount": "Créer un compte", "inviteNotAccepted": "Invitation non acceptée", @@ -39,12 +39,12 @@ "online": "En ligne", "offline": "Hors ligne", "site": "Site", - "dataIn": "Données dans", - "dataOut": "Données épuisées", + "dataIn": "Données entrantes", + "dataOut": "Données sortantes", "connectionType": "Type de connexion", "tunnelType": "Type de tunnel", "local": "Locale", - "edit": "Editer", + "edit": "Éditer", "siteConfirmDelete": "Confirmer la suppression du site", "siteDelete": "Supprimer le site", "siteMessageRemove": "Une fois supprimé, le site ne sera plus accessible. Toutes les cibles associées au site seront également supprimées.", @@ -63,11 +63,11 @@ "siteLearnNewt": "Apprenez à installer Newt sur votre système", "siteSeeConfigOnce": "Vous ne pourrez voir la configuration qu'une seule fois.", "siteLoadWGConfig": "Chargement de la configuration WireGuard...", - "siteDocker": "Développer les détails du déploiement Docker", + "siteDocker": "Développer pour obtenir plus de détails sur le déploiement Docker", "toggle": "Activer/désactiver", "dockerCompose": "Composition Docker", "dockerRun": "Exécution Docker", - "siteLearnLocal": "Les sites locaux ne tunnel, en savoir plus", + "siteLearnLocal": "Les sites locaux ne font pas de tunnel, en savoir plus", "siteConfirmCopy": "J'ai copié la configuration", "searchSitesProgress": "Rechercher des sites...", "siteAdd": "Ajouter un site", @@ -78,7 +78,7 @@ "operatingSystem": "Système d'exploitation", "commands": "Commandes", "recommended": "Recommandé", - "siteNewtDescription": "Pour une meilleure expérience d'utilisateur, utilisez Newt. Il utilise WireGuard sous le capot et vous permet d'adresser vos ressources privées par leur adresse LAN sur votre réseau privé à partir du tableau de bord Pangolin.", + "siteNewtDescription": "Pour une meilleure expérience d'utilisateur, utilisez Newt. Il utilise WireGuard sous le capot et vous permet de vous connecter à vos ressources privées par leur adresse LAN sur votre réseau privé à partir du tableau de bord Pangolin.", "siteRunsInDocker": "Exécute dans Docker", "siteRunsInShell": "Exécute en shell sur macOS, Linux et Windows", "siteErrorDelete": "Erreur lors de la suppression du site", @@ -93,7 +93,7 @@ "siteNewtTunnelDescription": "La façon la plus simple de créer un point d'entrée dans votre réseau. Pas de configuration supplémentaire.", "siteWg": "WireGuard basique", "siteWgDescription": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise.", - "siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES", + "siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES.", "siteLocalDescription": "Ressources locales seulement. Pas de tunneling.", "siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. Disponible uniquement sur les nœuds distants.", "siteSeeAll": "Voir tous les sites", @@ -130,9 +130,9 @@ "shareTitleOptional": "Titre (facultatif)", "expireIn": "Expire dans", "neverExpire": "N'expire jamais", - "shareExpireDescription": "Le temps d'expiration est combien de temps le lien sera utilisable et fournira un accès à la ressource. Après cette période, le lien ne fonctionnera plus et les utilisateurs qui ont utilisé ce lien perdront l'accès à la ressource.", - "shareSeeOnce": "Vous ne pourrez voir ce lien. Assurez-vous de le copier.", - "shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.", + "shareExpireDescription": "La durée d'expiration correspond à la période pendant laquelle le lien sera utilisable et permettra d'accéder à la ressource. Passé ce délai, le lien ne fonctionnera plus et les utilisateurs qui l'ont utilisé perdront l'accès à la ressource.", + "shareSeeOnce": "Vous ne pourrez voir ce lien qu'une seule fois. Assurez-vous de le copier.", + "shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec précaution.", "shareTokenUsage": "Voir Utilisation du jeton d'accès", "createLink": "Créer un lien", "resourcesNotFound": "Aucune ressource trouvée", @@ -155,9 +155,9 @@ "resourceMessageRemove": "Une fois supprimée, la ressource ne sera plus accessible. Toutes les cibles associées à la ressource seront également supprimées.", "resourceQuestionRemove": "Êtes-vous sûr de vouloir supprimer la ressource de l'organisation ?", "resourceHTTP": "Ressource HTTPS", - "resourceHTTPDescription": "Requêtes de proxy à votre application via HTTPS en utilisant un sous-domaine ou un domaine de base.", + "resourceHTTPDescription": "Requêtes de proxy vers votre application via HTTPS en utilisant un sous-domaine ou un domaine de base.", "resourceRaw": "Ressource TCP/UDP brute", - "resourceRawDescription": "Demandes de proxy à votre application via TCP/UDP en utilisant un numéro de port.", + "resourceRawDescription": "Demandes de proxy vers votre application via TCP/UDP en utilisant un numéro de port.", "resourceCreate": "Créer une ressource", "resourceCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle ressource", "resourceSeeAll": "Voir toutes les ressources", @@ -179,7 +179,7 @@ "baseDomain": "Domaine de base", "subdomnainDescription": "Le sous-domaine où votre ressource sera accessible.", "resourceRawSettings": "Paramètres TCP/UDP", - "resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP", + "resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP. Vous mappez la ressource à un port sur le serveur Pangolin, de sorte que vous puissiez accéder à la ressource depuis server-public-ip:mapped-port.", "protocol": "Protocole", "protocolSelect": "Sélectionner un protocole", "resourcePortNumber": "Numéro de port", @@ -206,7 +206,7 @@ "resourceSetting": "Réglages {resourceName}", "alwaysAllow": "Toujours autoriser", "alwaysDeny": "Toujours refuser", - "passToAuth": "Paser à l'authentification", + "passToAuth": "Passer à l'authentification", "orgSettingsDescription": "Configurer les paramètres généraux de votre organisation", "orgGeneralSettings": "Paramètres de l'organisation", "orgGeneralSettingsDescription": "Gérer les détails et la configuration de votre organisation", @@ -342,7 +342,7 @@ "licenseTitleDescription": "Voir et gérer les clés de licence dans le système", "licenseHost": "Licence Hôte", "licenseHostDescription": "Gérer la clé de licence principale de l'hôte.", - "licensedNot": "Non licencié", + "licensedNot": "Pas de licence", "hostId": "ID de l'hôte", "licenseReckeckAll": "Revérifier toutes les clés", "licenseSiteUsage": "Utilisation des sites", @@ -350,7 +350,7 @@ "licenseNoSiteLimit": "Il n'y a pas de limite sur le nombre de sites utilisant un hôte non autorisé.", "licensePurchase": "Acheter une licence", "licensePurchaseSites": "Acheter des sites supplémentaires", - "licenseSitesUsedMax": "{usedSites} des sites {maxSites} utilisés", + "licenseSitesUsedMax": "{usedSites} des {maxSites} sites utilisés", "licenseSitesUsed": "{count, plural, =0 {# sites} one {# site} other {# sites}} dans le système.", "licensePurchaseDescription": "Choisissez le nombre de sites que vous voulez {selectedMode, select, license {achetez une licence. Vous pouvez toujours ajouter plus de sites plus tard.} other {ajouter à votre licence existante.}}", "licenseFee": "Frais de licence", @@ -371,7 +371,7 @@ "inviteQuestionRemove": "Êtes-vous sûr de vouloir supprimer l'invitation?", "inviteMessageRemove": "Une fois supprimée, cette invitation ne sera plus valide. Vous pourrez toujours réinviter l'utilisateur plus tard.", "inviteMessageConfirm": "Pour confirmer, veuillez saisir l'adresse e-mail de l'invitation ci-dessous.", - "inviteQuestionRegenerate": "Êtes-vous sûr de vouloir régénérer l'invitation {email}? Cela révoquera l'invitation précédente.", + "inviteQuestionRegenerate": "Êtes-vous sûr de vouloir régénérer l'invitation pour {email}? Cela révoquera l'invitation précédente.", "inviteRemoveConfirm": "Confirmer la suppression de l'invitation", "inviteRegenerated": "Invitation régénérée", "inviteSent": "Une nouvelle invitation a été envoyée à {email}.", @@ -465,7 +465,7 @@ "proxyErrorTls": "Nom de serveur TLS invalide. Utilisez le format de nom de domaine, ou laissez vide pour supprimer le nom de serveur TLS.", "proxyEnableSSL": "Activer SSL", "proxyEnableSSLDescription": "Activez le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers vos cibles.", - "target": "Target", + "target": "Cible", "configureTarget": "Configurer les cibles", "targetErrorFetch": "Échec de la récupération des cibles", "targetErrorFetchDescription": "Une erreur s'est produite lors de la récupération des cibles", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domaines", "sidebarBluePrints": "Plans", "blueprints": "Plans", - "blueprintsDescription": "Les plans sont des configurations YAML déclaratives qui définissent vos ressources et leurs paramètres", + "blueprintsDescription": "Appliquer les configurations déclaratives et afficher les exécutions précédentes", "blueprintAdd": "Ajouter un Plan", "blueprintGoBack": "Voir tous les plans", "blueprintCreate": "Créer un Plan", "blueprintCreateDescription2": "Suivez les étapes ci-dessous pour créer et appliquer un nouveau plan", "blueprintDetails": "Détails du Plan", - "blueprintDetailsDescription": "Voir les détails de l'exécution des plans", + "blueprintDetailsDescription": "Voir le résultat du plan appliqué et les erreurs qui se sont produites", "blueprintInfo": "Informations sur le Plan", "message": "Message", "blueprintContentsDescription": "Définissez le contenu YAML décrivant votre infrastructure", @@ -1181,7 +1181,7 @@ "appliedAt": "Appliqué à", "source": "Source", "contents": "Contenus", - "parsedContents": "Contenu analysé", + "parsedContents": "Contenu analysé (lecture seule)", "enableDockerSocket": "Activer le Plan Docker", "enableDockerSocketDescription": "Activer le ramassage d'étiquettes de socket Docker pour les étiquettes de plan. Le chemin de socket doit être fourni à Newt.", "enableDockerSocketLink": "En savoir plus", @@ -2080,5 +2080,20 @@ "supportSending": "Envoi...", "supportSend": "Envoyer", "supportMessageSent": "Message envoyé !", - "supportWillContact": "Nous vous contacterons sous peu!" -} + "supportWillContact": "Nous vous contacterons sous peu!", + "selectLogRetention": "Sélectionner la durée de rétention du journal", + "showColumns": "Afficher les colonnes", + "hideColumns": "Cacher les colonnes", + "columnVisibility": "Visibilité des colonnes", + "toggleColumn": "Activer/désactiver la colonne {columnName}", + "allColumns": "Toutes les colonnes", + "defaultColumns": "Colonnes par défaut", + "customizeView": "Personnaliser la vue", + "viewOptions": "Voir les options", + "selectAll": "Tout sélectionner", + "selectNone": "Ne rien sélectionner", + "selectedResources": "Ressources sélectionnées", + "enableSelected": "Activer la sélection", + "disableSelected": "Désactiver la sélection", + "checkSelectedStatus": "Vérifier le statut de la sélection" +} \ No newline at end of file From a51c21cdd2d0f73aabd27a48b0794e6495f0545d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:26 -0800 Subject: [PATCH 41/63] New translations en-us.json (Spanish) --- messages/es-ES.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index c8ef3e98..e8409c25 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -131,7 +131,7 @@ "expireIn": "Caduca en", "neverExpire": "Nunca expirar", "shareExpireDescription": "El tiempo de caducidad es cuánto tiempo el enlace será utilizable y proporcionará acceso al recurso. Después de este tiempo, el enlace ya no funcionará, y los usuarios que usaron este enlace perderán el acceso al recurso.", - "shareSeeOnce": "Sólo podrá ver este enlace una vez. Asegúrese de copiarlo.", + "shareSeeOnce": "Sólo podrás ver este enlace una vez. Asegúrate de copiarlo.", "shareAccessHint": "Cualquiera con este enlace puede acceder al recurso. Compártelo con cuidado.", "shareTokenUsage": "Ver Uso de Token de Acceso", "createLink": "Crear enlace", @@ -179,7 +179,7 @@ "baseDomain": "Dominio base", "subdomnainDescription": "El subdominio al que su recurso será accesible.", "resourceRawSettings": "Configuración TCP/UDP", - "resourceRawSettingsDescription": "Configurar cómo se accederá a su recurso a través de TCP/UDP", + "resourceRawSettingsDescription": "Configure cómo se accederá a su recurso a través de TCP/UDP. Mapeas el recurso a un puerto en el servidor Pangolin host, así puedes acceder al recurso desde el servidor-public-ip:mapped-port.", "protocol": "Protocolo", "protocolSelect": "Seleccionar un protocolo", "resourcePortNumber": "Número de puerto", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Dominios", "sidebarBluePrints": "Planos", "blueprints": "Planos", - "blueprintsDescription": "Los planos son configuraciones YAML declarativas que definen sus recursos y sus configuraciones", + "blueprintsDescription": "Aplicar configuraciones declarativas y ver ejecuciones anteriores", "blueprintAdd": "Añadir plano", "blueprintGoBack": "Ver todos los Planos", "blueprintCreate": "Crear Plano", "blueprintCreateDescription2": "Siga los siguientes pasos para crear y aplicar un nuevo plano", "blueprintDetails": "Detalles del plano", - "blueprintDetailsDescription": "Ver los detalles de la ejecución del plano", + "blueprintDetailsDescription": "Ver el resultado del plano aplicado y cualquier error que haya ocurrido", "blueprintInfo": "Información del plano", "message": "Mensaje", "blueprintContentsDescription": "Defina el contenido YAML describiendo su infraestructura", @@ -1181,7 +1181,7 @@ "appliedAt": "Aplicado en", "source": "Fuente", "contents": "Contenido", - "parsedContents": "Contenido analizado", + "parsedContents": "Contenido analizado (Sólo lectura)", "enableDockerSocket": "Habilitar Plano Docker", "enableDockerSocketDescription": "Activar el raspado de etiquetas de Socket Docker para etiquetas de planos. La ruta del Socket debe proporcionarse a Newt.", "enableDockerSocketLink": "Saber más", @@ -2080,5 +2080,20 @@ "supportSending": "Enviando...", "supportSend": "Enviar", "supportMessageSent": "¡Mensaje enviado!", - "supportWillContact": "¡Estaremos en contacto en breve!" -} + "supportWillContact": "¡Estaremos en contacto en breve!", + "selectLogRetention": "Seleccionar retención de registro", + "showColumns": "Mostrar columnas", + "hideColumns": "Ocultar columnas", + "columnVisibility": "Visibilidad de la columna", + "toggleColumn": "Cambiar columna {columnName}", + "allColumns": "Todas las columnas", + "defaultColumns": "Columnas por defecto", + "customizeView": "Personalizar vista", + "viewOptions": "Ver opciones", + "selectAll": "Seleccionar todo", + "selectNone": "No seleccionar", + "selectedResources": "Recursos seleccionados", + "enableSelected": "Habilitar seleccionados", + "disableSelected": "Desactivar Seleccionado", + "checkSelectedStatus": "Comprobar el estado de selección" +} \ No newline at end of file From d9991a18e2f41e0b031ae6c801a3524ba5062941 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:27 -0800 Subject: [PATCH 42/63] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index aea8bf5a..ff6a1ad4 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -131,7 +131,7 @@ "expireIn": "Изтече", "neverExpire": "Никога не изтича", "shareExpireDescription": "Времето на изтичане е колко дълго връзката ще бъде използваема и ще предоставя достъп до ресурса. След това време, връзката няма да работи и потребителите, които са я използвали, ще загубят достъп до ресурса.", - "shareSeeOnce": "Ще можете да видите тази връзка само веднъж. Уверете се да я копирате.", + "shareSeeOnce": "Ще можете да видите този линк само веднъж. Уверете се, че го копирате.", "shareAccessHint": "Всеки с тази връзка може да има достъп до ресурса. Споделяйте я с внимание.", "shareTokenUsage": "Вижте използването на токена за достъп", "createLink": "Създаване на връзка", @@ -179,7 +179,7 @@ "baseDomain": "Базов домейн", "subdomnainDescription": "Субдомейнът, в който ще бъде достъпен вашият ресурс.", "resourceRawSettings": "TCP/UDP настройки", - "resourceRawSettingsDescription": "Конфигурирайте как вашият ресурс ще бъде достъпен през TCP/UDP", + "resourceRawSettingsDescription": "Настройте как ресурсът ви ще бъде достъпен през TCP/UDP. Свързвате ресурса към порт на хост сървъра Pangolin, за да го достъпвате от server-public-ip:mapped-port.", "protocol": "Протокол", "protocolSelect": "Изберете протокол", "resourcePortNumber": "Номер на порт", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Домейни", "sidebarBluePrints": "Чертежи", "blueprints": "Чертежи", - "blueprintsDescription": "Чертежите са декларативни YAML конфигурации, които определят вашите ресурси и техните настройки", + "blueprintsDescription": "Прилагайте декларативни конфигурации и преглеждайте предишни изпълнения", "blueprintAdd": "Добави Чертеж", "blueprintGoBack": "Виж всички Чертежи", "blueprintCreate": "Създай Чертеж", "blueprintCreateDescription2": "Следвайте стъпките по-долу, за да създадете и приложите нов чертеж", - "blueprintDetails": "Детайли за Чертежа", - "blueprintDetailsDescription": "Вижте детайлите за изпълнението на чертежа", + "blueprintDetails": "Детайли на чертежа", + "blueprintDetailsDescription": "Вижте резултата от приложените чертежи и всички възникнали грешки", "blueprintInfo": "Информация за Чертежа", "message": "Съобщение", "blueprintContentsDescription": "Дефинирайте YAML съдържанието, описващо вашата инфраструктура", @@ -1181,7 +1181,7 @@ "appliedAt": "Приложено във", "source": "Източник", "contents": "Съдържание", - "parsedContents": "Анализирано съдържание", + "parsedContents": "Парсирано съдържание (само за четене)", "enableDockerSocket": "Активиране на Docker Чернова", "enableDockerSocketDescription": "Активиране на Docker Socket маркировка за изтегляне на етикети на чернова. Пътят на гнездото трябва да бъде предоставен на Newt.", "enableDockerSocketLink": "Научете повече", @@ -2080,5 +2080,20 @@ "supportSending": "Изпращане...", "supportSend": "Изпрати", "supportMessageSent": "Съобщението е изпратено!", - "supportWillContact": "Ще се свържем с вас скоро!" -} + "supportWillContact": "Ще се свържем с вас скоро!", + "selectLogRetention": "Изберете съхранение на логовете", + "showColumns": "Покажи колони", + "hideColumns": "Скрий колони", + "columnVisibility": "Видимост на колоните", + "toggleColumn": "Превключване на колоната {columnName}", + "allColumns": "Всички колони", + "defaultColumns": "По подразбиране колони", + "customizeView": "Персонализиране на изгледа", + "viewOptions": "Опции за изгледа", + "selectAll": "Избери всички", + "selectNone": "Избери нищо", + "selectedResources": "Избрани ресурси", + "enableSelected": "Разреши избраните", + "disableSelected": "Забрани избраните", + "checkSelectedStatus": "Проверете състоянието на избраните" +} \ No newline at end of file From d00f12967d26359852bc0c969bb992ecd4f71974 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:28 -0800 Subject: [PATCH 43/63] New translations en-us.json (Czech) --- messages/cs-CZ.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 4e0e1f87..37c7a16d 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -131,7 +131,7 @@ "expireIn": "Platnost vyprší za", "neverExpire": "Nikdy nevyprší", "shareExpireDescription": "Doba platnosti určuje, jak dlouho bude odkaz použitelný a bude poskytovat přístup ke zdroji. Po této době odkaz již nebude fungovat a uživatelé kteří tento odkaz používali ztratí přístup ke zdroji.", - "shareSeeOnce": "Tento odkaz uvidíte pouze jednou. Ujistěte se, že jste jej zkopírovali.", + "shareSeeOnce": "Tento odkaz uvidíte pouze jednou. Nezapomeňte jej zkopírovat.", "shareAccessHint": "Kdokoli s tímto odkazem může přistupovat ke zdroji. Sdílejte jej s rozvahou.", "shareTokenUsage": "Zobrazit využití přístupového tokenu", "createLink": "Vytvořit odkaz", @@ -179,7 +179,7 @@ "baseDomain": "Základní doména", "subdomnainDescription": "Subdoména, kde bude váš zdroj přístupný.", "resourceRawSettings": "Nastavení TCP/UDP", - "resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP", + "resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP. Mapováte zdroj na port na serveru Pangolin, takže můžete přistupovat ke zdroji ze serveru-veřejné ip:mapped-port.", "protocol": "Protokol", "protocolSelect": "Vybrat protokol", "resourcePortNumber": "Číslo portu", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domény", "sidebarBluePrints": "Plány", "blueprints": "Plány", - "blueprintsDescription": "Plány jsou deklarativní YAML konfigurace, které definují vaše zdroje a jejich nastavení", + "blueprintsDescription": "Použít deklarativní konfigurace a zobrazit předchozí běhy", "blueprintAdd": "Přidat plán", "blueprintGoBack": "Zobrazit všechny plány", "blueprintCreate": "Vytvořit plán", "blueprintCreateDescription2": "Postupujte podle níže uvedených kroků pro vytvoření a použití nového plánu", "blueprintDetails": "Podrobnosti plánu", - "blueprintDetailsDescription": "Podívejte se na detaily běhu plánu", + "blueprintDetailsDescription": "Podívejte se na výsledek použitého plánu a případné chyby, které se vyskytly", "blueprintInfo": "Informace o plánu", "message": "Zpráva", "blueprintContentsDescription": "Definujte obsah YAML popisující vaši infrastrukturu", @@ -1181,7 +1181,7 @@ "appliedAt": "Použito v", "source": "Zdroj", "contents": "Obsah", - "parsedContents": "Parsovaný obsah", + "parsedContents": "Parsed content (Pouze pro čtení)", "enableDockerSocket": "Povolit Docker plán", "enableDockerSocketDescription": "Povolte seškrábání štítků na Docker Socket pro popisky plánů. Nová cesta musí být k dispozici.", "enableDockerSocketLink": "Zjistit více", @@ -2080,5 +2080,20 @@ "supportSending": "Odesílání...", "supportSend": "Poslat", "supportMessageSent": "Zpráva odeslána!", - "supportWillContact": "Brzy budeme v kontaktu!" -} + "supportWillContact": "Brzy budeme v kontaktu!", + "selectLogRetention": "Vyberte záznam", + "showColumns": "Zobrazit sloupce", + "hideColumns": "Skrýt sloupce", + "columnVisibility": "Viditelnost sloupců", + "toggleColumn": "Přepnout sloupec {columnName}", + "allColumns": "Všechny sloupce", + "defaultColumns": "Výchozí sloupce", + "customizeView": "Přizpůsobit zobrazení", + "viewOptions": "Možnosti zobrazení", + "selectAll": "Vybrat vše", + "selectNone": "Nevybrat žádný", + "selectedResources": "Vybrané zdroje", + "enableSelected": "Povolit vybrané", + "disableSelected": "Zakázat vybrané", + "checkSelectedStatus": "Zkontrolovat stav vybraného" +} \ No newline at end of file From 7bdf05bdf58ce23e3258304d2c1c888bd600ac9f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:30 -0800 Subject: [PATCH 44/63] New translations en-us.json (German) --- messages/de-DE.json | 77 +++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 5a4935fe..001550d4 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -131,7 +131,7 @@ "expireIn": "Verfällt in", "neverExpire": "Nie ablaufen", "shareExpireDescription": "Ablaufzeit ist, wie lange der Link verwendet werden kann und bietet Zugriff auf die Ressource. Nach dieser Zeit wird der Link nicht mehr funktionieren und Benutzer, die diesen Link benutzt haben, verlieren den Zugriff auf die Ressource.", - "shareSeeOnce": "Sie können diesen Link nur ein einziges Mal sehen. Bitte kopieren Sie ihn.", + "shareSeeOnce": "Sie können diesen Link nur einmal sehen. Bitte kopieren Sie ihn.", "shareAccessHint": "Jeder mit diesem Link kann auf die Ressource zugreifen. Teilen Sie sie mit Vorsicht.", "shareTokenUsage": "Zugriffstoken-Nutzung anzeigen", "createLink": "Link erstellen", @@ -179,7 +179,7 @@ "baseDomain": "Basis-Domain", "subdomnainDescription": "Die Subdomain, auf der Ihre Ressource erreichbar sein soll.", "resourceRawSettings": "TCP/UDP Einstellungen", - "resourceRawSettingsDescription": "Konfigurieren Sie den Zugriff auf Ihre Ressource über TCP/UDP", + "resourceRawSettingsDescription": "Legen Sie fest, wie auf Ihre Ressource über TCP/UDP zugegriffen wird. Sie ordnen die Ressource einem Port auf dem Pangolin-Server zu, so dass Sie auf die Ressource von server-public-ip:mapped-port zugreifen können.", "protocol": "Protokoll", "protocolSelect": "Wählen Sie ein Protokoll", "resourcePortNumber": "Portnummer", @@ -1021,7 +1021,7 @@ "actionDeleteSite": "Standort löschen", "actionGetSite": "Standort abrufen", "actionListSites": "Standorte auflisten", - "actionApplyBlueprint": "Blueprint anwenden", + "actionApplyBlueprint": "Blaupause anwenden", "setupToken": "Setup-Token", "setupTokenDescription": "Geben Sie das Setup-Token von der Serverkonsole ein.", "setupTokenRequired": "Setup-Token ist erforderlich", @@ -1080,11 +1080,11 @@ "actionDeleteIdpOrg": "IDP-Organisationsrichtlinie löschen", "actionListIdpOrgs": "IDP-Organisationen auflisten", "actionUpdateIdpOrg": "IDP-Organisation aktualisieren", - "actionCreateClient": "Client erstellen", - "actionDeleteClient": "Client löschen", - "actionUpdateClient": "Client aktualisieren", - "actionListClients": "Clientsn auflisten", - "actionGetClient": "Client abrufen", + "actionCreateClient": "Kunde erstellen", + "actionDeleteClient": "Kunde löschen", + "actionUpdateClient": "Kunde aktualisieren", + "actionListClients": "Kunden auflisten", + "actionGetClient": "Kunde holen", "actionCreateSiteResource": "Site-Ressource erstellen", "actionDeleteSiteResource": "Site-Ressource löschen", "actionGetSiteResource": "Site-Ressource abrufen", @@ -1161,29 +1161,29 @@ "sidebarAllUsers": "Alle Benutzer", "sidebarIdentityProviders": "Identitätsanbieter", "sidebarLicense": "Lizenz", - "sidebarClients": "Clients", + "sidebarClients": "Kunden", "sidebarDomains": "Domänen", - "sidebarBluePrints": "Blueprints", - "blueprints": "Blueprints", - "blueprintsDescription": "Blueprints sind deklarative YAML-Konfigurationen, die deine Ressourcen und deren Einstellungen definieren", - "blueprintAdd": "Blueprint hinzufügen", - "blueprintGoBack": "Alle Blueprints ansehen", - "blueprintCreate": "Blueprint erstellen", - "blueprintCreateDescription2": "Folge den Schritten unten, um einen neuen Blueprint zu erstellen und anzuwenden", - "blueprintDetails": "Blueprintdetails", - "blueprintDetailsDescription": "Siehe die Blueprint Details", - "blueprintInfo": "Blueprint Information", + "sidebarBluePrints": "Baupläne", + "blueprints": "Baupläne", + "blueprintsDescription": "Deklarative Konfigurationen anwenden und vorherige Abläufe anzeigen", + "blueprintAdd": "Blaupause hinzufügen", + "blueprintGoBack": "Alle Blaupausen ansehen", + "blueprintCreate": "Blaupause erstellen", + "blueprintCreateDescription2": "Folge den Schritten unten, um eine neue Blaupause zu erstellen und anzuwenden", + "blueprintDetails": "Blaupausendetails", + "blueprintDetailsDescription": "Siehe das Ergebnis der angewendeten Blaupause und alle aufgetretenen Fehler", + "blueprintInfo": "Blaupauseninformation", "message": "Nachricht", "blueprintContentsDescription": "Definieren Sie den YAML-Inhalt, der Ihre Infrastruktur beschreibt", - "blueprintErrorCreateDescription": "Fehler beim Anwenden des Blueprints", - "blueprintErrorCreate": "Fehler beim Erstellen des Blueprints", - "searchBlueprintProgress": "Blueprints suchen...", + "blueprintErrorCreateDescription": "Fehler beim Anwenden der Blaupause", + "blueprintErrorCreate": "Fehler beim Erstellen der Blaupause", + "searchBlueprintProgress": "Blaupausen suchen...", "appliedAt": "Angewandt am", "source": "Quelle", "contents": "Inhalt", - "parsedContents": "Analysierte Inhalte", - "enableDockerSocket": "Docker Blueprints aktivieren", - "enableDockerSocketDescription": "Aktiviere Docker-Socket-Label-Scraping für Blueprintbeschriftungen. Der Socket-Pfad muss neu angegeben werden.", + "parsedContents": "Analysierte Inhalte (Nur lesen)", + "enableDockerSocket": "Docker Blaupause aktivieren", + "enableDockerSocketDescription": "Aktiviere Docker-Socket-Label-Scraping für Blaupausenbeschriftungen. Der Socket-Pfad muss neu angegeben werden.", "enableDockerSocketLink": "Mehr erfahren", "viewDockerContainers": "Docker Container anzeigen", "containersIn": "Container in {siteName}", @@ -1423,14 +1423,14 @@ }, "siteRequired": "Standort ist erforderlich.", "olmTunnel": "Olm-Tunnel", - "olmTunnelDescription": "Nutzen Sie Olm für die Clientverbindung", + "olmTunnelDescription": "Nutzen Sie Olm für die Kundenverbindung", "errorCreatingClient": "Fehler beim Erstellen des Clients", "clientDefaultsNotFound": "Standardeinstellungen des Clients nicht gefunden", "createClient": "Client erstellen", "createClientDescription": "Erstellen Sie einen neuen Client für die Verbindung zu Ihren Standorten.", "seeAllClients": "Alle Clients anzeigen", - "clientInformation": "Clientninformationen", - "clientNamePlaceholder": "Clientname", + "clientInformation": "Kundeninformationen", + "clientNamePlaceholder": "Kundenname", "address": "Adresse", "subnetPlaceholder": "Subnetz", "addressDescription": "Die Adresse, die dieser Client für die Verbindung verwenden wird.", @@ -2049,7 +2049,7 @@ "orgOrDomainIdMissing": "Organisation oder Domänen-ID fehlt", "loadingDNSRecords": "Lade DNS-Einträge...", "olmUpdateAvailableInfo": "Eine aktualisierte Version von Olm ist verfügbar. Bitte aktualisieren Sie auf die neueste Version für die beste Erfahrung.", - "client": "Client", + "client": "Kunde", "proxyProtocol": "Proxy-Protokoll-Einstellungen", "proxyProtocolDescription": "Konfigurieren Sie das Proxy-Protokoll, um die IP-Adressen des Clients für TCP/UDP-Dienste zu erhalten.", "enableProxyProtocol": "Proxy-Protokoll aktivieren", @@ -2080,5 +2080,20 @@ "supportSending": "Senden...", "supportSend": "Senden", "supportMessageSent": "Nachricht gesendet!", - "supportWillContact": "Wir werden in Kürze kontaktieren!" -} + "supportWillContact": "Wir werden in Kürze kontaktieren!", + "selectLogRetention": "Log-Speicherung auswählen", + "showColumns": "Spalten anzeigen", + "hideColumns": "Spalten ausblenden", + "columnVisibility": "Spaltensichtbarkeit", + "toggleColumn": "{columnName} Spalte umschalten", + "allColumns": "Alle Spalten", + "defaultColumns": "Standardspalten", + "customizeView": "Ansicht anpassen", + "viewOptions": "Optionen anzeigen", + "selectAll": "Alle auswählen", + "selectNone": "Nichts auswählen", + "selectedResources": "Ausgewählte Ressourcen", + "enableSelected": "Ausgewählte aktivieren", + "disableSelected": "Ausgewählte deaktivieren", + "checkSelectedStatus": "Status der Auswahl überprüfen" +} \ No newline at end of file From 263fd80c18d960bf58ddc0c4793e388b1d9586a6 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:31 -0800 Subject: [PATCH 45/63] New translations en-us.json (Italian) --- messages/it-IT.json | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index c76e6b5a..0e8c2d83 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -131,7 +131,7 @@ "expireIn": "Scadenza In", "neverExpire": "Mai scadere", "shareExpireDescription": "Il tempo di scadenza è per quanto tempo il link sarà utilizzabile e fornirà accesso alla risorsa. Dopo questo tempo, il link non funzionerà più e gli utenti che hanno utilizzato questo link perderanno l'accesso alla risorsa.", - "shareSeeOnce": "Potrai vedere solo questo linkonce. Assicurati di copiarlo.", + "shareSeeOnce": "Potrai vedere questo link solo una volta. Assicurati di copiarlo.", "shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.", "shareTokenUsage": "Vedi Utilizzo Token Di Accesso", "createLink": "Crea Collegamento", @@ -179,7 +179,7 @@ "baseDomain": "Dominio Base", "subdomnainDescription": "Il sottodominio in cui la tua risorsa sarà accessibile.", "resourceRawSettings": "Impostazioni TCP/UDP", - "resourceRawSettingsDescription": "Configura come accedere alla tua risorsa tramite TCP/UDP", + "resourceRawSettingsDescription": "Configura come sarà possibile accedere alla tua risorsa tramite TCP/UDP. Mappare la risorsa a una porta sul server host Pangolin, in modo da poter accedere alla risorsa dal server-public ip:mapped-port.", "protocol": "Protocollo", "protocolSelect": "Seleziona un protocollo", "resourcePortNumber": "Numero Porta", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domini", "sidebarBluePrints": "Progetti", "blueprints": "Progetti", - "blueprintsDescription": "I progetti sono configurazioni YAML dichiarative che definiscono le tue risorse e le loro impostazioni", + "blueprintsDescription": "Applica le configurazioni dichiarative e visualizza le partite precedenti", "blueprintAdd": "Aggiungi Progetto", "blueprintGoBack": "Vedi tutti i progetti", "blueprintCreate": "Crea Progetto", "blueprintCreateDescription2": "Segui i passaggi qui sotto per creare e applicare un nuovo progetto", - "blueprintDetails": "Dettagli progetto", - "blueprintDetailsDescription": "Vedi i dettagli dell'esecuzione del progetto", + "blueprintDetails": "Dettagli Progetto", + "blueprintDetailsDescription": "Vedere il risultato del progetto applicato e gli eventuali errori verificatisi", "blueprintInfo": "Informazioni Sul Progetto", "message": "Messaggio", "blueprintContentsDescription": "Definisci il contenuto di YAML che descrive la tua infrastruttura", @@ -1181,7 +1181,7 @@ "appliedAt": "Applicato Il", "source": "Fonte", "contents": "Contenuti", - "parsedContents": "Sommario Analizzato", + "parsedContents": "Sommario Analizzato (Solo Lettura)", "enableDockerSocket": "Abilita Progetto Docker", "enableDockerSocketDescription": "Abilita la raschiatura dell'etichetta Docker Socket per le etichette dei progetti. Il percorso del socket deve essere fornito a Newt.", "enableDockerSocketLink": "Scopri di più", @@ -2080,5 +2080,20 @@ "supportSending": "Invio...", "supportSend": "Invia", "supportMessageSent": "Messaggio Inviato!", - "supportWillContact": "Saremo in contatto a breve!" -} + "supportWillContact": "Saremo in contatto a breve!", + "selectLogRetention": "Seleziona ritenzione log", + "showColumns": "Mostra Colonne", + "hideColumns": "Nascondi Colonne", + "columnVisibility": "Visibilità Colonna", + "toggleColumn": "Attiva/disattiva colonna {columnName}", + "allColumns": "Tutte Le Colonne", + "defaultColumns": "Colonne Predefinite", + "customizeView": "Personalizza Vista", + "viewOptions": "Opzioni Visualizzazione", + "selectAll": "Seleziona Tutto", + "selectNone": "Seleziona Nessuno", + "selectedResources": "Risorse Selezionate", + "enableSelected": "Abilita Selezionati", + "disableSelected": "Disabilita Selezionati", + "checkSelectedStatus": "Controlla lo stato dei selezionati" +} \ No newline at end of file From 8a8c3575636da3277ac521113d4c421b7e7f7887 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:32 -0800 Subject: [PATCH 46/63] New translations en-us.json (Korean) --- messages/ko-KR.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index bf4b28fa..d892a5a2 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -179,7 +179,7 @@ "baseDomain": "기본 도메인", "subdomnainDescription": "리소스에 접근할 수 있는 하위 도메인입니다.", "resourceRawSettings": "TCP/UDP 설정", - "resourceRawSettingsDescription": "TCP/UDP를 통해 리소스에 접근하는 방법을 구성하세요.", + "resourceRawSettingsDescription": "리소스를 TCP/UDP를 통해 액세스하는 방법을 구성합니다. 리소스를 호스트 Pangolin 서버의 포트에 매핑하여 서버-public-ip:매핑된 포트에서 리소스에 액세스할 수 있습니다.", "protocol": "프로토콜", "protocolSelect": "프로토콜 선택", "resourcePortNumber": "포트 번호", @@ -1165,13 +1165,13 @@ "sidebarDomains": "도메인", "sidebarBluePrints": "청사진", "blueprints": "청사진", - "blueprintsDescription": "청사진은 리소스와 그 설정을 정의하는 선언적인 YAML 구성입니다", + "blueprintsDescription": "선언적 구성을 적용하고 이전 실행을 봅니다", "blueprintAdd": "청사진 추가", "blueprintGoBack": "모든 청사진 보기", "blueprintCreate": "청사진 생성", "blueprintCreateDescription2": "새 청사진을 생성하고 적용하려면 아래 단계를 따르십시오", - "blueprintDetails": "청사진 세부 사항", - "blueprintDetailsDescription": "청사진 실행 세부 정보 보기", + "blueprintDetails": "청사진 세부사항", + "blueprintDetailsDescription": "적용된 청사진의 결과와 발생한 오류를 확인합니다", "blueprintInfo": "청사진 정보", "message": "메시지", "blueprintContentsDescription": "인프라를 설명하는 YAML 콘텐츠를 정의하십시오", @@ -1181,7 +1181,7 @@ "appliedAt": "적용 시점", "source": "출처", "contents": "콘텐츠", - "parsedContents": "구문 분석된 콘텐츠", + "parsedContents": "구문 분석된 콘텐츠 (읽기 전용)", "enableDockerSocket": "Docker 청사진 활성화", "enableDockerSocketDescription": "블루프린트 레이블을 위한 Docker 소켓 레이블 수집을 활성화합니다. 소켓 경로는 Newt에 제공되어야 합니다.", "enableDockerSocketLink": "자세히 알아보기", @@ -2080,5 +2080,20 @@ "supportSending": "발송 중...", "supportSend": "보내기", "supportMessageSent": "메시지 전송 완료!", - "supportWillContact": "곧 연락드리겠습니다!" -} + "supportWillContact": "곧 연락드리겠습니다!", + "selectLogRetention": "로그 보존 선택", + "showColumns": "열 표시", + "hideColumns": "열 숨기기", + "columnVisibility": "열 가시성", + "toggleColumn": "{columnName} 열 토글", + "allColumns": "모든 열", + "defaultColumns": "기본 열", + "customizeView": "보기 사용자 지정", + "viewOptions": "보기 옵션", + "selectAll": "모두 선택", + "selectNone": "선택하지 않음", + "selectedResources": "선택된 리소스", + "enableSelected": "선택된 항목 활성화", + "disableSelected": "선택된 항목 비활성화", + "checkSelectedStatus": "선택된 항목 상태 확인" +} \ No newline at end of file From ac5ee5c7ca92c45a4fb7bc3e61516a9aeb6d89c8 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:34 -0800 Subject: [PATCH 47/63] New translations en-us.json (Dutch) --- messages/nl-NL.json | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index db0c5414..0a6c2ae5 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -131,7 +131,7 @@ "expireIn": "Vervalt in", "neverExpire": "Nooit verlopen", "shareExpireDescription": "Vervaltijd is hoe lang de link bruikbaar is en geeft toegang tot de bron. Na deze tijd zal de link niet meer werken en zullen gebruikers die deze link hebben gebruikt de toegang tot de pagina verliezen.", - "shareSeeOnce": "Je kunt deze koppeling alleen zien. Zorg ervoor dat je het kopieert.", + "shareSeeOnce": "U kunt deze link slechts één keer zien. Zorg ervoor dat u deze kopieert.", "shareAccessHint": "Iedereen met deze link heeft toegang tot de bron. Deel deze met zorg.", "shareTokenUsage": "Zie Toegangstoken Gebruik", "createLink": "Koppeling aanmaken", @@ -179,7 +179,7 @@ "baseDomain": "Basis domein", "subdomnainDescription": "Het subdomein waar de bron toegankelijk is.", "resourceRawSettings": "TCP/UDP instellingen", - "resourceRawSettingsDescription": "Stel in hoe je bron wordt benaderd via TCP/UDP", + "resourceRawSettingsDescription": "Stel in hoe uw bron wordt benaderd via TCP/UDP. Je gooit de bron toe aan een poort op de host-Pangolin server, zodat je de bron kan bereiken vanaf server-public-ip:mapped-port.", "protocol": "Protocol", "protocolSelect": "Selecteer een protocol", "resourcePortNumber": "Nummer van poort", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domeinen", "sidebarBluePrints": "Blauwdrukken", "blueprints": "Blauwdrukken", - "blueprintsDescription": "Blauwdrukken zijn declaratieve YAML-configuraties die je bronnen en hun instellingen bepalen", + "blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.", "blueprintAdd": "Blauwdruk toevoegen", "blueprintGoBack": "Bekijk alle Blauwdrukken", "blueprintCreate": "Creëer blauwdruk", "blueprintCreateDescription2": "Volg de onderstaande stappen om een nieuwe blauwdruk te maken en toe te passen", - "blueprintDetails": "Blauwdruk details", - "blueprintDetailsDescription": "Bekijk de blauwdruk run details", + "blueprintDetails": "Blauwdruk Details", + "blueprintDetailsDescription": "Bekijk het resultaat van de toegepaste blauwdruk en eventuele fouten", "blueprintInfo": "Blauwdruk Informatie", "message": "bericht", "blueprintContentsDescription": "Definieer de YAML content die je infrastructuur beschrijft", @@ -1181,7 +1181,7 @@ "appliedAt": "Toegepast op", "source": "Bron", "contents": "Inhoud", - "parsedContents": "Geparseerde inhoud", + "parsedContents": "Geparseerde inhoud (alleen lezen)", "enableDockerSocket": "Schakel Docker Blauwdruk in", "enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.", "enableDockerSocketLink": "Meer informatie", @@ -2080,5 +2080,20 @@ "supportSending": "Verzenden...", "supportSend": "Verzenden", "supportMessageSent": "Bericht verzonden!", - "supportWillContact": "We nemen binnenkort contact met u op!" -} + "supportWillContact": "We nemen binnenkort contact met u op!", + "selectLogRetention": "Selecteer log retentie", + "showColumns": "Kolommen weergeven", + "hideColumns": "Kolommen verbergen", + "columnVisibility": "Zichtbaarheid kolommen", + "toggleColumn": "{columnName} kolom in-/uitschakelen", + "allColumns": "Alle kolommen", + "defaultColumns": "Standaard Kolommen", + "customizeView": "Weergave aanpassen", + "viewOptions": "Bekijk opties", + "selectAll": "Alles selecteren", + "selectNone": "Niets selecteren", + "selectedResources": "Geselecteerde bronnen", + "enableSelected": "Selectie inschakelen", + "disableSelected": "Selectie uitschakelen", + "checkSelectedStatus": "Controleer de status van de geselecteerde" +} \ No newline at end of file From 4a87cecf89e12790cf1ce0b448cb8367fe34dcc5 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:35 -0800 Subject: [PATCH 48/63] New translations en-us.json (Polish) --- messages/pl-PL.json | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index b137ab74..2acb3291 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -131,7 +131,7 @@ "expireIn": "Wygasa za", "neverExpire": "Nigdy nie wygasa", "shareExpireDescription": "Czas wygaśnięcia to jak długo link będzie mógł być użyty i zapewni dostęp do zasobu. Po tym czasie link nie będzie już działał, a użytkownicy, którzy użyli tego linku, utracą dostęp do zasobu.", - "shareSeeOnce": "Możesz zobaczyć tylko ten link. Upewnij się, że go skopiowało.", + "shareSeeOnce": "Możesz zobaczyć ten link tylko raz. Pamiętaj, aby go skopiować.", "shareAccessHint": "Każdy z tym linkiem może uzyskać dostęp do zasobu. Podziel się nim ostrożnie.", "shareTokenUsage": "Zobacz użycie tokenu dostępu", "createLink": "Utwórz link", @@ -179,7 +179,7 @@ "baseDomain": "Bazowa domena", "subdomnainDescription": "Poddomena, w której twój zasób będzie dostępny.", "resourceRawSettings": "Ustawienia TCP/UDP", - "resourceRawSettingsDescription": "Skonfiguruj jak twój zasób będzie dostępny przez TCP/UDP", + "resourceRawSettingsDescription": "Skonfiguruj jak twój zasób będzie dostępny przez TCP/UDP. Zmapujesz zasób do portu na serwerze hosta Pangolin, dzięki czemu możesz uzyskać dostęp do zasobu z serwera-public ip:mapped-port.", "protocol": "Protokół", "protocolSelect": "Wybierz protokół", "resourcePortNumber": "Numer portu", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domeny", "sidebarBluePrints": "Schematy", "blueprints": "Schematy", - "blueprintsDescription": "Plany to deklaratywne konfiguracje YAML, które definiują twoje zasoby i ich ustawienia", + "blueprintsDescription": "Zastosuj konfiguracje deklaracyjne i wyświetl poprzednie operacje", "blueprintAdd": "Dodaj schemat", "blueprintGoBack": "Zobacz wszystkie schematy", "blueprintCreate": "Utwórz schemat", "blueprintCreateDescription2": "Wykonaj poniższe kroki, aby utworzyć i zastosować nowy schemat", - "blueprintDetails": "Szczegóły projektu", - "blueprintDetailsDescription": "Zobacz szczegóły uruchomienia schematu", + "blueprintDetails": "Szczegóły Projektu", + "blueprintDetailsDescription": "Zobacz wynik zastosowanego schematu i wszelkie błędy, które wystąpiły", "blueprintInfo": "Informacje o projekcie", "message": "Wiadomość", "blueprintContentsDescription": "Zdefiniuj zawartość YAML opisującą Twoją infrastrukturę", @@ -1181,7 +1181,7 @@ "appliedAt": "Zastosowano", "source": "Źródło", "contents": "Treść", - "parsedContents": "Przetworzona zawartość", + "parsedContents": "Przetworzona zawartość (tylko do odczytu)", "enableDockerSocket": "Włącz schemat dokera", "enableDockerSocketDescription": "Włącz etykietowanie kieszeni dokującej dla etykiet schematów. Ścieżka do gniazda musi być dostarczona do Newt.", "enableDockerSocketLink": "Dowiedz się więcej", @@ -2080,5 +2080,20 @@ "supportSending": "Wysyłanie...", "supportSend": "Wyślij", "supportMessageSent": "Wiadomość wysłana!", - "supportWillContact": "Wkrótce będziemy w kontakcie!" -} + "supportWillContact": "Wkrótce będziemy w kontakcie!", + "selectLogRetention": "Wybierz zatrzymanie dziennika", + "showColumns": "Pokaż kolumny", + "hideColumns": "Ukryj kolumny", + "columnVisibility": "Widoczność kolumn", + "toggleColumn": "Przełącz kolumnę {columnName}", + "allColumns": "Wszystkie kolumny", + "defaultColumns": "Kolumny domyślne", + "customizeView": "Dostosuj widok", + "viewOptions": "Opcje widoku", + "selectAll": "Zaznacz wszystko", + "selectNone": "Nie wybierz żadnego", + "selectedResources": "Wybrane Zasoby", + "enableSelected": "Włącz zaznaczone", + "disableSelected": "Wyłącz zaznaczone", + "checkSelectedStatus": "Sprawdź status zaznaczonych" +} \ No newline at end of file From 4fddaa8f112f3229e1b2877e6a1be0b9df2d03ad Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:36 -0800 Subject: [PATCH 49/63] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 983e3f12..cb0bc29b 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -179,7 +179,7 @@ "baseDomain": "Domínio Base", "subdomnainDescription": "O subdomínio onde seu recurso estará acessível.", "resourceRawSettings": "Configurações TCP/UDP", - "resourceRawSettingsDescription": "Configure como seu recurso será acessado sobre TCP/UDP", + "resourceRawSettingsDescription": "Configure como seu recurso será acessado sobre TCP/UDP. Você mapeia o recurso para uma porta no servidor Pangolin do hospedeiro, para que você possa acessar o recurso do server-public-ip:mapped-port.", "protocol": "Protocolo", "protocolSelect": "Selecione um protocolo", "resourcePortNumber": "Número da Porta", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domínios", "sidebarBluePrints": "Diagramas", "blueprints": "Diagramas", - "blueprintsDescription": "Diagramas são configurações declarativas YAML que definem seus recursos e suas configurações", + "blueprintsDescription": "Aplicar configurações declarativas e ver execuções anteriores", "blueprintAdd": "Adicionar Diagrama", "blueprintGoBack": "Ver todos os Diagramas", "blueprintCreate": "Criar Diagrama", "blueprintCreateDescription2": "Siga as etapas abaixo para criar e aplicar um novo diagrama", "blueprintDetails": "Detalhes do Diagrama", - "blueprintDetailsDescription": "Veja os detalhes da execução do diagrama", + "blueprintDetailsDescription": "Veja o resultado do diagrama aplicado e todos os erros que ocorreram", "blueprintInfo": "Informação do Diagrama", "message": "mensagem", "blueprintContentsDescription": "Defina o conteúdo YAML descrevendo a sua infraestrutura", @@ -1181,7 +1181,7 @@ "appliedAt": "Aplicado em", "source": "fonte", "contents": "Conteúdo", - "parsedContents": "Conteúdo analisado", + "parsedContents": "Conteúdo analisado (Somente Leitura)", "enableDockerSocket": "Habilitar o Diagrama Docker", "enableDockerSocketDescription": "Ativar a scraping de rótulo Docker para rótulos de diagramas. Caminho de Socket deve ser fornecido para Newt.", "enableDockerSocketLink": "Saiba mais", @@ -2080,5 +2080,20 @@ "supportSending": "Enviando...", "supportSend": "Mandar", "supportMessageSent": "Mensagem enviada!", - "supportWillContact": "Entraremos em contato em breve!" -} + "supportWillContact": "Entraremos em contato em breve!", + "selectLogRetention": "Selecionar retenção de log", + "showColumns": "Exibir Colunas", + "hideColumns": "Ocultar colunas", + "columnVisibility": "Visibilidade da Coluna", + "toggleColumn": "Alternar coluna {columnName}", + "allColumns": "Todas as colunas", + "defaultColumns": "Colunas padrão", + "customizeView": "Personalizar visualização", + "viewOptions": "Opções de visualização", + "selectAll": "Selecionar Todos", + "selectNone": "Não selecionar nada", + "selectedResources": "Recursos Selecionados", + "enableSelected": "Habilitar Selecionados", + "disableSelected": "Desativar Selecionados", + "checkSelectedStatus": "Status de Verificação dos Selecionados" +} \ No newline at end of file From 5e0d822d45e5782deb8c6cccb76cf291108b89c7 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:38 -0800 Subject: [PATCH 50/63] New translations en-us.json (Russian) --- messages/ru-RU.json | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 732bee6a..b3608b98 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -131,7 +131,7 @@ "expireIn": "Срок действия", "neverExpire": "Бессрочный доступ", "shareExpireDescription": "Срок действия - это период, в течение которого ссылка будет работать и предоставлять доступ к ресурсу. После этого времени ссылка перестанет работать, и пользователи, использовавшие эту ссылку, потеряют доступ к ресурсу.", - "shareSeeOnce": "Вы сможете увидеть эту ссылку только один раз. Обязательно скопируйте её.", + "shareSeeOnce": "Вы сможете увидеть эту ссылку только один раз. Обязательно скопируйте ее.", "shareAccessHint": "Любой, у кого есть эта ссылка, может получить доступ к ресурсу. Делитесь ею с осторожностью.", "shareTokenUsage": "Посмотреть использование токена доступа", "createLink": "Создать ссылку", @@ -179,7 +179,7 @@ "baseDomain": "Базовый домен", "subdomnainDescription": "Поддомен, на котором будет доступен ресурс.", "resourceRawSettings": "Настройки TCP/UDP", - "resourceRawSettingsDescription": "Настройте, как будет осуществляться доступ к вашему ресурсу через TCP/UDP", + "resourceRawSettingsDescription": "Настройте доступ к вашему ресурсу по TCP/UDP. Вы соотносите ресурс с портом на сервере хоста Pangolin, так что вы можете получить доступ к ресурсу с сервера server-public-ip:mapped-порта.", "protocol": "Протокол", "protocolSelect": "Выберите протокол", "resourcePortNumber": "Номер порта", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Домены", "sidebarBluePrints": "Чертежи", "blueprints": "Чертежи", - "blueprintsDescription": "Чертежи являются декларативными конфигурациями YAML, которые определяют ваши ресурсы и их настройки", + "blueprintsDescription": "Применить декларирующие конфигурации и просмотреть предыдущие запуски", "blueprintAdd": "Добавить чертёж", "blueprintGoBack": "Посмотреть все чертежи", "blueprintCreate": "Создать чертёж", "blueprintCreateDescription2": "Для создания и применения нового чертежа выполните следующие шаги", - "blueprintDetails": "Подробности чертежа", - "blueprintDetailsDescription": "Посмотреть детали запуска чертежа", + "blueprintDetails": "Детали чертежа", + "blueprintDetailsDescription": "Посмотреть результат примененного чертежа и все возникшие ошибки", "blueprintInfo": "Информация о чертеже", "message": "Сообщение", "blueprintContentsDescription": "Определите содержимое YAML, описывающее вашу инфраструктуру", @@ -1181,7 +1181,7 @@ "appliedAt": "Заявка на", "source": "Источник", "contents": "Содержание", - "parsedContents": "Обработанное содержимое", + "parsedContents": "Переработанное содержимое (только для чтения)", "enableDockerSocket": "Включить чертёж Docker", "enableDockerSocketDescription": "Включить scraping ярлыка Docker Socket для ярлыков чертежей. Путь к сокету должен быть предоставлен в Newt.", "enableDockerSocketLink": "Узнать больше", @@ -2080,5 +2080,20 @@ "supportSending": "Отправка...", "supportSend": "Отправить", "supportMessageSent": "Сообщение отправлено!", - "supportWillContact": "Мы скоро свяжемся с Вами!" -} + "supportWillContact": "Мы скоро свяжемся с Вами!", + "selectLogRetention": "Выберите удержание журнала", + "showColumns": "Показать колонки", + "hideColumns": "Скрыть столбцы", + "columnVisibility": "Видимость столбцов", + "toggleColumn": "Столбец {columnName}", + "allColumns": "Все колонки", + "defaultColumns": "Столбцы по умолчанию", + "customizeView": "Настроить вид", + "viewOptions": "Параметры просмотра", + "selectAll": "Выделить все", + "selectNone": "Не выбирать", + "selectedResources": "Выбранные ресурсы", + "enableSelected": "Включить выбранные", + "disableSelected": "Отключить выбранные", + "checkSelectedStatus": "Проверить статус выбранных" +} \ No newline at end of file From 7995fd364e81f81f9eb1fb3185a22051bfabca4c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:39 -0800 Subject: [PATCH 51/63] New translations en-us.json (Turkish) --- messages/tr-TR.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index a3470f53..a396059e 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -179,7 +179,7 @@ "baseDomain": "Temel Alan Adı", "subdomnainDescription": "Kaynağınızın erişilebileceği alt alan adı.", "resourceRawSettings": "TCP/UDP Ayarları", - "resourceRawSettingsDescription": "Kaynağınıza TCP/UDP üzerinden erişimin nasıl sağlanacağını yapılandırın", + "resourceRawSettingsDescription": "Kaynağınızın TCP/UDP üzerinden nasıl erişileceğini yapılandırın. Kaynağı, sunucudan erişebilmeniz için bir ana bilgisayar Pangolin sunucusundaki bir bağlantı noktasına eşlersiniz: sunucu genel-IP: eşlenen-bağlantı-noktası.", "protocol": "Protokol", "protocolSelect": "Bir protokol seçin", "resourcePortNumber": "Port Numarası", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Alan Adları", "sidebarBluePrints": "Planlar", "blueprints": "Planlar", - "blueprintsDescription": "Planlar, kaynaklarınızı ve ayarlarını tanımlayan bildirimsel YAML yapılandırmalarıdır", + "blueprintsDescription": "Deklaratif yapılandırmaları uygulayın ve önceki çalışmaları görüntüleyin", "blueprintAdd": "Plan Ekle", "blueprintGoBack": "Tüm Planları Gör", "blueprintCreate": "Plan Oluştur", "blueprintCreateDescription2": "Yeni bir plan oluşturup uygulamak için aşağıdaki adımları izleyin", - "blueprintDetails": "Plan Detayları", - "blueprintDetailsDescription": "Plan çalıştırma detaylarını görün", + "blueprintDetails": "Mavi Yazılım Detayları", + "blueprintDetailsDescription": "Uygulanan mavi yazılımın sonucunu ve oluşan hataları görün", "blueprintInfo": "Plan Bilgileri", "message": "Mesaj", "blueprintContentsDescription": "Altyapınızı tanımlayan YAML içeriğini tanımlayın", @@ -1181,7 +1181,7 @@ "appliedAt": "Uygulama Zamanı", "source": "Kaynak", "contents": "İçerik", - "parsedContents": "Ayrıştırılmış İçerik", + "parsedContents": "Verilerin Ayrıştırılmış İçeriği (Salt Okunur)", "enableDockerSocket": "Docker Soketini Etkinleştir", "enableDockerSocketDescription": "Plan etiketleri için Docker Socket etiket toplamasını etkinleştirin. Newt'e soket yolu sağlanmalıdır.", "enableDockerSocketLink": "Daha fazla bilgi", @@ -2080,5 +2080,20 @@ "supportSending": "Gönderiliyor...", "supportSend": "Gönder", "supportMessageSent": "Mesaj Gönderildi!", - "supportWillContact": "En kısa sürede size geri döneceğiz!" -} + "supportWillContact": "En kısa sürede size geri döneceğiz!", + "selectLogRetention": "Kayıt saklama seç", + "showColumns": "Sütunları Göster", + "hideColumns": "Sütunları Gizle", + "columnVisibility": "Sütun Görünürlüğü", + "toggleColumn": "{columnName} sütununu aç/kapat", + "allColumns": "Tüm Sütunlar", + "defaultColumns": "Varsayılan Sütunlar", + "customizeView": "Görünümü Özelleştir", + "viewOptions": "Görünüm Seçenekleri", + "selectAll": "Tümünü Seç", + "selectNone": "Hiçbirini Seçme", + "selectedResources": "Seçilen Kaynaklar", + "enableSelected": "Seçilenleri Etkinleştir", + "disableSelected": "Seçilenleri Devre Dışı Bırak", + "checkSelectedStatus": "Seçilenlerin Durumunu Kontrol Et" +} \ No newline at end of file From a229fc1c6125f9109cb97d30c0c9508d9ef637b1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:40 -0800 Subject: [PATCH 52/63] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 32b09130..87401859 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -131,7 +131,7 @@ "expireIn": "过期时间", "neverExpire": "永不过期", "shareExpireDescription": "过期时间是链接可以使用并提供对资源的访问时间。 此时间后,链接将不再工作,使用此链接的用户将失去对资源的访问。", - "shareSeeOnce": "您只能看到此链接。请确保复制它。", + "shareSeeOnce": "您只能看到一次此链接。请确保复制它。", "shareAccessHint": "任何具有此链接的人都可以访问该资源。小心地分享它。", "shareTokenUsage": "查看访问令牌使用情况", "createLink": "创建链接", @@ -179,7 +179,7 @@ "baseDomain": "根域名", "subdomnainDescription": "您的资源可以访问的子域名。", "resourceRawSettings": "TCP/UDP 设置", - "resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问您的资源", + "resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问您的资源。 您映射资源到主机Pangolin服务器上的端口,这样您就可以访问服务器-公共-ip:mapped端口的资源。", "protocol": "协议", "protocolSelect": "选择协议", "resourcePortNumber": "端口号", @@ -1165,13 +1165,13 @@ "sidebarDomains": "域", "sidebarBluePrints": "蓝图", "blueprints": "蓝图", - "blueprintsDescription": "蓝图是用于定义资源及其设置的 YAML 声明配置", + "blueprintsDescription": "应用声明配置并查看先前运行的", "blueprintAdd": "添加蓝图", "blueprintGoBack": "查看所有蓝图", "blueprintCreate": "创建蓝图", "blueprintCreateDescription2": "按照下面的步骤创建和应用新的蓝图", "blueprintDetails": "蓝图详细信息", - "blueprintDetailsDescription": "查看蓝图运行详情", + "blueprintDetailsDescription": "查看应用蓝图的结果和发生的任何错误", "blueprintInfo": "蓝图信息", "message": "留言", "blueprintContentsDescription": "定义描述您基础设施的 YAML 内容", @@ -1181,7 +1181,7 @@ "appliedAt": "应用于", "source": "来源", "contents": "目录", - "parsedContents": "解析内容", + "parsedContents": "解析内容 (只读)", "enableDockerSocket": "启用 Docker 蓝图", "enableDockerSocketDescription": "启用 Docker Socket 标签擦除蓝图标签。套接字路径必须提供给新的。", "enableDockerSocketLink": "了解更多", @@ -2080,5 +2080,20 @@ "supportSending": "正在发送...", "supportSend": "发送", "supportMessageSent": "消息已发送!", - "supportWillContact": "我们很快就会联系起来!" -} + "supportWillContact": "我们很快就会联系起来!", + "selectLogRetention": "选择保留日志", + "showColumns": "显示列", + "hideColumns": "隐藏列", + "columnVisibility": "列可见性", + "toggleColumn": "切换 {columnName} 列", + "allColumns": "全部列", + "defaultColumns": "默认列", + "customizeView": "自定义视图", + "viewOptions": "查看选项", + "selectAll": "选择所有", + "selectNone": "没有选择", + "selectedResources": "选定的资源", + "enableSelected": "启用选中的", + "disableSelected": "禁用选中的", + "checkSelectedStatus": "检查选中的状态" +} \ No newline at end of file From a33695506617419645de1c79dae24c420b2177ca Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Sat, 8 Nov 2025 16:22:42 -0800 Subject: [PATCH 53/63] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index d59d5e83..fabeec04 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -131,7 +131,7 @@ "expireIn": "Utløper om", "neverExpire": "Utløper aldri", "shareExpireDescription": "Utløpstid er hvor lenge lenken vil være brukbar og gi tilgang til ressursen. Etter denne tiden vil lenken ikke lenger fungere, og brukere som brukte denne lenken vil miste tilgangen til ressursen.", - "shareSeeOnce": "Du får bare se denne lenken én gang. Pass på å kopiere den.", + "shareSeeOnce": "Du vil bare kunne se denne linken én gang. Pass på å kopiere den.", "shareAccessHint": "Alle med denne lenken kan få tilgang til ressursen. Del forsiktig.", "shareTokenUsage": "Se tilgangstokenbruk", "createLink": "Opprett lenke", @@ -179,7 +179,7 @@ "baseDomain": "Grunndomene", "subdomnainDescription": "Underdomenet der ressursen din vil være tilgjengelig.", "resourceRawSettings": "TCP/UDP-innstillinger", - "resourceRawSettingsDescription": "Konfigurer tilgang til ressursen din over TCP/UDP", + "resourceRawSettingsDescription": "Konfigurer hvordan din ressurs vil bli tilgjengelig over TCP/UDP. Du kartlegger ressursen til en port på vertsserveren Pangolin slik at du får tilgang til ressursene fra server-ip:mappet port.", "protocol": "Protokoll", "protocolSelect": "Velg en protokoll", "resourcePortNumber": "Portnummer", @@ -1165,13 +1165,13 @@ "sidebarDomains": "Domener", "sidebarBluePrints": "Tegninger", "blueprints": "Tegninger", - "blueprintsDescription": "Tegninger er deklarative YAML konfigurasjoner som definerer dine ressurser og deres innstillinger", + "blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer", "blueprintAdd": "Legg til blåkopi", "blueprintGoBack": "Se alle blåkopier", "blueprintCreate": "Opprette mal", "blueprintCreateDescription2": "Følg trinnene nedenfor for å opprette og bruke en ny plantegning", "blueprintDetails": "Blåkopi detaljer", - "blueprintDetailsDescription": "Se detaljer om plantegning", + "blueprintDetailsDescription": "Se resultatet av den påførte blåkopien og alle feil som oppstod", "blueprintInfo": "Blåkopi informasjon", "message": "Melding", "blueprintContentsDescription": "Definer innhold av YAML som beskriver din infrastruktur", @@ -1181,7 +1181,7 @@ "appliedAt": "Anvendt på", "source": "Kilde", "contents": "Innhold", - "parsedContents": "Parket innhold", + "parsedContents": "Parastinnhold (kun lese)", "enableDockerSocket": "Aktiver Docker blåkopi", "enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.", "enableDockerSocketLink": "Lær mer", @@ -2080,5 +2080,20 @@ "supportSending": "Sender...", "supportSend": "Sende", "supportMessageSent": "Melding sendt!", - "supportWillContact": "Vi kommer raskt til å ta kontakt!" -} + "supportWillContact": "Vi kommer raskt til å ta kontakt!", + "selectLogRetention": "Velg oppbevaring av logg", + "showColumns": "Vis kolonner", + "hideColumns": "Skjul kolonner", + "columnVisibility": "Kolonne Synlighet", + "toggleColumn": "Veksle {columnName} kolonne", + "allColumns": "Alle kolonner", + "defaultColumns": "Standard kolonner", + "customizeView": "Tilpass visning", + "viewOptions": "Vis alternativer", + "selectAll": "Velg alle", + "selectNone": "Velg ingen", + "selectedResources": "Valgte ressurser", + "enableSelected": "Aktiver valgte", + "disableSelected": "Deaktiver valgte", + "checkSelectedStatus": "Kontroller status for valgte" +} \ No newline at end of file From 37acdc2796ddbcfc39ee853efe09b2be8349babb Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 8 Nov 2025 16:33:36 -0800 Subject: [PATCH 54/63] Revert transaction --- server/routers/target/deleteTarget.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/routers/target/deleteTarget.ts b/server/routers/target/deleteTarget.ts index 8a400d60..596691e4 100644 --- a/server/routers/target/deleteTarget.ts +++ b/server/routers/target/deleteTarget.ts @@ -48,12 +48,10 @@ export async function deleteTarget( const { targetId } = parsedParams.data; - const [deletedTarget] = await db.transaction(async (tx) => { - return await tx - .delete(targets) - .where(eq(targets.targetId, targetId)) - .returning(); - }); + const [deletedTarget] = await db + .delete(targets) + .where(eq(targets.targetId, targetId)) + .returning(); if (!deletedTarget) { return next( From d38b321f85ffdf7fc2ea22ed0f4d518d3baed12c Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 8 Nov 2025 16:47:03 -0800 Subject: [PATCH 55/63] Add missing header --- server/private/routers/misc/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/private/routers/misc/index.ts b/server/private/routers/misc/index.ts index d8d5f4d3..da598425 100644 --- a/server/private/routers/misc/index.ts +++ b/server/private/routers/misc/index.ts @@ -1 +1,14 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + export * from "./sendSupportEmail"; From bdf1625976fa375108a3177e8ef114ce40229a50 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 9 Nov 2025 10:46:46 -0800 Subject: [PATCH 56/63] Add headers --- server/private/routers/re-key/index.ts | 13 +++++++++++++ .../routers/re-key/reGenerateClientSecret.ts | 13 +++++++++++++ .../private/routers/re-key/reGenerateSiteSecret.ts | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/server/private/routers/re-key/index.ts b/server/private/routers/re-key/index.ts index 7e04d9e4..41a1c967 100644 --- a/server/private/routers/re-key/index.ts +++ b/server/private/routers/re-key/index.ts @@ -1,3 +1,16 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + export * from "./reGenerateClientSecret"; export * from "./reGenerateSiteSecret"; export * from "./reGenerateExitNodeSecret"; \ No newline at end of file diff --git a/server/private/routers/re-key/reGenerateClientSecret.ts b/server/private/routers/re-key/reGenerateClientSecret.ts index d16d433b..e07099a4 100644 --- a/server/private/routers/re-key/reGenerateClientSecret.ts +++ b/server/private/routers/re-key/reGenerateClientSecret.ts @@ -1,3 +1,16 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, olms, } from "@server/db"; diff --git a/server/private/routers/re-key/reGenerateSiteSecret.ts b/server/private/routers/re-key/reGenerateSiteSecret.ts index 1d046933..3826cbc3 100644 --- a/server/private/routers/re-key/reGenerateSiteSecret.ts +++ b/server/private/routers/re-key/reGenerateSiteSecret.ts @@ -1,3 +1,16 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, newts, sites } from "@server/db"; From c004e969cbbfca681e4805afb8577ec7828d4f5a Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Wed, 12 Nov 2025 00:30:08 +0530 Subject: [PATCH 57/63] improve IPv6 validation to support all variants using ipaddr.js --- server/lib/validators.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/server/lib/validators.ts b/server/lib/validators.ts index 59776105..db6ff26b 100644 --- a/server/lib/validators.ts +++ b/server/lib/validators.ts @@ -1,4 +1,5 @@ import z from "zod"; +import ipaddr from "ipaddr.js"; export function isValidCIDR(cidr: string): boolean { return z.string().cidr().safeParse(cidr).success; @@ -68,11 +69,11 @@ export function isUrlValid(url: string | undefined) { if (!url) return true; // the link is optional in the schema so if it's empty it's valid var pattern = new RegExp( "^(https?:\\/\\/)?" + // protocol - "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name - "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address - "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path - "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string - "(\\#[-a-z\\d_]*)?$", + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name + "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", "i" ); return !!pattern.test(url); @@ -83,12 +84,15 @@ export function isTargetValid(value: string | undefined) { const DOMAIN_REGEX = /^[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9_])?(?:\.[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9_])?)*$/; - const IPV4_REGEX = - /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i; + // const IPV4_REGEX = + // /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + // const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i; - if (IPV4_REGEX.test(value) || IPV6_REGEX.test(value)) { - return true; + try { + const addr = ipaddr.parse(value); + return addr.kind() === "ipv4" || addr.kind() === "ipv6"; + } catch { + // fall through to domain regex check } return DOMAIN_REGEX.test(value); @@ -169,10 +173,10 @@ export function isSecondLevelDomain(domain: string): boolean { } const trimmedDomain = domain.trim().toLowerCase(); - + // Split into parts const parts = trimmedDomain.split('.'); - + // Should have exactly 2 parts for a second-level domain (e.g., "example.com") if (parts.length !== 2) { return false; From 63a1ecfb86b554986ef36154aa1457ce4adf98b1 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Thu, 13 Nov 2025 23:31:29 +0530 Subject: [PATCH 58/63] role in header --- server/routers/badger/verifySession.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index eec83d0f..da5b0f18 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -60,6 +60,7 @@ type BasicUserData = { username: string; email: string | null; name: string | null; + role: string | null; }; export type VerifyUserResponse = { @@ -883,7 +884,8 @@ async function isUserAllowedToAccessResource( return { username: user.username, email: user.email, - name: user.name + name: user.name, + role: user.role }; } @@ -896,7 +898,8 @@ async function isUserAllowedToAccessResource( return { username: user.username, email: user.email, - name: user.name + name: user.name, + role: user.role }; } From 8674ca931b158cee6135575e6396dbe040f3dc1f Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 13 Nov 2025 17:34:49 -0500 Subject: [PATCH 59/63] remove from address in saas suppport email --- server/private/routers/misc/sendSupportEmail.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/private/routers/misc/sendSupportEmail.ts b/server/private/routers/misc/sendSupportEmail.ts index fef43ef8..9b3f9c14 100644 --- a/server/private/routers/misc/sendSupportEmail.ts +++ b/server/private/routers/misc/sendSupportEmail.ts @@ -68,7 +68,7 @@ export async function sendSupportEmail( { name: req.user?.email || "Support User", to: "support@pangolin.net", - from: req.user?.email || config.getNoReplyEmail(), + from: config.getNoReplyEmail(), subject: `Support Request: ${subject}` } ); From 0798a0c6c2108dcd1433fa92de5a8ff71bbd27b2 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 13 Nov 2025 21:48:37 -0500 Subject: [PATCH 60/63] clean up info box --- src/components/InfoSection.tsx | 8 +++++++- src/components/ResourceInfoBox.tsx | 26 +++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/InfoSection.tsx b/src/components/InfoSection.tsx index 41f5cd89..5959bfc3 100644 --- a/src/components/InfoSection.tsx +++ b/src/components/InfoSection.tsx @@ -50,5 +50,11 @@ export function InfoSectionContent({ children: React.ReactNode; className?: string; }) { - return
{children}
; + return ( +
+
+ {children} +
+
+ ); } diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 4d77a434..a75ca78c 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -1,7 +1,7 @@ "use client"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { ShieldCheck, ShieldOff } from "lucide-react"; +import { ShieldCheck, ShieldOff, Eye, EyeOff } from "lucide-react"; import { useResourceContext } from "@app/hooks/useResourceContext"; import CopyToClipboard from "@app/components/CopyToClipboard"; import { @@ -54,12 +54,12 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { authInfo.whitelist || authInfo.headerAuth ? (
- + {t("protected")}
) : (
- + {t("notProtected")}
)} @@ -100,9 +100,7 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { {t("protocol")} - - {resource.protocol.toUpperCase()} - + {resource.protocol.toUpperCase()} @@ -164,11 +162,17 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { {t("visibility")} - - {resource.enabled - ? t("enabled") - : t("disabled")} - + {resource.enabled ? ( +
+ + {t("enabled")} +
+ ) : ( +
+ + {t("disabled")} +
+ )}
From d9564ed6fea37bbe55e60e4153ac8e8184916464 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 13 Nov 2025 22:04:29 -0500 Subject: [PATCH 61/63] improve spacing and colors --- messages/en-US.json | 6 + src/components/ResourceInfoBox.tsx | 12 +- src/components/ResourcesTable.tsx | 218 +++++++++++++++++------------ 3 files changed, 143 insertions(+), 93 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 7f877984..08d4e21a 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1525,6 +1525,12 @@ "resourcesTableTheseResourcesForUseWith": "These resources are for use with", "resourcesTableClients": "Clients", "resourcesTableAndOnlyAccessibleInternally": "and are only accessible internally when connected with a client.", + "resourcesTableNoTargets": "No targets", + "resourcesTableHealthy": "Healthy", + "resourcesTableDegraded": "Degraded", + "resourcesTableOffline": "Offline", + "resourcesTableUnknown": "Unknown", + "resourcesTableNotMonitored": "Not monitored", "editInternalResourceDialogEditClientResource": "Edit Client Resource", "editInternalResourceDialogUpdateResourceProperties": "Update the resource properties and target configuration for {resourceName}.", "editInternalResourceDialogResourceProperties": "Resource Properties", diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index a75ca78c..0696c605 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -53,8 +53,8 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { authInfo.sso || authInfo.whitelist || authInfo.headerAuth ? ( -
- +
+ {t("protected")}
) : ( @@ -163,13 +163,13 @@ export default function ResourceInfoBox({ }: ResourceInfoBoxType) { {t("visibility")} {resource.enabled ? ( -
- +
+ {t("enabled")}
) : ( -
- +
+ {t("disabled")}
)} diff --git a/src/components/ResourcesTable.tsx b/src/components/ResourcesTable.tsx index 252ac7d2..171f3491 100644 --- a/src/components/ResourcesTable.tsx +++ b/src/components/ResourcesTable.tsx @@ -17,7 +17,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, - DropdownMenuCheckboxItem, + DropdownMenuCheckboxItem } from "@app/components/ui/dropdown-menu"; import { Button } from "@app/components/ui/button"; import { @@ -36,7 +36,7 @@ import { Wifi, WifiOff, CheckCircle2, - XCircle, + XCircle } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; @@ -75,13 +75,12 @@ import EditInternalResourceDialog from "@app/components/EditInternalResourceDial import CreateInternalResourceDialog from "@app/components/CreateInternalResourceDialog"; import { Alert, AlertDescription } from "@app/components/ui/alert"; - export type TargetHealth = { targetId: number; ip: string; port: number; enabled: boolean; - healthStatus?: 'healthy' | 'unhealthy' | 'unknown'; + healthStatus?: "healthy" | "unhealthy" | "unknown"; }; export type ResourceRow = { @@ -102,45 +101,55 @@ export type ResourceRow = { targets?: TargetHealth[]; }; - -function getOverallHealthStatus(targets?: TargetHealth[]): 'online' | 'degraded' | 'offline' | 'unknown' { +function getOverallHealthStatus( + targets?: TargetHealth[] +): "online" | "degraded" | "offline" | "unknown" { if (!targets || targets.length === 0) { - return 'unknown'; + return "unknown"; } - const monitoredTargets = targets.filter(t => t.enabled && t.healthStatus && t.healthStatus !== 'unknown'); + const monitoredTargets = targets.filter( + (t) => t.enabled && t.healthStatus && t.healthStatus !== "unknown" + ); if (monitoredTargets.length === 0) { - return 'unknown'; + return "unknown"; } - const healthyCount = monitoredTargets.filter(t => t.healthStatus === 'healthy').length; - const unhealthyCount = monitoredTargets.filter(t => t.healthStatus === 'unhealthy').length; + const healthyCount = monitoredTargets.filter( + (t) => t.healthStatus === "healthy" + ).length; + const unhealthyCount = monitoredTargets.filter( + (t) => t.healthStatus === "unhealthy" + ).length; if (healthyCount === monitoredTargets.length) { - return 'online'; + return "online"; } else if (unhealthyCount === monitoredTargets.length) { - return 'offline'; + return "offline"; } else { - return 'degraded'; + return "degraded"; } } -function StatusIcon({ status, className = "" }: { - status: 'online' | 'degraded' | 'offline' | 'unknown'; +function StatusIcon({ + status, + className = "" +}: { + status: "online" | "degraded" | "offline" | "unknown"; className?: string; }) { const iconClass = `h-4 w-4 ${className}`; switch (status) { - case 'online': + case "online": return ; - case 'degraded': + case "degraded": return ; - case 'offline': + case "offline": return ; - case 'unknown': - return ; + case "unknown": + return ; default: return null; } @@ -171,15 +180,14 @@ type ResourcesTableProps = { }; }; - const STORAGE_KEYS = { - PAGE_SIZE: 'datatable-page-size', + PAGE_SIZE: "datatable-page-size", getTablePageSize: (tableId?: string) => tableId ? `datatable-${tableId}-page-size` : STORAGE_KEYS.PAGE_SIZE }; const getStoredPageSize = (tableId?: string, defaultSize = 20): number => { - if (typeof window === 'undefined') return defaultSize; + if (typeof window === "undefined") return defaultSize; try { const key = STORAGE_KEYS.getTablePageSize(tableId); @@ -191,24 +199,22 @@ const getStoredPageSize = (tableId?: string, defaultSize = 20): number => { } } } catch (error) { - console.warn('Failed to read page size from localStorage:', error); + console.warn("Failed to read page size from localStorage:", error); } return defaultSize; }; const setStoredPageSize = (pageSize: number, tableId?: string): void => { - if (typeof window === 'undefined') return; + if (typeof window === "undefined") return; try { const key = STORAGE_KEYS.getTablePageSize(tableId); localStorage.setItem(key, pageSize.toString()); } catch (error) { - console.warn('Failed to save page size to localStorage:', error); + console.warn("Failed to save page size to localStorage:", error); } }; - - export default function ResourcesTable({ resources, internalResources, @@ -224,12 +230,11 @@ export default function ResourcesTable({ const api = createApiClient({ env }); - const [proxyPageSize, setProxyPageSize] = useState(() => - getStoredPageSize('proxy-resources', 20) + getStoredPageSize("proxy-resources", 20) ); const [internalPageSize, setInternalPageSize] = useState(() => - getStoredPageSize('internal-resources', 20) + getStoredPageSize("internal-resources", 20) ); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -247,8 +252,10 @@ export default function ResourcesTable({ defaultSort ? [defaultSort] : [] ); - const [proxyColumnVisibility, setProxyColumnVisibility] = useState({}); - const [internalColumnVisibility, setInternalColumnVisibility] = useState({}); + const [proxyColumnVisibility, setProxyColumnVisibility] = + useState({}); + const [internalColumnVisibility, setInternalColumnVisibility] = + useState({}); const [proxyColumnFilters, setProxyColumnFilters] = useState([]); @@ -427,24 +434,34 @@ export default function ResourcesTable({ return (
- No targets + + {t("resourcesTableNoTargets")} +
); } - const monitoredTargets = targets.filter(t => t.enabled && t.healthStatus && t.healthStatus !== 'unknown'); - const unknownTargets = targets.filter(t => !t.enabled || !t.healthStatus || t.healthStatus === 'unknown'); + const monitoredTargets = targets.filter( + (t) => t.enabled && t.healthStatus && t.healthStatus !== "unknown" + ); + const unknownTargets = targets.filter( + (t) => !t.enabled || !t.healthStatus || t.healthStatus === "unknown" + ); return ( - @@ -453,16 +470,29 @@ export default function ResourcesTable({ {monitoredTargets.length > 0 && ( <> {monitoredTargets.map((target) => ( - +
{`${target.ip}:${target.port}`}
- + {target.healthStatus}
@@ -472,13 +502,21 @@ export default function ResourcesTable({ {unknownTargets.length > 0 && ( <> {unknownTargets.map((target) => ( - +
- + {`${target.ip}:${target.port}`}
- {!target.enabled ? 'Disabled' : 'Not monitored'} + {!target.enabled + ? t("disabled") + : t("resourcesTableNotMonitored")}
))} @@ -489,7 +527,6 @@ export default function ResourcesTable({ ); } - const proxyColumns: ColumnDef[] = [ { accessorKey: "name", @@ -512,7 +549,15 @@ export default function ResourcesTable({ header: t("protocol"), cell: ({ row }) => { const resourceRow = row.original; - return {resourceRow.http ? (resourceRow.ssl ? "HTTPS" : "HTTP") : resourceRow.protocol.toUpperCase()}; + return ( + + {resourceRow.http + ? resourceRow.ssl + ? "HTTPS" + : "HTTP" + : resourceRow.protocol.toUpperCase()} + + ); } }, { @@ -538,7 +583,12 @@ export default function ResourcesTable({ sortingFn: (rowA, rowB) => { const statusA = getOverallHealthStatus(rowA.original.targets); const statusB = getOverallHealthStatus(rowB.original.targets); - const statusOrder = { online: 3, degraded: 2, offline: 1, unknown: 0 }; + const statusOrder = { + online: 3, + degraded: 2, + offline: 1, + unknown: 0 + }; return statusOrder[statusA] - statusOrder[statusB]; } }, @@ -589,13 +639,13 @@ export default function ResourcesTable({ return (
{resourceRow.authState === "protected" ? ( - - + + {t("protected")} ) : resourceRow.authState === "not_protected" ? ( - - + + {t("notProtected")} ) : ( @@ -841,12 +891,12 @@ export default function ResourcesTable({ const handleProxyPageSizeChange = (newPageSize: number) => { setProxyPageSize(newPageSize); - setStoredPageSize(newPageSize, 'proxy-resources'); + setStoredPageSize(newPageSize, "proxy-resources"); }; const handleInternalPageSizeChange = (newPageSize: number) => { setInternalPageSize(newPageSize); - setStoredPageSize(newPageSize, 'internal-resources'); + setStoredPageSize(newPageSize, "internal-resources"); }; return ( @@ -860,12 +910,8 @@ export default function ResourcesTable({ }} dialog={
-

- {t("resourceQuestionRemove")} -

-

- {t("resourceMessageRemove")} -

+

{t("resourceQuestionRemove")}

+

{t("resourceMessageRemove")}

} buttonText={t("resourceDeleteConfirm")} @@ -884,12 +930,8 @@ export default function ResourcesTable({ }} dialog={
-

- {t("resourceQuestionRemove")} -

-

- {t("resourceMessageRemove")} -

+

{t("resourceQuestionRemove")}

+

{t("resourceMessageRemove")}

} buttonText={t("resourceDeleteConfirm")} @@ -939,9 +981,7 @@ export default function ResourcesTable({ {t("refresh")}
-
- {getActionButton()} -
+
{getActionButton()}
@@ -960,12 +1000,12 @@ export default function ResourcesTable({ {header.isPlaceholder ? null : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} + header + .column + .columnDef + .header, + header.getContext() + )} ) )} @@ -1023,7 +1063,9 @@ export default function ResourcesTable({
@@ -1061,12 +1103,12 @@ export default function ResourcesTable({ {header.isPlaceholder ? null : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} + header + .column + .columnDef + .header, + header.getContext() + )} ) )} @@ -1124,7 +1166,9 @@ export default function ResourcesTable({
From 4e0a2e441b1454911afeb9b965b0a750699fdd91 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 14 Nov 2025 11:31:44 -0500 Subject: [PATCH 62/63] hide domain status info if not flags.use_pangolin_dns --- src/components/DNSRecordTable.tsx | 48 ++++++++++---------- src/components/DomainInfoCard.tsx | 50 +++++++++++---------- src/components/DomainsTable.tsx | 73 ++++++++++++++++--------------- 3 files changed, 91 insertions(+), 80 deletions(-) diff --git a/src/components/DNSRecordTable.tsx b/src/components/DNSRecordTable.tsx index 37fa66c0..8c8f6bb4 100644 --- a/src/components/DNSRecordTable.tsx +++ b/src/components/DNSRecordTable.tsx @@ -5,6 +5,7 @@ import { useTranslations } from "next-intl"; import { Badge } from "@app/components/ui/badge"; import { DNSRecordsDataTable } from "./DNSRecordsDataTable"; import CopyToClipboard from "@app/components/CopyToClipboard"; +import { useEnvContext } from "@app/hooks/useEnvContext"; export type DNSRecordRow = { id: string; @@ -24,6 +25,30 @@ export default function DNSRecordsTable({ type }: Props) { const t = useTranslations(); + const env = useEnvContext(); + + const statusColumn: ColumnDef = { + accessorKey: "verified", + header: ({ column }) => { + return
{t("status")}
; + }, + cell: ({ row }) => { + const verified = row.original.verified; + return verified ? ( + type === "wildcard" ? ( + + {t("manual", { fallback: "Manual" })} + + ) : ( + {t("verified")} + ) + ) : ( + + {t("pending", { fallback: "Pending" })} + + ); + } + }; const columns: ColumnDef[] = [ { @@ -81,28 +106,7 @@ export default function DNSRecordsTable({ ); } }, - { - accessorKey: "verified", - header: ({ column }) => { - return
{t("status")}
; - }, - cell: ({ row }) => { - const verified = row.original.verified; - return verified ? ( - type === "wildcard" ? ( - - {t("manual", { fallback: "Manual" })} - - ) : ( - {t("verified")} - ) - ) : ( - - {t("pending", { fallback: "Pending" })} - - ); - } - } + ...(env.env.flags.usePangolinDns ? [statusColumn] : []) ]; return ( diff --git a/src/components/DomainInfoCard.tsx b/src/components/DomainInfoCard.tsx index b5de6cd3..e33998da 100644 --- a/src/components/DomainInfoCard.tsx +++ b/src/components/DomainInfoCard.tsx @@ -9,6 +9,7 @@ import { } from "@app/components/InfoSection"; import { useTranslations } from "next-intl"; import { Badge } from "./ui/badge"; +import { useEnvContext } from "@app/hooks/useEnvContext"; type DomainInfoCardProps = { failed: boolean; @@ -22,6 +23,7 @@ export default function DomainInfoCard({ type }: DomainInfoCardProps) { const t = useTranslations(); + const env = useEnvContext(); const getTypeDisplay = (type: string) => { switch (type) { @@ -46,32 +48,34 @@ export default function DomainInfoCard({ {getTypeDisplay(type ? type : "")} - - {t("status")} - - {failed ? ( - - {t("failed", { fallback: "Failed" })} - - ) : verified ? ( - type === "wildcard" ? ( - - {t("manual", { - fallback: "Manual" - })} + {env.env.flags.usePangolinDns && ( + + {t("status")} + + {failed ? ( + + {t("failed", { fallback: "Failed" })} + ) : verified ? ( + type === "wildcard" ? ( + + {t("manual", { + fallback: "Manual" + })} + + ) : ( + + {t("verified")} + + ) ) : ( - - {t("verified")} + + {t("pending", { fallback: "Pending" })} - ) - ) : ( - - {t("pending", { fallback: "Pending" })} - - )} - - + )} + + + )} diff --git a/src/components/DomainsTable.tsx b/src/components/DomainsTable.tsx index 3e12bab5..a099a8b8 100644 --- a/src/components/DomainsTable.tsx +++ b/src/components/DomainsTable.tsx @@ -55,7 +55,8 @@ export default function DomainsTable({ domains, orgId }: Props) { const [restartingDomains, setRestartingDomains] = useState>( new Set() ); - const api = createApiClient(useEnvContext()); + const env = useEnvContext(); + const api = createApiClient(env); const router = useRouter(); const t = useTranslations(); const { toast } = useToast(); @@ -134,6 +135,41 @@ export default function DomainsTable({ domains, orgId }: Props) { } }; + const statusColumn: ColumnDef = { + accessorKey: "verified", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const { verified, failed, type } = row.original; + if (verified) { + return type == "wildcard" ? ( + {t("manual")} + ) : ( + {t("verified")} + ); + } else if (failed) { + return ( + + {t("failed", { fallback: "Failed" })} + + ); + } else { + return {t("pending")}; + } + } + }; + const columns: ColumnDef[] = [ { accessorKey: "baseDomain", @@ -173,40 +209,7 @@ export default function DomainsTable({ domains, orgId }: Props) { ); } }, - { - accessorKey: "verified", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - const { verified, failed, type } = row.original; - if (verified) { - return type == "wildcard" ? ( - {t("manual")} - ) : ( - {t("verified")} - ); - } else if (failed) { - return ( - - {t("failed", { fallback: "Failed" })} - - ); - } else { - return {t("pending")}; - } - } - }, + ...(env.env.flags.usePangolinDns ? [statusColumn] : []), { id: "actions", cell: ({ row }) => { From 7c728c144caaf781a684ff69b4f203c63be3832b Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 14 Nov 2025 11:57:29 -0500 Subject: [PATCH 63/63] fix broken inputs in health check form --- .../resources/[niceId]/proxy/page.tsx | 20 +++++++++++++++---- .../settings/resources/create/page.tsx | 4 +++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx index aa268250..461d3f1c 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -512,9 +512,18 @@ export default function ReverseProxyTargets(props: { port: target.port, enabled: target.enabled, hcEnabled: target.hcEnabled, - hcPath: target.hcPath, - hcInterval: target.hcInterval, - hcTimeout: target.hcTimeout + hcPath: target.hcPath || null, + hcScheme: target.hcScheme || null, + hcHostname: target.hcHostname || null, + hcPort: target.hcPort || null, + hcInterval: target.hcInterval || null, + hcTimeout: target.hcTimeout || null, + hcHeaders: target.hcHeaders || null, + hcFollowRedirects: target.hcFollowRedirects || null, + hcMethod: target.hcMethod || null, + hcStatus: target.hcStatus || null, + hcUnhealthyInterval: target.hcUnhealthyInterval || null, + hcMode: target.hcMode || null }; // Only include path-related fields for HTTP resources @@ -718,7 +727,9 @@ export default function ReverseProxyTargets(props: { hcHeaders: target.hcHeaders || null, hcFollowRedirects: target.hcFollowRedirects || null, hcMethod: target.hcMethod || null, - hcStatus: target.hcStatus || null + hcStatus: target.hcStatus || null, + hcUnhealthyInterval: target.hcUnhealthyInterval || null, + hcMode: target.hcMode || null }; // Only include path-related fields for HTTP resources @@ -1814,6 +1825,7 @@ export default function ReverseProxyTargets(props: { 30 }} onChanges={async (config) => { + console.log("here"); if (selectedTargetForHealthCheck) { console.log(config); updateTargetHealthCheck( diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index 1f98cf69..ae5e452d 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -574,7 +574,9 @@ export default function Page() { hcPort: target.hcPort || null, hcFollowRedirects: target.hcFollowRedirects || null, - hcStatus: target.hcStatus || null + hcStatus: target.hcStatus || null, + hcUnhealthyInterval: target.hcUnhealthyInterval || null, + hcMode: target.hcMode || null }; // Only include path-related fields for HTTP resources