Compare commits

..

6 Commits

Author SHA1 Message Date
dependabot[bot]
04d15a5a3f Bump the dev-minor-updates group across 1 directory with 7 updates
Bumps the dev-minor-updates group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `1.54.1` | `1.57.2` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.91.3` | `5.95.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.3.5` | `25.5.0` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.18.0` | `8.20.0` |
| [eslint](https://github.com/eslint/eslint) | `10.0.3` | `10.1.0` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.1.7` | `16.2.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.56.1` | `8.57.1` |



Updates `@dotenvx/dotenvx` from 1.54.1 to 1.57.2
- [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.54.1...v1.57.2)

Updates `@tanstack/react-query-devtools` from 5.91.3 to 5.95.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.95.0/packages/react-query-devtools)

Updates `@types/node` from 25.3.5 to 25.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/pg` from 8.18.0 to 8.20.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

Updates `eslint` from 10.0.3 to 10.1.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.0.3...v10.1.0)

Updates `eslint-config-next` from 16.1.7 to 16.2.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.2.1/packages/eslint-config-next)

Updates `typescript-eslint` from 8.56.1 to 8.57.1
- [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.57.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 1.57.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.95.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@types/pg"
  dependency-version: 8.20.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: eslint
  dependency-version: 10.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: eslint-config-next
  dependency-version: 16.2.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
  dependency-version: 8.57.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-01 01:37:59 +00:00
Owen Schwartz
0fc1aa9191 Merge pull request #2755 from fosrl/dev
Update go version
2026-03-31 16:04:11 -07:00
Owen Schwartz
ddf417f4ca Merge pull request #2753 from fosrl/dev
Update security
2026-03-31 15:27:47 -07:00
Owen Schwartz
d08be59055 Merge pull request #2752 from fosrl/dev
1.17.0-rc.0
2026-03-31 15:24:25 -07:00
Owen Schwartz
322c136d1f Merge pull request #2748 from jaydeep-pipaliya/fix/empty-targets-toast-message
fix: show contextual toast when saving with no targets
2026-03-31 15:11:12 -07:00
jaydeep-pipaliya
e06f2f47b1 fix: show contextual toast when saving with no targets
Instead of always showing "Settings updated" when saving, show
"Targets cleared" when the target list is empty. This gives the user
accurate feedback without blocking the save action.

Fixes #586
2026-03-31 11:48:56 +05:30
16 changed files with 143 additions and 199 deletions

View File

@@ -60,7 +60,7 @@ Pangolin is an open-source, identity-based remote access platform built on WireG
| <img width=500 /> | Description |
|-----------------|--------------|
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing - no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/understanding-nodes) and connect to our control plane. |
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/understanding-nodes) and connect to our control plane. |
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |

View File

@@ -371,10 +371,10 @@
"provisioningKeysUpdated": "Provisioning key updated",
"provisioningKeysUpdatedDescription": "Your changes have been saved.",
"provisioningKeysBannerTitle": "Site Provisioning Keys",
"provisioningKeysBannerDescription": "Generate a provisioning key and use it with the Newt connector to automatically create sites on first startup - no need to set up separate credentials for each site.",
"provisioningKeysBannerDescription": "Generate a provisioning key and use it with the Newt connector to automatically create sites on first startup no need to set up separate credentials for each site.",
"provisioningKeysBannerButtonText": "Learn More",
"pendingSitesBannerTitle": "Pending Sites",
"pendingSitesBannerDescription": "Sites that connect using a provisioning key appear here for review.",
"pendingSitesBannerDescription": "Sites that connect using a provisioning key appear here for review. Approve each site before it becomes active and gains access to your resources.",
"pendingSitesBannerButtonText": "Learn More",
"apiKeysSettings": "{apiKeyName} Settings",
"userTitle": "Manage All Users",
@@ -624,6 +624,8 @@
"targetErrorInvalidPortDescription": "Please enter a valid port number",
"targetErrorNoSite": "No site selected",
"targetErrorNoSiteDescription": "Please select a site for the target",
"targetTargetsCleared": "Targets cleared",
"targetTargetsClearedDescription": "All targets have been removed from this resource",
"targetCreated": "Target created",
"targetCreatedDescription": "Target has been created successfully",
"targetErrorCreate": "Failed to create target",
@@ -2346,7 +2348,7 @@
"description": "Enterprise features, 50 users, 50 sites, and priority support."
}
},
"personalUseOnly": "Personal use only (free license - no checkout)",
"personalUseOnly": "Personal use only (free license no checkout)",
"buttons": {
"continueToCheckout": "Continue to Checkout"
},

228
package-lock.json generated
View File

@@ -89,13 +89,13 @@
"reodotdev": "1.1.0",
"resend": "6.9.2",
"semver": "7.7.4",
"sshpk": "^1.18.0",
"sshpk": "1.18.0",
"stripe": "20.4.1",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.5.0",
"topojson-client": "3.1.0",
"tw-animate-css": "1.4.0",
"use-debounce": "^10.1.0",
"use-debounce": "10.1.0",
"uuid": "13.0.0",
"vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0",
@@ -108,11 +108,11 @@
"zod-validation-error": "5.0.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.54.1",
"@dotenvx/dotenvx": "1.59.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/preview-server": "5.2.10",
"@tailwindcss/postcss": "4.2.2",
"@tanstack/react-query-devtools": "5.91.3",
"@tanstack/react-query-devtools": "5.96.0",
"@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
@@ -123,14 +123,14 @@
"@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "25.3.5",
"@types/node": "25.5.0",
"@types/nodemailer": "7.0.11",
"@types/nprogress": "0.2.3",
"@types/pg": "8.18.0",
"@types/pg": "8.20.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
"@types/sshpk": "^1.17.4",
"@types/sshpk": "1.17.4",
"@types/swagger-ui-express": "4.1.8",
"@types/topojson-client": "3.1.5",
"@types/ws": "8.18.1",
@@ -139,8 +139,8 @@
"drizzle-kit": "0.31.10",
"esbuild": "0.27.4",
"esbuild-node-externals": "1.20.1",
"eslint": "10.0.3",
"eslint-config-next": "16.1.7",
"eslint": "10.1.0",
"eslint-config-next": "16.2.2",
"postcss": "8.5.8",
"prettier": "3.8.1",
"react-email": "5.2.10",
@@ -148,7 +148,7 @@
"tsc-alias": "1.8.16",
"tsx": "4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "8.56.1"
"typescript-eslint": "8.58.0"
}
},
"node_modules/@alloc/quick-lru": {
@@ -1460,9 +1460,9 @@
"license": "MIT"
},
"node_modules/@dotenvx/dotenvx": {
"version": "1.54.1",
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.54.1.tgz",
"integrity": "sha512-41gU3q7v05GM92QPuPUf4CmUw+mmF8p4wLUh6MCRlxpCkJ9ByLcY9jUf6MwrMNmiKyG/rIckNxj9SCfmNCmCqw==",
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.59.1.tgz",
"integrity": "sha512-Qg+meC+XFxliuVSDlEPkKnaUjdaJKK6FNx/Wwl2UxhQR8pyPIuLhMavsF7ePdB9qFZUWV1jEK3ckbJir/WmF4w==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -2868,9 +2868,9 @@
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "16.1.7",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.7.tgz",
"integrity": "sha512-v/bRGOJlfRCO+NDKt0bZlIIWjhMKU8xbgEQBo+rV9C8S6czZvs96LZ/v24/GvpEnovZlL4QDpku/RzWHVbmPpA==",
"version": "16.2.2",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.2.tgz",
"integrity": "sha512-IOPbWzDQ+76AtjZioaCjpIY72xNSDMnarZ2GMQ4wjNLvnJEJHqxQwGFhgnIWLV9klb4g/+amg88Tk5OXVpyLTw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8403,9 +8403,9 @@
}
},
"node_modules/@tanstack/query-devtools": {
"version": "5.93.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz",
"integrity": "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==",
"version": "5.96.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.96.0.tgz",
"integrity": "sha512-MEdO1M/9ItB62OtTqVo8AIj/G6vJemA642N56bw8aIqpXKIj5VG/3xWgh2piw76NmoCIlapxjjWp1MMLmrvKJw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -8429,20 +8429,20 @@
}
},
"node_modules/@tanstack/react-query-devtools": {
"version": "5.91.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz",
"integrity": "sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==",
"version": "5.96.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.96.0.tgz",
"integrity": "sha512-P0WFX0s3iYii4oZTSCK9T3/PBQ9uY/SVTzcZFbyzCo5ujeIAsqos3HjBWoF/lhJXWVe8UXkjAmgXr3TUD11q2A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/query-devtools": "5.93.0"
"@tanstack/query-devtools": "5.96.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"@tanstack/react-query": "^5.90.20",
"@tanstack/react-query": "^5.96.0",
"react": "^18 || ^19"
}
},
@@ -8969,9 +8969,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.3.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -8996,9 +8996,9 @@
"license": "MIT"
},
"node_modules/@types/pg": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
"integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -9153,20 +9153,20 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
"integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz",
"integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/type-utils": "8.56.1",
"@typescript-eslint/utils": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1",
"@typescript-eslint/scope-manager": "8.58.0",
"@typescript-eslint/type-utils": "8.58.0",
"@typescript-eslint/utils": "8.58.0",
"@typescript-eslint/visitor-keys": "8.58.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9176,9 +9176,9 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.56.1",
"@typescript-eslint/parser": "^8.58.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
@@ -9192,16 +9192,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz",
"integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1",
"@typescript-eslint/scope-manager": "8.58.0",
"@typescript-eslint/types": "8.58.0",
"@typescript-eslint/typescript-estree": "8.58.0",
"@typescript-eslint/visitor-keys": "8.58.0",
"debug": "^4.4.3"
},
"engines": {
@@ -9213,18 +9213,18 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
"integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz",
"integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.56.1",
"@typescript-eslint/types": "^8.56.1",
"@typescript-eslint/tsconfig-utils": "^8.58.0",
"@typescript-eslint/types": "^8.58.0",
"debug": "^4.4.3"
},
"engines": {
@@ -9235,18 +9235,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
"integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz",
"integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1"
"@typescript-eslint/types": "8.58.0",
"@typescript-eslint/visitor-keys": "8.58.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9257,9 +9257,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
"integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz",
"integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9270,21 +9270,21 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
"integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz",
"integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/utils": "8.56.1",
"@typescript-eslint/types": "8.58.0",
"@typescript-eslint/typescript-estree": "8.58.0",
"@typescript-eslint/utils": "8.58.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9295,13 +9295,13 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
"integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz",
"integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9313,21 +9313,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
"integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz",
"integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.56.1",
"@typescript-eslint/tsconfig-utils": "8.56.1",
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1",
"@typescript-eslint/project-service": "8.58.0",
"@typescript-eslint/tsconfig-utils": "8.58.0",
"@typescript-eslint/types": "8.58.0",
"@typescript-eslint/visitor-keys": "8.58.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.4.0"
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9337,20 +9337,20 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
"integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz",
"integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1"
"@typescript-eslint/scope-manager": "8.58.0",
"@typescript-eslint/types": "8.58.0",
"@typescript-eslint/typescript-estree": "8.58.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9361,17 +9361,17 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
"integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz",
"integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/types": "8.58.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -12378,16 +12378,16 @@
}
},
"node_modules/eslint": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz",
"integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz",
"integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.23.3",
"@eslint/config-helpers": "^0.5.2",
"@eslint/config-helpers": "^0.5.3",
"@eslint/core": "^1.1.1",
"@eslint/plugin-kit": "^0.6.1",
"@humanfs/node": "^0.16.6",
@@ -12400,7 +12400,7 @@
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^9.1.2",
"eslint-visitor-keys": "^5.0.1",
"espree": "^11.1.1",
"espree": "^11.2.0",
"esquery": "^1.7.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -12434,13 +12434,13 @@
}
},
"node_modules/eslint-config-next": {
"version": "16.1.7",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.7.tgz",
"integrity": "sha512-FTq1i/QDltzq+zf9aB/cKWAiZ77baG0V7h8dRQh3thVx7I4dwr6ZXQrWKAaTB7x5VwVXlzoUTyMLIVQPLj2gJg==",
"version": "16.2.2",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.2.tgz",
"integrity": "sha512-6VlvEhwoug2JpVgjZDhyXrJXUEuPY++TddzIpTaIRvlvlXXFgvQUtm3+Zr84IjFm0lXtJt73w19JA08tOaZVwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "16.1.7",
"@next/eslint-plugin-next": "16.2.2",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -18819,9 +18819,9 @@
}
},
"node_modules/ts-api-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
"integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -19160,16 +19160,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz",
"integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==",
"version": "8.58.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz",
"integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/utils": "8.56.1"
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
"@typescript-eslint/typescript-estree": "8.58.0",
"@typescript-eslint/utils": "8.58.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -19180,7 +19180,7 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/uint8array-extras": {

View File

@@ -131,11 +131,11 @@
"zod-validation-error": "5.0.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.54.1",
"@dotenvx/dotenvx": "1.59.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/preview-server": "5.2.10",
"@tailwindcss/postcss": "4.2.2",
"@tanstack/react-query-devtools": "5.91.3",
"@tanstack/react-query-devtools": "5.96.0",
"@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
@@ -146,10 +146,10 @@
"@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "25.3.5",
"@types/node": "25.5.0",
"@types/nodemailer": "7.0.11",
"@types/nprogress": "0.2.3",
"@types/pg": "8.18.0",
"@types/pg": "8.20.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
@@ -162,8 +162,8 @@
"drizzle-kit": "0.31.10",
"esbuild": "0.27.4",
"esbuild-node-externals": "1.20.1",
"eslint": "10.0.3",
"eslint-config-next": "16.1.7",
"eslint": "10.1.0",
"eslint-config-next": "16.2.2",
"postcss": "8.5.8",
"prettier": "3.8.1",
"react-email": "5.2.10",
@@ -171,7 +171,7 @@
"tsc-alias": "1.8.16",
"tsx": "4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "8.56.1"
"typescript-eslint": "8.58.0"
},
"overrides": {
"esbuild": "0.27.4",

View File

@@ -8,7 +8,6 @@ import { sendToExitNode } from "#dynamic/lib/exitNodes";
import { buildClientConfigurationForNewtClient } from "./buildConfiguration";
import { convertTargetsIfNessicary } from "../client/targets";
import { canCompress } from "@server/lib/clientVersionChecks";
import config from "@server/lib/config";
export const handleGetConfigMessage: MessageHandler = async (context) => {
const { message, client, sendToClient } = context;
@@ -56,7 +55,7 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 5) {
logger.warn(
`Site last hole punch is too old; skipping this register. The site is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?`
`handleGetConfigMessage: Site ${existingSite.siteId} last hole punch is too old, skipping`
);
return;
}

View File

@@ -1,4 +1,4 @@
import { db, newts, sites, targetHealthCheck, targets } from "@server/db";
import { db, newts, sites } from "@server/db";
import {
hasActiveConnections,
getClientConfigVersion
@@ -78,32 +78,6 @@ export const startNewtOfflineChecker = (): void => {
.update(sites)
.set({ online: false })
.where(eq(sites.siteId, staleSite.siteId));
const healthChecksOnSite = await db
.select()
.from(targetHealthCheck)
.innerJoin(
targets,
eq(targets.targetId, targetHealthCheck.targetId)
)
.innerJoin(sites, eq(sites.siteId, targets.siteId))
.where(eq(sites.siteId, staleSite.siteId));
for (const healthCheck of healthChecksOnSite) {
logger.info(
`Marking health check ${healthCheck.targetHealthCheck.targetHealthCheckId} offline due to site ${staleSite.siteId} being marked offline`
);
await db
.update(targetHealthCheck)
.set({ hcHealth: "unknown" })
.where(
eq(
targetHealthCheck.targetHealthCheckId,
healthCheck.targetHealthCheck
.targetHealthCheckId
)
);
}
}
// this part only effects self hosted. Its not efficient but we dont expect people to have very many wireguard sites
@@ -128,8 +102,7 @@ export const startNewtOfflineChecker = (): void => {
// loop over each one. If its offline and there is a new update then mark it online. If its online and there is no update then mark it offline
for (const site of allWireguardSites) {
const lastBandwidthUpdate =
new Date(site.lastBandwidthUpdate!).getTime() / 1000;
const lastBandwidthUpdate = new Date(site.lastBandwidthUpdate!).getTime() / 1000;
if (
lastBandwidthUpdate < wireguardOfflineThreshold &&
site.online

View File

@@ -20,7 +20,6 @@ import { handleFingerprintInsertion } from "./fingerprintingUtils";
import { Alias } from "@server/lib/ip";
import { build } from "@server/build";
import { canCompress } from "@server/lib/clientVersionChecks";
import config from "@server/lib/config";
export const handleOlmRegisterMessage: MessageHandler = async (context) => {
logger.info("Handling register olm message!");
@@ -275,7 +274,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
// TODO: I still think there is a better way to do this rather than locking it out here but ???
if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0) {
logger.warn(
`Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?`
"Client last hole punch is too old and we have sites to send; skipping this register"
);
return;
}

View File

@@ -77,8 +77,7 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
const [targetCheck] = await db
.select({
targetId: targets.targetId,
siteId: targets.siteId,
hcStatus: targetHealthCheck.hcHealth
siteId: targets.siteId
})
.from(targets)
.innerJoin(
@@ -86,7 +85,6 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
eq(targets.resourceId, resources.resourceId)
)
.innerJoin(sites, eq(targets.siteId, sites.siteId))
.innerJoin(targetHealthCheck, eq(targets.targetId, targetHealthCheck.targetId))
.where(
and(
eq(targets.targetId, targetIdNum),
@@ -103,14 +101,6 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
continue;
}
// check if the status has changed
if (targetCheck.hcStatus === healthStatus.status) {
logger.debug(
`Health status for target ${targetId} is already ${healthStatus.status}, skipping update`
);
continue;
}
// Update the target's health status in the database
await db
.update(targetHealthCheck)

View File

@@ -9,8 +9,6 @@ import DismissableBanner from "@app/components/DismissableBanner";
import Link from "next/link";
import { Button } from "@app/components/ui/button";
import { ArrowRight, Plug } from "lucide-react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
type PendingSitesPageProps = {
params: Promise<{ orgId: string }>;
@@ -98,10 +96,6 @@ export default async function PendingSitesPage(props: PendingSitesPageProps) {
</Button>
</Link>
</DismissableBanner>
<PaidFeaturesAlert
tiers={tierMatrix[TierFeature.SiteProvisioningKeys]}
/>
<PendingSitesTable
sites={siteRows}
orgId={params.orgId}

View File

@@ -774,8 +774,12 @@ function ProxyResourceTargetsForm({
}
toast({
title: t("settingsUpdated"),
description: t("settingsUpdatedDescription")
title: targets.length === 0
? t("targetTargetsCleared")
: t("settingsUpdated"),
description: targets.length === 0
? t("targetTargetsClearedDescription")
: t("settingsUpdatedDescription")
});
setTargetsToRemove([]);

View File

@@ -614,7 +614,6 @@ export function InternalResourceForm({
<SitesSelector
orgId={orgId}
selectedSite={selectedSite}
filterTypes={["newt"]}
onSelectSite={(site) => {
setSelectedSite(site);
field.onChange(site.siteId);

View File

@@ -15,8 +15,6 @@ import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { build } from "@server/build";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { type PaginationState } from "@tanstack/react-table";
import {
ArrowDown01Icon,
@@ -65,10 +63,6 @@ export default function PendingSitesTable({
const api = createApiClient(useEnvContext());
const t = useTranslations();
const { isPaidUser } = usePaidStatus();
const canUseSiteProvisioning =
isPaidUser(tierMatrix[TierFeature.SiteProvisioningKeys]) &&
build !== "oss";
const booleanSearchFilterSchema = z
.enum(["true", "false"])
@@ -456,7 +450,6 @@ export default function PendingSitesTable({
onSearch={handleSearchChange}
onRefresh={refreshData}
isRefreshing={isRefreshing || isFiltering}
refreshButtonDisabled={!canUseSiteProvisioning}
rowCount={rowCount}
columnVisibility={{
niceId: false,

View File

@@ -311,7 +311,6 @@ export default function SiteProvisioningKeysTable({
addButtonDisabled={!canUseSiteProvisioning}
onRefresh={refreshData}
isRefreshing={isRefreshing}
refreshButtonDisabled={!canUseSiteProvisioning}
addButtonText={t("provisioningKeysAdd")}
enableColumnVisibility={true}
stickyLeftColumn="name"

View File

@@ -24,14 +24,12 @@ export type SitesSelectorProps = {
orgId: string;
selectedSite?: Selectedsite | null;
onSelectSite: (selected: Selectedsite) => void;
filterTypes?: string[];
};
export function SitesSelector({
orgId,
selectedSite,
onSelectSite,
filterTypes
onSelectSite
}: SitesSelectorProps) {
const t = useTranslations();
const [siteSearchQuery, setSiteSearchQuery] = useState("");
@@ -47,9 +45,7 @@ export function SitesSelector({
// always include the selected site in the list of sites shown
const sitesShown = useMemo(() => {
const allSites: Array<Selectedsite> = filterTypes
? sites.filter((s) => filterTypes.includes(s.type))
: [...sites];
const allSites: Array<Selectedsite> = [...sites];
if (
debouncedQuery.trim().length === 0 &&
selectedSite &&
@@ -58,7 +54,7 @@ export function SitesSelector({
allSites.unshift(selectedSite);
}
return allSites;
}, [debouncedQuery, sites, selectedSite, filterTypes]);
}, [debouncedQuery, sites, selectedSite]);
return (
<Command shouldFilter={false}>

View File

@@ -69,7 +69,6 @@ type ControlledDataTableProps<TData, TValue> = {
onAdd?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
refreshButtonDisabled?: boolean;
isNavigatingToAddPage?: boolean;
searchPlaceholder?: string;
filters?: DataTableFilter[];
@@ -92,7 +91,6 @@ export function ControlledDataTable<TData, TValue>({
onAdd,
onRefresh,
isRefreshing,
refreshButtonDisabled = false,
searchPlaceholder = "Search...",
filters,
filterDisplayMode = "label",
@@ -337,7 +335,7 @@ export function ControlledDataTable<TData, TValue>({
<Button
variant="outline"
onClick={onRefresh}
disabled={isRefreshing || refreshButtonDisabled}
disabled={isRefreshing}
>
<RefreshCw
className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}

View File

@@ -174,7 +174,6 @@ type DataTableProps<TData, TValue> = {
addButtonDisabled?: boolean;
onRefresh?: () => void;
isRefreshing?: boolean;
refreshButtonDisabled?: boolean;
searchPlaceholder?: string;
searchColumn?: string;
defaultSort?: {
@@ -208,7 +207,6 @@ export function DataTable<TData, TValue>({
addButtonDisabled = false,
onRefresh,
isRefreshing,
refreshButtonDisabled = false,
searchPlaceholder = "Search...",
searchColumn = "name",
defaultSort,
@@ -626,7 +624,7 @@ export function DataTable<TData, TValue>({
<Button
variant="outline"
onClick={onRefresh}
disabled={isRefreshing || refreshButtonDisabled}
disabled={isRefreshing}
>
<RefreshCw
className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}