mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 17:40:44 +00:00
Merge branch 'main' into feat/addReplicateFolderContent
This commit is contained in:
Generated
+193
-158
@@ -362,26 +362,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
|
||||
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
|
||||
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/types": "^7.26.0"
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz",
|
||||
"integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
|
||||
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.3"
|
||||
"@babel/types": "^7.26.10"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -423,9 +423,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
||||
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
@@ -435,14 +435,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.25.9",
|
||||
"@babel/parser": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -476,9 +476,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz",
|
||||
"integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
@@ -1010,6 +1010,23 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
@@ -1028,9 +1045,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1647,12 +1664,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||
"version": "10.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
|
||||
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0",
|
||||
"@octokit/types": "^13.6.2",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1674,18 +1691,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==",
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "11.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.6.tgz",
|
||||
"integrity": "sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==",
|
||||
"version": "11.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz",
|
||||
"integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.6.2"
|
||||
"@octokit/types": "^13.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@@ -1722,14 +1739,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
||||
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
|
||||
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^10.0.0",
|
||||
"@octokit/request-error": "^6.0.1",
|
||||
"@octokit/types": "^13.1.0",
|
||||
"@octokit/endpoint": "^10.1.3",
|
||||
"@octokit/request-error": "^6.1.7",
|
||||
"@octokit/types": "^13.6.2",
|
||||
"fast-content-type-parse": "^2.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1737,12 +1755,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
|
||||
"integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0"
|
||||
"@octokit/types": "^13.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@@ -1764,12 +1782,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "13.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz",
|
||||
"integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==",
|
||||
"version": "13.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-cms": {
|
||||
@@ -4956,9 +4974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
|
||||
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
@@ -5451,9 +5469,9 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
|
||||
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -7314,6 +7332,22 @@
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"node_modules/fast-content-type-parse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
|
||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -12588,13 +12622,13 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
|
||||
"integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
|
||||
"version": "4.19.3",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz",
|
||||
"integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.23.0",
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
@@ -12608,9 +12642,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
|
||||
"integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -12625,9 +12659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
|
||||
"integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -12642,9 +12676,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -12659,9 +12693,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12676,9 +12710,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -12693,9 +12727,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12710,9 +12744,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -12727,9 +12761,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12744,9 +12778,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
|
||||
"integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -12761,9 +12795,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -12778,9 +12812,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
|
||||
"integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -12795,9 +12829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
||||
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
|
||||
"integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -12812,9 +12846,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
||||
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
|
||||
"integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -12829,9 +12863,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
|
||||
"integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -12846,9 +12880,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
||||
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
|
||||
"integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -12863,9 +12897,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
||||
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
|
||||
"integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -12880,9 +12914,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12897,9 +12931,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12914,9 +12948,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12931,9 +12965,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12948,9 +12982,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
|
||||
"integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -12965,9 +12999,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
|
||||
"integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -12982,9 +13016,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
|
||||
"integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12999,9 +13033,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/esbuild": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
||||
"integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -13012,30 +13046,31 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.23.1",
|
||||
"@esbuild/android-arm": "0.23.1",
|
||||
"@esbuild/android-arm64": "0.23.1",
|
||||
"@esbuild/android-x64": "0.23.1",
|
||||
"@esbuild/darwin-arm64": "0.23.1",
|
||||
"@esbuild/darwin-x64": "0.23.1",
|
||||
"@esbuild/freebsd-arm64": "0.23.1",
|
||||
"@esbuild/freebsd-x64": "0.23.1",
|
||||
"@esbuild/linux-arm": "0.23.1",
|
||||
"@esbuild/linux-arm64": "0.23.1",
|
||||
"@esbuild/linux-ia32": "0.23.1",
|
||||
"@esbuild/linux-loong64": "0.23.1",
|
||||
"@esbuild/linux-mips64el": "0.23.1",
|
||||
"@esbuild/linux-ppc64": "0.23.1",
|
||||
"@esbuild/linux-riscv64": "0.23.1",
|
||||
"@esbuild/linux-s390x": "0.23.1",
|
||||
"@esbuild/linux-x64": "0.23.1",
|
||||
"@esbuild/netbsd-x64": "0.23.1",
|
||||
"@esbuild/openbsd-arm64": "0.23.1",
|
||||
"@esbuild/openbsd-x64": "0.23.1",
|
||||
"@esbuild/sunos-x64": "0.23.1",
|
||||
"@esbuild/win32-arm64": "0.23.1",
|
||||
"@esbuild/win32-ia32": "0.23.1",
|
||||
"@esbuild/win32-x64": "0.23.1"
|
||||
"@esbuild/aix-ppc64": "0.25.1",
|
||||
"@esbuild/android-arm": "0.25.1",
|
||||
"@esbuild/android-arm64": "0.25.1",
|
||||
"@esbuild/android-x64": "0.25.1",
|
||||
"@esbuild/darwin-arm64": "0.25.1",
|
||||
"@esbuild/darwin-x64": "0.25.1",
|
||||
"@esbuild/freebsd-arm64": "0.25.1",
|
||||
"@esbuild/freebsd-x64": "0.25.1",
|
||||
"@esbuild/linux-arm": "0.25.1",
|
||||
"@esbuild/linux-arm64": "0.25.1",
|
||||
"@esbuild/linux-ia32": "0.25.1",
|
||||
"@esbuild/linux-loong64": "0.25.1",
|
||||
"@esbuild/linux-mips64el": "0.25.1",
|
||||
"@esbuild/linux-ppc64": "0.25.1",
|
||||
"@esbuild/linux-riscv64": "0.25.1",
|
||||
"@esbuild/linux-s390x": "0.25.1",
|
||||
"@esbuild/linux-x64": "0.25.1",
|
||||
"@esbuild/netbsd-arm64": "0.25.1",
|
||||
"@esbuild/netbsd-x64": "0.25.1",
|
||||
"@esbuild/openbsd-arm64": "0.25.1",
|
||||
"@esbuild/openbsd-x64": "0.25.1",
|
||||
"@esbuild/sunos-x64": "0.25.1",
|
||||
"@esbuild/win32-arm64": "0.25.1",
|
||||
"@esbuild/win32-ia32": "0.25.1",
|
||||
"@esbuild/win32-x64": "0.25.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsyringe": {
|
||||
@@ -13552,9 +13587,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
||||
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
|
||||
"version": "5.4.14",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
|
||||
"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -17,6 +17,8 @@ export type CheckboxProps = Omit<
|
||||
isError?: boolean;
|
||||
isIndeterminate?: boolean;
|
||||
containerClassName?: string;
|
||||
indicatorClassName?: string;
|
||||
allowMultilineLabel?: boolean;
|
||||
};
|
||||
|
||||
export const Checkbox = ({
|
||||
@@ -30,6 +32,8 @@ export const Checkbox = ({
|
||||
isError,
|
||||
isIndeterminate,
|
||||
containerClassName,
|
||||
indicatorClassName,
|
||||
allowMultilineLabel,
|
||||
...props
|
||||
}: CheckboxProps): JSX.Element => {
|
||||
return (
|
||||
@@ -48,7 +52,9 @@ export const Checkbox = ({
|
||||
{...props}
|
||||
id={id}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={twMerge(`${checkIndicatorBg || "text-bunker-800"}`, indicatorClassName)}
|
||||
>
|
||||
{isIndeterminate ? (
|
||||
<FontAwesomeIcon icon={faMinus} size="sm" />
|
||||
) : (
|
||||
@@ -57,7 +63,11 @@ export const Checkbox = ({
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
<label
|
||||
className={twMerge("truncate whitespace-nowrap text-sm", isError && "text-red-400")}
|
||||
className={twMerge(
|
||||
"text-sm",
|
||||
!allowMultilineLabel && "truncate whitespace-nowrap",
|
||||
isError && "text-red-400"
|
||||
)}
|
||||
htmlFor={id}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,81 +1,118 @@
|
||||
import Select, { Props } from "react-select";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ClearIndicator, DropdownIndicator, MultiValueRemove, Option } from "../Select/components";
|
||||
import {
|
||||
ClearIndicator,
|
||||
DropdownIndicator,
|
||||
Group,
|
||||
MultiValueRemove,
|
||||
Option
|
||||
} from "../Select/components";
|
||||
|
||||
export const FilterableSelect = <T,>({
|
||||
isMulti,
|
||||
closeMenuOnSelect,
|
||||
tabSelectsValue = false,
|
||||
groupBy = null,
|
||||
getGroupHeaderLabel = null,
|
||||
options = [],
|
||||
...props
|
||||
}: Props<T>) => (
|
||||
<Select
|
||||
isMulti={isMulti}
|
||||
closeMenuOnSelect={closeMenuOnSelect ?? !isMulti}
|
||||
hideSelectedOptions={false}
|
||||
unstyled
|
||||
styles={{
|
||||
input: (base) => ({
|
||||
...base,
|
||||
"input:focus": {
|
||||
boxShadow: "none"
|
||||
}
|
||||
}),
|
||||
multiValueLabel: (base) => ({
|
||||
...base,
|
||||
whiteSpace: "normal",
|
||||
overflow: "visible"
|
||||
}),
|
||||
control: (base) => ({
|
||||
...base,
|
||||
transition: "none"
|
||||
})
|
||||
}}
|
||||
tabSelectsValue={tabSelectsValue}
|
||||
components={{
|
||||
DropdownIndicator,
|
||||
ClearIndicator,
|
||||
MultiValueRemove,
|
||||
Option,
|
||||
...props.components
|
||||
}}
|
||||
classNames={{
|
||||
container: ({ isDisabled }) =>
|
||||
twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"),
|
||||
control: ({ isFocused, isDisabled }) =>
|
||||
twMerge(
|
||||
isFocused ? "border-primary-400/50" : "border-mineshaft-600",
|
||||
`w-full rounded-md border bg-mineshaft-900 p-0.5 font-inter text-mineshaft-200 ${
|
||||
isDisabled ? "!cursor-not-allowed" : "hover:cursor-pointer hover:border-gray-400"
|
||||
} `
|
||||
),
|
||||
placeholder: () =>
|
||||
`${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`,
|
||||
input: () => "pl-1",
|
||||
valueContainer: () =>
|
||||
`px-1 max-h-[8.2rem] ${
|
||||
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]"
|
||||
} gap-1`,
|
||||
singleValue: () => "leading-7 ml-1",
|
||||
multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5",
|
||||
multiValueLabel: () => "leading-6 text-sm",
|
||||
multiValueRemove: () => "hover:text-red text-bunker-400",
|
||||
indicatorsContainer: () => "p-1 gap-1",
|
||||
clearIndicator: () => "p-1 hover:text-red text-bunker-400",
|
||||
indicatorSeparator: () => "bg-bunker-400",
|
||||
dropdownIndicator: () => "text-bunker-200 p-1",
|
||||
menuList: () => "flex flex-col gap-1",
|
||||
menu: () =>
|
||||
"my-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
||||
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
||||
option: ({ isFocused, isSelected }) =>
|
||||
twMerge(
|
||||
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
|
||||
isSelected && "text-mineshaft-200",
|
||||
"rounded px-3 py-2 text-xs hover:cursor-pointer"
|
||||
),
|
||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}: Props<T> & {
|
||||
groupBy?: string | null;
|
||||
getGroupHeaderLabel?: ((groupValue: any) => string) | null;
|
||||
}) => {
|
||||
let processedOptions = options;
|
||||
|
||||
if (groupBy && Array.isArray(options)) {
|
||||
const groupedOptions = options.reduce((acc, option) => {
|
||||
const groupValue = option[groupBy];
|
||||
const groupKey = groupValue?.toString() || "undefined";
|
||||
|
||||
if (!acc[groupKey]) {
|
||||
acc[groupKey] = {
|
||||
label: getGroupHeaderLabel ? getGroupHeaderLabel(groupValue) : groupValue,
|
||||
options: []
|
||||
};
|
||||
}
|
||||
|
||||
acc[groupKey].options.push(option);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
processedOptions = Object.values(groupedOptions);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
isMulti={isMulti}
|
||||
closeMenuOnSelect={closeMenuOnSelect ?? !isMulti}
|
||||
hideSelectedOptions={false}
|
||||
unstyled
|
||||
options={processedOptions}
|
||||
styles={{
|
||||
input: (base) => ({
|
||||
...base,
|
||||
"input:focus": {
|
||||
boxShadow: "none"
|
||||
}
|
||||
}),
|
||||
multiValueLabel: (base) => ({
|
||||
...base,
|
||||
whiteSpace: "normal",
|
||||
overflow: "visible"
|
||||
}),
|
||||
control: (base) => ({
|
||||
...base,
|
||||
transition: "none"
|
||||
})
|
||||
}}
|
||||
tabSelectsValue={tabSelectsValue}
|
||||
components={{
|
||||
DropdownIndicator,
|
||||
ClearIndicator,
|
||||
MultiValueRemove,
|
||||
Option,
|
||||
Group,
|
||||
...props.components
|
||||
}}
|
||||
classNames={{
|
||||
container: ({ isDisabled }) =>
|
||||
twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"),
|
||||
control: ({ isFocused, isDisabled }) =>
|
||||
twMerge(
|
||||
isFocused ? "border-primary-400/50" : "border-mineshaft-600",
|
||||
`w-full rounded-md border bg-mineshaft-900 p-0.5 font-inter text-mineshaft-200 ${
|
||||
isDisabled ? "!cursor-not-allowed" : "hover:cursor-pointer hover:border-gray-400"
|
||||
} `
|
||||
),
|
||||
placeholder: () =>
|
||||
`${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`,
|
||||
input: () => "pl-1",
|
||||
valueContainer: () =>
|
||||
`px-1 max-h-[8.2rem] ${
|
||||
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]"
|
||||
} gap-1`,
|
||||
singleValue: () => "leading-7 ml-1",
|
||||
multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5",
|
||||
multiValueLabel: () => "leading-6 text-sm",
|
||||
multiValueRemove: () => "hover:text-red text-bunker-400",
|
||||
indicatorsContainer: () => "p-1 gap-1",
|
||||
clearIndicator: () => "p-1 hover:text-red text-bunker-400",
|
||||
indicatorSeparator: () => "bg-bunker-400",
|
||||
dropdownIndicator: () => "text-bunker-200 p-1",
|
||||
menuList: () => "flex flex-col gap-1",
|
||||
menu: () =>
|
||||
"my-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
||||
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
||||
option: ({ isFocused, isSelected }) =>
|
||||
twMerge(
|
||||
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
|
||||
isSelected && "text-mineshaft-200",
|
||||
"rounded px-3 py-2 text-xs hover:cursor-pointer"
|
||||
),
|
||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ClearIndicatorProps,
|
||||
components,
|
||||
DropdownIndicatorProps,
|
||||
GroupProps,
|
||||
MultiValueRemoveProps,
|
||||
OptionProps
|
||||
} from "react-select";
|
||||
@@ -45,3 +46,7 @@ export const Option = <T,>({ isSelected, children, ...props }: OptionProps<T>) =
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
export const Group = <T,>(props: GroupProps<T>) => {
|
||||
return <components.Group {...props} />;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export { useOrgPermission } from "./OrgPermissionContext";
|
||||
export type { TOrgPermission } from "./types";
|
||||
export { OrgPermissionActions, OrgPermissionSubjects } from "./types";
|
||||
export {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionGroupActions,
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionSubjects
|
||||
} from "./types";
|
||||
|
||||
@@ -59,6 +59,28 @@ export enum OrgPermissionKmipActions {
|
||||
Setup = "setup"
|
||||
}
|
||||
|
||||
export enum OrgPermissionIdentityActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges",
|
||||
RevokeAuth = "revoke-auth",
|
||||
CreateToken = "create-token",
|
||||
GetToken = "get-token",
|
||||
DeleteToken = "delete-token"
|
||||
}
|
||||
|
||||
export enum OrgPermissionGroupActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges",
|
||||
AddMembers = "add-members",
|
||||
RemoveMembers = "remove-members"
|
||||
}
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
connectionId: string;
|
||||
};
|
||||
@@ -73,15 +95,15 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
||||
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||
| [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections]
|
||||
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
|
||||
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
|
||||
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]
|
||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
|
||||
|
||||
@@ -4,6 +4,9 @@ export {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionCmekActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionGroupActions,
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSub
|
||||
} from "./types";
|
||||
|
||||
@@ -51,6 +51,30 @@ export enum ProjectPermissionSecretSyncActions {
|
||||
RemoveSecrets = "remove-secrets"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionIdentityActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionMemberActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionGroupActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges"
|
||||
}
|
||||
|
||||
export enum PermissionConditionOperators {
|
||||
$IN = "$in",
|
||||
$ALL = "$all",
|
||||
|
||||
@@ -2,6 +2,8 @@ export { useOrganization } from "./OrganizationContext";
|
||||
export type { TOrgPermission } from "./OrgPermissionContext";
|
||||
export {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionGroupActions,
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrgPermission
|
||||
} from "./OrgPermissionContext";
|
||||
@@ -10,7 +12,10 @@ export {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionCmekActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionGroupActions,
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission
|
||||
} from "./ProjectPermissionContext";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const isInfisicalCloud = () =>
|
||||
window.location.origin.includes("https://app.infisical.com") ||
|
||||
window.location.origin.includes("https://us.infisical.com") ||
|
||||
window.location.origin.includes("https://eu.infisical.com");
|
||||
window.location.origin.includes("https://eu.infisical.com") ||
|
||||
window.location.origin.includes("https://gamma.infisical.com");
|
||||
|
||||
@@ -23,7 +23,8 @@ export const useCreateAccessApprovalPolicy = () => {
|
||||
approvers,
|
||||
name,
|
||||
secretPath,
|
||||
enforcementLevel
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
}) => {
|
||||
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
||||
environment,
|
||||
@@ -32,7 +33,8 @@ export const useCreateAccessApprovalPolicy = () => {
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
});
|
||||
return data;
|
||||
},
|
||||
@@ -48,13 +50,22 @@ export const useUpdateAccessApprovalPolicy = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<object, object, TUpdateAccessPolicyDTO>({
|
||||
mutationFn: async ({ id, approvers, approvals, name, secretPath, enforcementLevel }) => {
|
||||
mutationFn: async ({
|
||||
id,
|
||||
approvers,
|
||||
approvals,
|
||||
name,
|
||||
secretPath,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
||||
approvals,
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ export type TAccessApprovalPolicy = {
|
||||
enforcementLevel: EnforcementLevel;
|
||||
updatedAt: Date;
|
||||
approvers?: Approver[];
|
||||
allowedSelfApprovals: boolean;
|
||||
};
|
||||
|
||||
export enum ApproverType {
|
||||
@@ -71,6 +72,7 @@ export type TAccessApprovalRequest = {
|
||||
envId: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
deletedAt: Date | null;
|
||||
allowedSelfApprovals: boolean;
|
||||
};
|
||||
|
||||
reviewers: {
|
||||
@@ -144,6 +146,7 @@ export type TCreateAccessPolicyDTO = {
|
||||
approvals?: number;
|
||||
secretPath?: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
};
|
||||
|
||||
export type TUpdateAccessPolicyDTO = {
|
||||
@@ -154,6 +157,7 @@ export type TUpdateAccessPolicyDTO = {
|
||||
environment?: string;
|
||||
approvals?: number;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
// for invalidating list
|
||||
projectSlug: string;
|
||||
};
|
||||
|
||||
@@ -143,6 +143,7 @@ export const useGetProjectSecretsOverview = (
|
||||
search = "",
|
||||
includeSecrets,
|
||||
includeFolders,
|
||||
includeImports,
|
||||
includeDynamicSecrets,
|
||||
environments
|
||||
}: TGetDashboardProjectSecretsOverviewDTO,
|
||||
@@ -170,6 +171,7 @@ export const useGetProjectSecretsOverview = (
|
||||
projectId,
|
||||
includeSecrets,
|
||||
includeFolders,
|
||||
includeImports,
|
||||
includeDynamicSecrets,
|
||||
environments
|
||||
}),
|
||||
@@ -184,6 +186,7 @@ export const useGetProjectSecretsOverview = (
|
||||
projectId,
|
||||
includeSecrets,
|
||||
includeFolders,
|
||||
includeImports,
|
||||
includeDynamicSecrets,
|
||||
environments
|
||||
}),
|
||||
@@ -197,12 +200,15 @@ export const useGetProjectSecretsOverview = (
|
||||
? unique(select.dynamicSecrets, (i) => i.name)
|
||||
: [];
|
||||
|
||||
const uniqueSecretImports = select.imports ? unique(select.imports, (i) => i.id) : [];
|
||||
|
||||
return {
|
||||
...select,
|
||||
secrets: secrets ? mergePersonalSecrets(secrets) : undefined,
|
||||
totalUniqueSecretsInPage: uniqueSecrets.length,
|
||||
totalUniqueDynamicSecretsInPage: uniqueDynamicSecrets.length,
|
||||
totalUniqueFoldersInPage: uniqueFolders.length
|
||||
totalUniqueFoldersInPage: uniqueFolders.length,
|
||||
totalUniqueSecretImportsInPage: uniqueSecretImports.length
|
||||
};
|
||||
}, []),
|
||||
placeholderData: (previousData) => previousData
|
||||
|
||||
@@ -9,13 +9,16 @@ export type DashboardProjectSecretsOverviewResponse = {
|
||||
folders?: (TSecretFolder & { environment: string })[];
|
||||
dynamicSecrets?: (TDynamicSecret & { environment: string })[];
|
||||
secrets?: SecretV3Raw[];
|
||||
imports?: TSecretImport[];
|
||||
totalSecretCount?: number;
|
||||
totalFolderCount?: number;
|
||||
totalDynamicSecretCount?: number;
|
||||
totalImportCount?: number;
|
||||
totalCount: number;
|
||||
totalUniqueSecretsInPage: number;
|
||||
totalUniqueDynamicSecretsInPage: number;
|
||||
totalUniqueFoldersInPage: number;
|
||||
totalUniqueSecretImportsInPage: number;
|
||||
};
|
||||
|
||||
export type DashboardProjectSecretsDetailsResponse = {
|
||||
@@ -63,6 +66,7 @@ export type TGetDashboardProjectSecretsOverviewDTO = {
|
||||
includeSecrets?: boolean;
|
||||
includeFolders?: boolean;
|
||||
includeDynamicSecrets?: boolean;
|
||||
includeImports?: boolean;
|
||||
environments: string[];
|
||||
};
|
||||
|
||||
|
||||
@@ -350,7 +350,7 @@ export type AddIdentityKubernetesAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
kubernetesHost: string;
|
||||
tokenReviewerJwt: string;
|
||||
tokenReviewerJwt?: string;
|
||||
allowedNamespaces: string;
|
||||
allowedNames: string;
|
||||
allowedAudience: string;
|
||||
@@ -367,7 +367,7 @@ export type UpdateIdentityKubernetesAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
kubernetesHost?: string;
|
||||
tokenReviewerJwt?: string;
|
||||
tokenReviewerJwt?: string | null;
|
||||
allowedNamespaces?: string;
|
||||
allowedNames?: string;
|
||||
allowedAudience?: string;
|
||||
|
||||
@@ -20,5 +20,6 @@ export {
|
||||
useGetOrgTaxIds,
|
||||
useGetOrgTrialUrl,
|
||||
useUpdateOrg,
|
||||
useUpdateOrgBillingDetails
|
||||
useUpdateOrgBillingDetails,
|
||||
useUpgradePrivilegeSystem
|
||||
} from "./queries";
|
||||
|
||||
@@ -129,6 +129,18 @@ export const useUpdateOrg = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpgradePrivilegeSystem = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => {
|
||||
return apiRequest.post("/api/v2/organizations/privilege-system-upgrade");
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetOrgTrialUrl = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
|
||||
|
||||
@@ -15,6 +15,7 @@ export type Organization = {
|
||||
defaultMembershipRole: string;
|
||||
enforceMfa: boolean;
|
||||
selectedMfaMethod?: MfaMethod;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
allowSecretSharingOutsideOrganization?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ export const useCreateSecretApprovalPolicy = () => {
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
}) => {
|
||||
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
|
||||
environment,
|
||||
@@ -25,7 +26,8 @@ export const useCreateSecretApprovalPolicy = () => {
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
});
|
||||
return data;
|
||||
},
|
||||
@@ -41,13 +43,22 @@ export const useUpdateSecretApprovalPolicy = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<object, object, TUpdateSecretPolicyDTO>({
|
||||
mutationFn: async ({ id, approvers, approvals, secretPath, name, enforcementLevel }) => {
|
||||
mutationFn: async ({
|
||||
id,
|
||||
approvers,
|
||||
approvals,
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
|
||||
approvals,
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ export type TSecretApprovalPolicy = {
|
||||
approvers: Approver[];
|
||||
updatedAt: Date;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
};
|
||||
|
||||
export enum ApproverType {
|
||||
@@ -42,6 +43,7 @@ export type TCreateSecretPolicyDTO = {
|
||||
approvers?: Approver[];
|
||||
approvals?: number;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
};
|
||||
|
||||
export type TUpdateSecretPolicyDTO = {
|
||||
@@ -50,6 +52,7 @@ export type TUpdateSecretPolicyDTO = {
|
||||
approvers?: Approver[];
|
||||
secretPath?: string | null;
|
||||
approvals?: number;
|
||||
allowedSelfApprovals?: boolean;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
// for invalidating list
|
||||
workspaceId: string;
|
||||
|
||||
@@ -182,7 +182,8 @@ export const useGetImportedSecretsAllEnvs = ({
|
||||
comment: encSecret.secretComment,
|
||||
createdAt: encSecret.createdAt,
|
||||
updatedAt: encSecret.updatedAt,
|
||||
version: encSecret.version
|
||||
version: encSecret.version,
|
||||
sourceEnv: env
|
||||
};
|
||||
})
|
||||
})),
|
||||
|
||||
@@ -14,6 +14,7 @@ export type TSecretImport = {
|
||||
isReplicationSuccess?: boolean;
|
||||
replicationStatus?: string;
|
||||
lastReplicated?: string;
|
||||
environment?: string;
|
||||
};
|
||||
|
||||
export type TGetImportedFoldersByEnvDTO = {
|
||||
|
||||
@@ -86,13 +86,5 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr
|
||||
[secrets]
|
||||
);
|
||||
|
||||
const getSecretByKey = useCallback(
|
||||
(env: string, key: string) => {
|
||||
const sec = secrets?.find((s) => s.env === env && s.key === key);
|
||||
return sec;
|
||||
},
|
||||
[secrets]
|
||||
);
|
||||
|
||||
return { secKeys, getSecretByKey, getEnvSecretKeyCount };
|
||||
return { secKeys, getEnvSecretKeyCount };
|
||||
};
|
||||
|
||||
@@ -12,17 +12,13 @@ export const DefaultSideBar = () => (
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||
window.location.origin.includes("https://eu.infisical.com") ||
|
||||
window.location.origin.includes("https://gamma.infisical.com")) && (
|
||||
<Link to="/organization/billing">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||
Usage & Billing
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/organization/billing">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||
Usage & Billing
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Other">
|
||||
<Link to="/organization/access-management">
|
||||
|
||||
+9
-15
@@ -176,7 +176,7 @@ export const MinimizedOrgSidebar = () => {
|
||||
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div>
|
||||
<div className="flex items-center hover:bg-mineshaft-700">
|
||||
<DropdownMenu open={openOrg} onOpenChange={setOpenOrg} modal>
|
||||
<DropdownMenu open={openOrg} onOpenChange={setOpenOrg} modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
onMouseEnter={() => setOpenOrg(true)}
|
||||
onMouseLeave={() => setOpenOrg(false)}
|
||||
@@ -329,7 +329,7 @@ export const MinimizedOrgSidebar = () => {
|
||||
)}
|
||||
</Link>
|
||||
<div className="my-1 w-full bg-mineshaft-500" style={{ height: "1px" }} />
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
@@ -370,17 +370,11 @@ export const MinimizedOrgSidebar = () => {
|
||||
Gateways
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||
window.location.origin.includes("https://eu.infisical.com") ||
|
||||
window.location.origin.includes("https://gamma.infisical.com")) && (
|
||||
<Link to="/organization/billing">
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}
|
||||
>
|
||||
Usage & Billing
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/organization/billing">
|
||||
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}>
|
||||
Usage & Billing
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link to="/organization/audit-logs">
|
||||
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faBook} />}>
|
||||
Audit Logs
|
||||
@@ -402,7 +396,7 @@ export const MinimizedOrgSidebar = () => {
|
||||
: "mb-4"
|
||||
} flex w-full cursor-default flex-col items-center px-1 text-sm text-mineshaft-400`}
|
||||
>
|
||||
<DropdownMenu open={openSupport} onOpenChange={setOpenSupport}>
|
||||
<DropdownMenu open={openSupport} onOpenChange={setOpenSupport} modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
onMouseEnter={() => setOpenSupport(true)}
|
||||
onMouseLeave={() => setOpenSupport(false)}
|
||||
@@ -485,7 +479,7 @@ export const MinimizedOrgSidebar = () => {
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<DropdownMenu open={openUser} onOpenChange={setOpenUser}>
|
||||
<DropdownMenu open={openUser} onOpenChange={setOpenUser} modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
onMouseEnter={() => setOpenUser(true)}
|
||||
onMouseLeave={() => setOpenUser(false)}
|
||||
|
||||
+28
-5
@@ -22,14 +22,37 @@ import "./translation";
|
||||
// have a look at the Quick start guide
|
||||
// for passing in lng and translations on init/
|
||||
|
||||
// https://vite.dev/guide/build#load-error-handling
|
||||
window.addEventListener("vite:preloadError", () => {
|
||||
window.location.reload(); // for example, refresh the page
|
||||
});
|
||||
|
||||
// Create a new router instance
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
window.addEventListener("vite:preloadError", async (event) => {
|
||||
event.preventDefault();
|
||||
// Get current count from session storage or initialize to 0
|
||||
const reloadCount = parseInt(sessionStorage.getItem("vitePreloadErrorCount") || "0", 10);
|
||||
|
||||
// Check if we've already tried 3 times
|
||||
if (reloadCount >= 2) {
|
||||
console.warn("Vite preload has failed multiple times. Stopping automatic reload.");
|
||||
// Optionally show a user-facing message here
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if ("caches" in window) {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map((key) => caches.delete(key)));
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.error(cleanupError);
|
||||
}
|
||||
//
|
||||
// Increment and save the counter
|
||||
sessionStorage.setItem("vitePreloadErrorCount", (reloadCount + 1).toString());
|
||||
|
||||
console.log(`Reloading page (attempt ${reloadCount + 1} of 2)...`);
|
||||
window.location.reload(); // for example, refresh the page
|
||||
});
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: { serverConfig: null, queryClient },
|
||||
|
||||
@@ -18,14 +18,6 @@ export const RequestNewInvitePage = () => {
|
||||
<span className="rounded-md bg-primary-500/40 px-1 text-black">Note:</span> If it still
|
||||
doesn't work, please reach out to us at support@infisical.com
|
||||
</p>
|
||||
<div className="">
|
||||
<img
|
||||
src="/images/invitation-expired.svg"
|
||||
height={500}
|
||||
width={800}
|
||||
alt="invitation expired illustration"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -223,15 +223,10 @@ export const SignupInvitePage = () => {
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const stepConfirmEmail = (
|
||||
<div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-4 py-8 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
|
||||
<p className="mb-6 flex justify-center text-center text-4xl font-semibold text-primary-100">
|
||||
<p className="mb-2 flex justify-center text-center text-4xl font-semibold text-primary-100">
|
||||
Confirm your email
|
||||
</p>
|
||||
<img
|
||||
src="/images/dragon-signupinvite.svg"
|
||||
style={{ height: "262px", width: "410px" }}
|
||||
alt="verify email"
|
||||
/>
|
||||
<div className="mx-auto mb-2 mt-10 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||
<div className="mx-auto mb-2 mt-4 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
try {
|
||||
|
||||
+1
-9
@@ -40,15 +40,7 @@ import {
|
||||
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/hooks/api/certificates/enums";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const validateTemplateRegexField = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.regex(/^[a-zA-Z0-9 *@\-\\.\\]+$/, {
|
||||
message:
|
||||
"Invalid pattern: only alphanumeric characters, spaces, *, ., @, -, and \\ are allowed."
|
||||
});
|
||||
const validateTemplateRegexField = z.string().trim().min(1).max(100);
|
||||
|
||||
const schema = z.object({
|
||||
caId: z.string(),
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import { useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { Button, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionGroupActions,
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useOrgPermission
|
||||
} from "@app/context";
|
||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||
|
||||
import { UpgradePrivilegeSystemModal } from "./components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal";
|
||||
import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components";
|
||||
|
||||
export const AccessManagementPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { permission } = useOrgPermission();
|
||||
const { currentOrg } = useOrganization();
|
||||
|
||||
const navigate = useNavigate({
|
||||
from: ROUTE_PATHS.Organization.AccessControlPage.path
|
||||
});
|
||||
@@ -21,6 +34,8 @@ export const AccessManagementPage = () => {
|
||||
structuralSharing: true
|
||||
});
|
||||
|
||||
const [isUpgradePrivilegeSystemModalOpen, setIsUpgradePrivilegeSystemModalOpen] = useState(false);
|
||||
|
||||
const updateSelectedTab = (tab: string) => {
|
||||
navigate({
|
||||
search: { selectedTab: tab }
|
||||
@@ -37,13 +52,16 @@ export const AccessManagementPage = () => {
|
||||
{
|
||||
key: OrgAccessControlTabSections.Groups,
|
||||
label: "Groups",
|
||||
isHidden: permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Groups),
|
||||
isHidden: permission.cannot(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups),
|
||||
component: OrgGroupsTab
|
||||
},
|
||||
{
|
||||
key: OrgAccessControlTabSections.Identities,
|
||||
label: "Identities",
|
||||
isHidden: permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Identity),
|
||||
isHidden: permission.cannot(
|
||||
OrgPermissionIdentityActions.Read,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
component: OrgIdentityTab
|
||||
},
|
||||
{
|
||||
@@ -64,6 +82,30 @@ export const AccessManagementPage = () => {
|
||||
title="Organization Access Control"
|
||||
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
|
||||
/>
|
||||
{!currentOrg.shouldUseNewPrivilegeSystem && (
|
||||
<div className="mb-4 mt-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
|
||||
<div className="mb-1 flex items-center text-sm">
|
||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||
Your organization is using legacy privilege management
|
||||
</div>
|
||||
<p className="mb-2 mt-1 text-sm text-bunker-300">
|
||||
We've developed an improved privilege management system to better serve your
|
||||
security needs. Upgrade to our new permission-based approach that allows you to
|
||||
explicitly designate who can modify specific access levels, rather than relying on hierarchy comparisons.
|
||||
</p>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
className="mt-2 w-fit text-xs"
|
||||
onClick={() => setIsUpgradePrivilegeSystemModalOpen(true)}
|
||||
>
|
||||
Learn More & Upgrade
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<UpgradePrivilegeSystemModal
|
||||
isOpen={isUpgradePrivilegeSystemModalOpen}
|
||||
onOpenChange={setIsUpgradePrivilegeSystemModalOpen}
|
||||
/>
|
||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
{tabSections
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { useDeleteGroup } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -58,7 +58,7 @@ export const OrgGroupsSection = () => {
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Groups</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan I={OrgPermissionGroupActions.Create} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
|
||||
+5
-5
@@ -33,7 +33,7 @@ import {
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
@@ -241,7 +241,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
|
||||
<Td>{slug}</Td>
|
||||
<Td>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionGroupActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
@@ -289,7 +289,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
|
||||
Copy Group ID
|
||||
</DropdownMenuItem>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionGroupActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
@@ -315,7 +315,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionGroupActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
@@ -339,7 +339,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
I={OrgPermissionGroupActions.Delete}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
|
||||
+5
-5
@@ -31,7 +31,7 @@ import { IdentityFormTab } from "./types";
|
||||
const schema = z
|
||||
.object({
|
||||
kubernetesHost: z.string().min(1),
|
||||
tokenReviewerJwt: z.string().min(1),
|
||||
tokenReviewerJwt: z.string().optional(),
|
||||
allowedNames: z.string(),
|
||||
allowedNamespaces: z.string(),
|
||||
allowedAudience: z.string(),
|
||||
@@ -166,7 +166,7 @@ export const IdentityKubernetesAuthForm = ({
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
kubernetesHost,
|
||||
tokenReviewerJwt,
|
||||
tokenReviewerJwt: tokenReviewerJwt || null,
|
||||
allowedNames,
|
||||
allowedNamespaces,
|
||||
allowedAudience,
|
||||
@@ -182,7 +182,7 @@ export const IdentityKubernetesAuthForm = ({
|
||||
organizationId: orgId,
|
||||
identityId,
|
||||
kubernetesHost: kubernetesHost || "",
|
||||
tokenReviewerJwt,
|
||||
tokenReviewerJwt: tokenReviewerJwt || undefined,
|
||||
allowedNames: allowedNames || "",
|
||||
allowedNamespaces: allowedNamespaces || "",
|
||||
allowedAudience: allowedAudience || "",
|
||||
@@ -255,11 +255,11 @@ export const IdentityKubernetesAuthForm = ({
|
||||
name="tokenReviewerJwt"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipClassName="max-w-md"
|
||||
label="Token Reviewer JWT"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="A long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods."
|
||||
isRequired
|
||||
tooltipText="Optional JWT token for accessing Kubernetes TokenReview API. If provided, this long-lived token will be used to validate service account tokens during authentication. If omitted, the client's own JWT will be used instead, which requires the client to have the system:auth-delegator ClusterRole binding."
|
||||
>
|
||||
<Input {...field} placeholder="" type="password" />
|
||||
</FormControl>
|
||||
|
||||
+6
-3
@@ -6,7 +6,7 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
@@ -87,7 +87,10 @@ export const IdentitySection = withPermission(
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Create}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
@@ -143,5 +146,5 @@ export const IdentitySection = withPermission(
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Identity }
|
||||
{ action: OrgPermissionIdentityActions.Read, subject: OrgPermissionSubjects.Identity }
|
||||
);
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@ import {
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import { useGetIdentityMembershipOrgs, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
@@ -207,7 +207,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
@@ -243,7 +243,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
@@ -267,7 +267,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
I={OrgPermissionIdentityActions.Delete}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
|
||||
+18
-1
@@ -23,7 +23,7 @@ import {
|
||||
useGetUserWorkspaces
|
||||
} from "@app/hooks/api";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { OrgInviteLink } from "./OrgInviteLink";
|
||||
@@ -168,6 +168,21 @@ export const AddOrgMemberModal = ({
|
||||
reset();
|
||||
};
|
||||
|
||||
const getGroupHeaderLabel = (type: ProjectType) => {
|
||||
switch (type) {
|
||||
case ProjectType.SecretManager:
|
||||
return "Secrets";
|
||||
case ProjectType.CertificateManager:
|
||||
return "PKI";
|
||||
case ProjectType.KMS:
|
||||
return "KMS";
|
||||
case ProjectType.SSH:
|
||||
return "SSH";
|
||||
default:
|
||||
return "Other";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addMember?.isOpen}
|
||||
@@ -247,6 +262,8 @@ export const AddOrgMemberModal = ({
|
||||
getOptionLabel={(project) => project.name}
|
||||
getOptionValue={(project) => project.id}
|
||||
options={projects}
|
||||
groupBy="type"
|
||||
getGroupHeaderLabel={getGroupHeaderLabel}
|
||||
placeholder="Select projects..."
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, Checkbox, Modal, ModalContent } from "@app/components/v2";
|
||||
import { useOrgPermission } from "@app/context";
|
||||
import { useUpgradePrivilegeSystem } from "@app/hooks/api";
|
||||
|
||||
const formSchema = z.object({
|
||||
isProjectPrivilegesUpdated: z.literal(true),
|
||||
isOrgPrivilegesUpdated: z.literal(true),
|
||||
isInfrastructureUpdated: z.literal(true),
|
||||
acknowledgesPermanentChange: z.literal(true)
|
||||
});
|
||||
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
export const UpgradePrivilegeSystemModal = ({ isOpen, onOpenChange }: Props) => {
|
||||
const { membership } = useOrgPermission();
|
||||
const [step, setStep] = useState<"info" | "upgrade">("info");
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
formState: { isSubmitting }
|
||||
} = useForm({ resolver: zodResolver(formSchema) });
|
||||
const { mutateAsync: upgradePrivilegeSystem } = useUpgradePrivilegeSystem();
|
||||
|
||||
const isProjectPrivilegesUpdated = watch("isProjectPrivilegesUpdated");
|
||||
const isOrgPrivilegesUpdated = watch("isOrgPrivilegesUpdated");
|
||||
const isInfrastructureUpdated = watch("isInfrastructureUpdated");
|
||||
const acknowledgesPermanentChange = watch("acknowledgesPermanentChange");
|
||||
const isAllChecksCompleted =
|
||||
isProjectPrivilegesUpdated &&
|
||||
isOrgPrivilegesUpdated &&
|
||||
isInfrastructureUpdated &&
|
||||
acknowledgesPermanentChange;
|
||||
|
||||
const handlePrivilegeSystemUpgrade = async () => {
|
||||
try {
|
||||
await upgradePrivilegeSystem();
|
||||
|
||||
createNotification({
|
||||
text: "Privilege system upgrade completed",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed to upgrade privilege system",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onOpenChange(false);
|
||||
setStep("info");
|
||||
};
|
||||
|
||||
const isAdmin = membership?.role === "admin";
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={handleClose}>
|
||||
<ModalContent title="Privilege Management System Upgrade" className="max-w-2xl">
|
||||
{step === "info" ? (
|
||||
<div className="mb-4">
|
||||
<p className="mb-4 text-sm text-mineshaft-300">
|
||||
We've developed an improved privilege management system that enhances how access
|
||||
controls work in your organization.
|
||||
</p>
|
||||
|
||||
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-4">
|
||||
<div className="mb-3">
|
||||
<div className="mb-3">
|
||||
<p className="mb-3 text-sm text-mineshaft-300">
|
||||
<strong>Current system: </strong>
|
||||
Users with higher privilege levels can modify access for anyone below them. This
|
||||
rigid hierarchy makes it difficult to implement precise access control policies,
|
||||
forcing you to either over-grant permissions or create complex workarounds when
|
||||
specialized roles (like team leads) need to manage their team's access
|
||||
without receiving broader administrative powers.
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
<strong>New system:</strong> Users need explicit permission to modify specific
|
||||
access levels, providing targeted control. After upgrading, you'll need to
|
||||
grant the new 'Grant Privileges' permission. At the organization
|
||||
level, this is available for the{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/internals/permissions/organization-permissions#subject%3A-groups"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-bunker-200 hover:decoration-primary-700"
|
||||
>
|
||||
Group
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/internals/permissions/organization-permissions#subject%3A-identity"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-bunker-200 hover:decoration-primary-700"
|
||||
>
|
||||
Identity
|
||||
</a>{" "}
|
||||
subjects while at the project level, this is available for the{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/internals/permissions/project-permissions#subject%3A-member"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-bunker-200 hover:decoration-primary-700"
|
||||
>
|
||||
Member,
|
||||
</a>{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/internals/permissions/project-permissions#subject%3A-groups"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-bunker-200 hover:decoration-primary-700"
|
||||
>
|
||||
Group,
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/internals/permissions/project-permissions#subject%3A-identity"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-bunker-200 hover:decoration-primary-700"
|
||||
>
|
||||
Identity
|
||||
</a>{" "}
|
||||
subjects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
onClick={() => setStep("upgrade")}
|
||||
variant="solid"
|
||||
colorSchema="primary"
|
||||
size="md"
|
||||
className="w-[120px] bg-primary hover:bg-primary-600"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-4">
|
||||
<p className="mb-4 text-sm text-mineshaft-300">
|
||||
Your existing access control workflows will continue to function. However, actions
|
||||
that involve modifying privileges or permissions will now use the new permission-based
|
||||
system, requiring users to have explicit permission to modify given resource.
|
||||
</p>
|
||||
|
||||
<p className="mb-4 text-sm text-mineshaft-300">
|
||||
This upgrade affects operations like updating roles, managing group memberships, and
|
||||
modifying privileges across your organization and projects.
|
||||
</p>
|
||||
|
||||
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-4">
|
||||
<p className="mb-4 text-sm text-mineshaft-300">
|
||||
Once upgraded, your organization <span className="font-bold">cannot</span> revert to
|
||||
the legacy privilege system. Please ensure you've completed all preparations
|
||||
before proceeding.
|
||||
</p>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="mb-3 text-sm font-medium">Required preparation checklist:</p>
|
||||
|
||||
<div className="flex flex-col space-y-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="isProjectPrivilegesUpdated"
|
||||
defaultValue={false}
|
||||
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
|
||||
<Checkbox
|
||||
containerClassName="items-start"
|
||||
className="mt-0.5 items-start"
|
||||
id="is-project-privileges-updated"
|
||||
indicatorClassName="flex h-full w-full items-center justify-center"
|
||||
allowMultilineLabel
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
onBlur={onBlur}
|
||||
isError={Boolean(error?.message)}
|
||||
>
|
||||
I have reviewed project-level privileges and updated them if necessary
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="isOrgPrivilegesUpdated"
|
||||
defaultValue={false}
|
||||
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
|
||||
<Checkbox
|
||||
containerClassName="items-start"
|
||||
className="mt-0.5 items-start"
|
||||
id="is-org-privileges-updated"
|
||||
indicatorClassName="flex h-full w-full items-center justify-center"
|
||||
allowMultilineLabel
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
onBlur={onBlur}
|
||||
isError={Boolean(error?.message)}
|
||||
>
|
||||
I have reviewed organization-level privileges and updated them if necessary
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="isInfrastructureUpdated"
|
||||
defaultValue={false}
|
||||
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
|
||||
<Checkbox
|
||||
containerClassName="items-start"
|
||||
className="mt-0.5 items-start"
|
||||
id="is-infrastructure-updated"
|
||||
indicatorClassName="flex h-full w-full items-center justify-center"
|
||||
allowMultilineLabel
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
onBlur={onBlur}
|
||||
isError={Boolean(error?.message)}
|
||||
>
|
||||
I have checked Terraform configurations and API integrations for
|
||||
compatibility with the new system
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="acknowledgesPermanentChange"
|
||||
defaultValue={false}
|
||||
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
|
||||
<Checkbox
|
||||
containerClassName="items-start"
|
||||
className="mt-0.5 items-start"
|
||||
id="acknowledges-permanent-change"
|
||||
indicatorClassName="flex h-full w-full items-center justify-center"
|
||||
allowMultilineLabel
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
onBlur={onBlur}
|
||||
isError={Boolean(error?.message)}
|
||||
>
|
||||
I understand that this upgrade is permanent and cannot be reversed
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(handlePrivilegeSystemUpgrade)}>
|
||||
<div className="mt-6 flex items-center justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStep("info")}
|
||||
className="w-[120px] text-sm text-mineshaft-300 hover:text-mineshaft-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorSchema="primary"
|
||||
size="md"
|
||||
className="w-[120px] bg-primary hover:bg-primary-600"
|
||||
isDisabled={!isAllChecksCompleted || !isAdmin}
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -18,7 +18,7 @@ export const AuditLogsPage = () => {
|
||||
title="Audit logs"
|
||||
description="Audit logs for security and compliance teams to monitor information access."
|
||||
/>
|
||||
<LogsSection filterClassName="static py-2" showFilters isOrgAuditLogs showActorColumn />
|
||||
<LogsSection filterClassName="static py-2" showFilters />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/en
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { LogsFilter } from "./LogsFilter";
|
||||
import { LogsTable, TAuditLogTableHeader } from "./LogsTable";
|
||||
import { LogsTable } from "./LogsTable";
|
||||
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
||||
|
||||
type Props = {
|
||||
@@ -25,22 +25,11 @@ type Props = {
|
||||
|
||||
showFilters?: boolean;
|
||||
filterClassName?: string;
|
||||
isOrgAuditLogs?: boolean;
|
||||
showActorColumn?: boolean;
|
||||
remappedHeaders?: Partial<Record<TAuditLogTableHeader, string>>;
|
||||
refetchInterval?: number;
|
||||
};
|
||||
|
||||
export const LogsSection = withPermission(
|
||||
({
|
||||
presets,
|
||||
filterClassName,
|
||||
remappedHeaders,
|
||||
isOrgAuditLogs,
|
||||
showActorColumn,
|
||||
refetchInterval,
|
||||
showFilters
|
||||
}: Props) => {
|
||||
({ presets, filterClassName, refetchInterval, showFilters }: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
@@ -90,9 +79,6 @@ export const LogsSection = withPermission(
|
||||
)}
|
||||
<LogsTable
|
||||
refetchInterval={refetchInterval}
|
||||
remappedHeaders={remappedHeaders}
|
||||
isOrgAuditLogs={isOrgAuditLogs}
|
||||
showActorColumn={!!showActorColumn}
|
||||
filter={{
|
||||
secretPath: debouncedSecretPath || undefined,
|
||||
eventMetadata: presets?.eventMetadata,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Fragment } from "react";
|
||||
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faFile, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useGetAuditLogs } from "@app/hooks/api";
|
||||
@@ -19,32 +21,13 @@ import { TGetAuditLogsFilter } from "@app/hooks/api/auditLogs/types";
|
||||
import { LogsTableRow } from "./LogsTableRow";
|
||||
|
||||
type Props = {
|
||||
isOrgAuditLogs?: boolean;
|
||||
showActorColumn: boolean;
|
||||
filter?: TGetAuditLogsFilter;
|
||||
remappedHeaders?: Partial<Record<TAuditLogTableHeader, string>>;
|
||||
refetchInterval?: number;
|
||||
};
|
||||
|
||||
const AUDIT_LOG_LIMIT = 15;
|
||||
|
||||
const TABLE_HEADERS = [
|
||||
"Timestamp (MM/DD/YYYY)",
|
||||
"Event",
|
||||
"Project",
|
||||
"Actor",
|
||||
"Source",
|
||||
"Metadata"
|
||||
] as const;
|
||||
export type TAuditLogTableHeader = (typeof TABLE_HEADERS)[number];
|
||||
|
||||
export const LogsTable = ({
|
||||
showActorColumn,
|
||||
isOrgAuditLogs,
|
||||
filter,
|
||||
remappedHeaders,
|
||||
refetchInterval
|
||||
}: Props) => {
|
||||
export const LogsTable = ({ filter, refetchInterval }: Props) => {
|
||||
// Determine the project ID for filtering
|
||||
const filterProjectId =
|
||||
// Use the projectId from the filter if it exists
|
||||
@@ -69,38 +52,37 @@ export const LogsTable = ({
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
{TABLE_HEADERS.map((header, idx) => {
|
||||
if (
|
||||
(header === "Project" && !isOrgAuditLogs) ||
|
||||
(header === "Actor" && !showActorColumn)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Th key={`table-header-${idx + 1}`}>{remappedHeaders?.[header] || header}</Th>
|
||||
);
|
||||
})}
|
||||
<Th className="w-24" />
|
||||
<Th className="w-64">
|
||||
Timestamp
|
||||
<Tooltip
|
||||
className="normal-case"
|
||||
content="Time displayed in your system's time zone."
|
||||
sideOffset={10}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="ml-1" />
|
||||
</Tooltip>
|
||||
</Th>
|
||||
<Th>Event</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{!isPending &&
|
||||
data?.pages?.map((group, i) => (
|
||||
<Fragment key={`audit-log-fragment-${i + 1}`}>
|
||||
{group.map((auditLog) => (
|
||||
{group.map((auditLog, index) => (
|
||||
<LogsTableRow
|
||||
showActorColumn={showActorColumn}
|
||||
isOrgAuditLogs={isOrgAuditLogs}
|
||||
rowNumber={index + i * AUDIT_LOG_LIMIT + 1}
|
||||
auditLog={auditLog}
|
||||
key={`audit-log-${auditLog.id}`}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
{isPending && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
||||
{isPending && <TableSkeleton innerKey="logs-table" columns={3} key="logs-loading" />}
|
||||
{isEmpty && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Td colSpan={3}>
|
||||
<EmptyState title="No audit logs on file" icon={faFile} />
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -1,128 +1,76 @@
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
||||
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { Actor, AuditLog } from "@app/hooks/api/auditLogs/types";
|
||||
import { Td, Tr } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { AuditLog } from "@app/hooks/api/auditLogs/types";
|
||||
|
||||
type Props = {
|
||||
auditLog: AuditLog;
|
||||
isOrgAuditLogs?: boolean;
|
||||
showActorColumn: boolean;
|
||||
rowNumber: number;
|
||||
};
|
||||
|
||||
export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Props) => {
|
||||
const renderActor = (actor: Actor) => {
|
||||
if (!actor) {
|
||||
return <Td />;
|
||||
}
|
||||
|
||||
switch (actor.type) {
|
||||
case ActorType.USER:
|
||||
return (
|
||||
<Td>
|
||||
<p>{actor.metadata.email}</p>
|
||||
<p>User</p>
|
||||
</Td>
|
||||
);
|
||||
case ActorType.SERVICE:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`${actor.metadata.name}`}</p>
|
||||
<p>Service token</p>
|
||||
</Td>
|
||||
);
|
||||
case ActorType.IDENTITY:
|
||||
return (
|
||||
<Td>
|
||||
<p>{`${actor.metadata.name}`}</p>
|
||||
<p>Machine Identity</p>
|
||||
</Td>
|
||||
);
|
||||
case ActorType.PLATFORM:
|
||||
return (
|
||||
<Td>
|
||||
<p>Platform</p>
|
||||
</Td>
|
||||
);
|
||||
case ActorType.KMIP_CLIENT:
|
||||
return (
|
||||
<Td>
|
||||
<p>{actor.metadata.name}</p>
|
||||
<p>KMIP Client</p>
|
||||
</Td>
|
||||
);
|
||||
case ActorType.UNKNOWN_USER:
|
||||
return (
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
<p>Unknown User</p>
|
||||
<Tooltip content="This action was performed by a user who was not authenticated at the time.">
|
||||
<FontAwesomeIcon className="text-mineshaft-400" icon={faQuestionCircle} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
);
|
||||
default:
|
||||
return <Td />;
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateToFormat: string) => {
|
||||
const date = new Date(dateToFormat);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
|
||||
let hours = date.getHours();
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
|
||||
// convert from 24h to 12h format
|
||||
const period = hours >= 12 ? "PM" : "AM";
|
||||
hours %= 12;
|
||||
hours = hours || 12; // the hour '0' should be '12'
|
||||
|
||||
const formattedDate = `${month}-${day}-${year} at ${hours}:${minutes} ${period}`;
|
||||
return formattedDate;
|
||||
};
|
||||
|
||||
const renderSource = () => {
|
||||
const { event, actor } = auditLog;
|
||||
|
||||
if (event.type === EventType.INTEGRATION_SYNCED) {
|
||||
if (actor.type === ActorType.USER) {
|
||||
return (
|
||||
<Td>
|
||||
<p>Manually triggered by {actor.metadata.email}</p>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
|
||||
// Platform / automatic syncs
|
||||
return (
|
||||
<Td>
|
||||
<p>Automatically synced by Infisical</p>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Td>
|
||||
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
||||
<p>{auditLog.ipAddress}</p>
|
||||
</Td>
|
||||
);
|
||||
};
|
||||
type TagProps = {
|
||||
label: string;
|
||||
value?: string;
|
||||
};
|
||||
const Tag = ({ label, value }: TagProps) => {
|
||||
if (!value) return null;
|
||||
|
||||
return (
|
||||
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
||||
<Td>{formatDate(auditLog.createdAt)}</Td>
|
||||
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
||||
{isOrgAuditLogs && <Td>{auditLog?.projectName ?? auditLog?.projectId ?? "N/A"}</Td>}
|
||||
{showActorColumn && renderActor(auditLog.actor)}
|
||||
{renderSource()}
|
||||
<Td className="max-w-xs break-all">{JSON.stringify(auditLog.event.metadata || {})}</Td>
|
||||
</Tr>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<div className="rounded bg-mineshaft-600 p-0.5 pl-1 font-mono">{label}:</div>
|
||||
<div>{value}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogsTableRow = ({ auditLog, rowNumber }: Props) => {
|
||||
const [isOpen, setIsOpen] = useToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr
|
||||
className="h-10 cursor-pointer border-x-0 border-b border-t-0 hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setIsOpen.toggle()}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") setIsOpen.toggle();
|
||||
}}
|
||||
isHoverable
|
||||
>
|
||||
<Td className="flex items-center gap-2 pr-0 align-top">
|
||||
<FontAwesomeIcon icon={isOpen ? faCaretDown : faCaretRight} />
|
||||
{rowNumber}
|
||||
</Td>
|
||||
<Td className="align-top">
|
||||
{format(new Date(auditLog.createdAt), "MMM do yyyy, hh:mm a")}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-wrap gap-4 text-sm">
|
||||
<Tag label="event" value={auditLog.event.type} />
|
||||
<Tag label="actor" value={auditLog.actor.type} />
|
||||
{auditLog.actor.type === ActorType.USER && (
|
||||
<Tag label="user_email" value={auditLog.actor.metadata.email} />
|
||||
)}
|
||||
{auditLog.actor.type === ActorType.IDENTITY && (
|
||||
<Tag label="identity_name" value={auditLog.actor.metadata.name} />
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isOpen && (
|
||||
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
||||
<Td colSpan={3} className="px-3 py-2">
|
||||
<div className="thin-scrollbar my-1 max-h-96 overflow-auto whitespace-pre-wrap rounded border border-mineshaft-600 bg-bunker-800 p-2 font-mono leading-6">
|
||||
{JSON.stringify(auditLog, null, 4)}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+42
-18
@@ -9,6 +9,7 @@ import {
|
||||
useOrganization,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
useCreateCustomerPortalSession,
|
||||
useGetOrgPlanBillingInfo,
|
||||
@@ -47,6 +48,9 @@ export const PreviewSection = () => {
|
||||
};
|
||||
|
||||
function formatPlanSlug(slug: string) {
|
||||
if (!slug) {
|
||||
return "-";
|
||||
}
|
||||
return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " ");
|
||||
}
|
||||
|
||||
@@ -54,6 +58,11 @@ export const PreviewSection = () => {
|
||||
try {
|
||||
if (!subscription || !currentOrg) return;
|
||||
|
||||
if (!isInfisicalCloud()) {
|
||||
window.open("https://infisical.com/pricing", "_blank");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!subscription.has_used_trial) {
|
||||
// direct user to start pro trial
|
||||
const url = await getOrgTrialUrl.mutateAsync({
|
||||
@@ -71,6 +80,19 @@ export const PreviewSection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getUpgradePlanLabel = () => {
|
||||
if (!isInfisicalCloud()) {
|
||||
return (
|
||||
<div>
|
||||
Go to Pricing
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="mb-[0.06rem] ml-1 text-xs" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return !subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan";
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{subscription &&
|
||||
@@ -97,7 +119,7 @@ export const PreviewSection = () => {
|
||||
color="mineshaft"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
|
||||
{getUpgradePlanLabel()}
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
@@ -133,22 +155,24 @@ export const PreviewSection = () => {
|
||||
subscription.status === "trialing" ? "(Trial)" : ""
|
||||
}`}
|
||||
</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||
{(isAllowed) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (!currentOrg?.id) return;
|
||||
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg.id);
|
||||
window.location.href = url;
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
className="text-primary"
|
||||
>
|
||||
Manage plan →
|
||||
</button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{isInfisicalCloud() && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||
{(isAllowed) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (!currentOrg?.id) return;
|
||||
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg.id);
|
||||
window.location.href = url;
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
className="text-primary"
|
||||
>
|
||||
Manage plan →
|
||||
</button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<p className="mb-2 text-gray-400">Price</p>
|
||||
@@ -161,7 +185,7 @@ export const PreviewSection = () => {
|
||||
<div className="flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<p className="mb-2 text-gray-400">Subscription renews on</p>
|
||||
<p className="mb-8 text-2xl font-semibold text-mineshaft-50">
|
||||
{formatDate(data.currentPeriodEnd)}
|
||||
{data.currentPeriodEnd ? formatDate(data.currentPeriodEnd) : "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+19
-10
@@ -1,5 +1,6 @@
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
import { BillingCloudTab } from "../BillingCloudTab";
|
||||
@@ -16,25 +17,33 @@ const tabs = [
|
||||
|
||||
export const BillingTabGroup = withPermission(
|
||||
() => {
|
||||
const tabsFiltered = isInfisicalCloud()
|
||||
? tabs
|
||||
: [{ name: "Infisical Self-Hosted", key: "tab-infisical-cloud" }];
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
{tabsFiltered.map((tab) => (
|
||||
<Tab value={tab.key}>{tab.name}</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanel value={tabs[0].key}>
|
||||
<BillingCloudTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[1].key}>
|
||||
<BillingSelfHostedTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[2].key}>
|
||||
<BillingReceiptsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[3].key}>
|
||||
<BillingDetailsTab />
|
||||
</TabPanel>
|
||||
{isInfisicalCloud() && (
|
||||
<>
|
||||
<TabPanel value={tabs[1].key}>
|
||||
<BillingSelfHostedTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[2].key}>
|
||||
<BillingReceiptsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[3].key}>
|
||||
<BillingDetailsTab />
|
||||
</TabPanel>
|
||||
</>
|
||||
)}
|
||||
</Tabs>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { useDeleteGroup } from "@app/hooks/api";
|
||||
import { useGetGroupById } from "@app/hooks/api/groups/queries";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
@@ -93,7 +93,10 @@ const Page = () => {
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="p-1">
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionGroupActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
@@ -113,7 +116,10 @@ const Page = () => {
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Delete} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionGroupActions.Delete}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
@@ -179,7 +185,7 @@ export const GroupDetailsByIDPage = () => {
|
||||
</Helmet>
|
||||
<OrgPermissionCan
|
||||
passThrough={false}
|
||||
I={OrgPermissionActions.Read}
|
||||
I={OrgPermissionGroupActions.Read}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
<Page />
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ import {
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { useDebounce, useResetPageHelper } from "@app/hooks";
|
||||
import { useAddUserToGroup, useListGroupUsers } from "@app/hooks/api";
|
||||
import { EFilterReturnedUsers } from "@app/hooks/api/groups/types";
|
||||
@@ -124,7 +124,7 @@ export const AddGroupMembersModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
</Td>
|
||||
<Td className="flex justify-end">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionGroupActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { IconButton, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { CopyButton } from "@app/components/v2/CopyButton";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { useGetGroupById } from "@app/hooks/api/";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -22,7 +22,7 @@ export const GroupDetailsSection = ({ groupId, handlePopUpOpen }: Props) => {
|
||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">Group Details</h3>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan I={OrgPermissionGroupActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Tooltip content="Edit Group">
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { DeleteActionModal, IconButton, Tooltip } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useOidcManageGroupMembershipsEnabled, useRemoveUserFromGroup } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -54,7 +54,7 @@ export const GroupMembersSection = ({ groupId, groupSlug }: Props) => {
|
||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">Group Members</h3>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan I={OrgPermissionGroupActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Tooltip
|
||||
className="text-center"
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import { useListGroupUsers, useOidcManageGroupMembershipsEnabled } from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
@@ -177,7 +177,7 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props
|
||||
/>
|
||||
)}
|
||||
{!groupMemberships?.users.length && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan I={OrgPermissionGroupActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Tooltip
|
||||
className="text-center"
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useOidcManageGroupMembershipsEnabled } from "@app/hooks/api";
|
||||
import { TGroupUser } from "@app/hooks/api/groups/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
@@ -39,7 +39,7 @@ export const GroupMembershipRow = ({
|
||||
</Tooltip>
|
||||
</Td>
|
||||
<Td className="justify-end">
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
<OrgPermissionCan I={OrgPermissionGroupActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Tooltip
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { DeleteActionModal, PageHeader } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useDeleteIdentity, useGetIdentityById } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal";
|
||||
@@ -132,7 +132,7 @@ export const IdentityDetailsByIDPage = () => {
|
||||
</Helmet>
|
||||
<OrgPermissionCan
|
||||
passThrough={false}
|
||||
I={OrgPermissionActions.Read}
|
||||
I={OrgPermissionIdentityActions.Read}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
<Page />
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -47,7 +47,7 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
|
||||
{!Object.values(IdentityAuthMethod).every((method) =>
|
||||
data.identity.authMethods.includes(method)
|
||||
) && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { format } from "date-fns";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, IconButton, Tooltip } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import {
|
||||
useGetIdentityById,
|
||||
@@ -118,7 +118,7 @@ export const IdentityClientSecrets = ({ identityId, handlePopUpOpen }: Props) =>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Button
|
||||
|
||||
+9
-3
@@ -20,7 +20,7 @@ import {
|
||||
Tag,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import { useGetIdentityById } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
@@ -54,7 +54,10 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="min-w-[120px]" align="end">
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
@@ -75,7 +78,10 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Delete} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Delete}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
|
||||
+4
-4
@@ -20,7 +20,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useRevokeIdentityTokenAuthToken } from "@app/hooks/api";
|
||||
import { IdentityAccessToken } from "@app/hooks/api/identities/types";
|
||||
@@ -79,7 +79,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
|
||||
<div className="col-span-2 mt-3">
|
||||
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
|
||||
<span className="text-bunker-300">Access Tokens</span>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
size="xs"
|
||||
@@ -144,7 +144,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
@@ -169,7 +169,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
|
||||
</OrgPermissionCan>
|
||||
{!isAccessTokenRevoked && (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useRevokeIdentityUniversalAuthClientSecret } from "@app/hooks/api";
|
||||
import { ClientSecretData } from "@app/hooks/api/identities/types";
|
||||
@@ -68,7 +68,7 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
|
||||
<div className="col-span-2">
|
||||
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
|
||||
<span className="text-bunker-300">Client Secrets</span>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
@@ -129,7 +129,7 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
|
||||
</Td>
|
||||
<Td>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
|
||||
+9
-3
@@ -10,7 +10,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
@@ -36,7 +36,10 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="min-w-[120px]" align="end">
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
@@ -47,7 +50,10 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Delete}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
|
||||
+20
-14
@@ -70,20 +70,26 @@ export const ViewIdentityKubernetesAuthContent = ({
|
||||
{data.kubernetesHost}
|
||||
</IdentityAuthFieldDisplay>
|
||||
<IdentityAuthFieldDisplay className="col-span-2" label="Token Reviewer JWT">
|
||||
<Tooltip
|
||||
side="right"
|
||||
className="max-w-xl p-2"
|
||||
content={
|
||||
<p className="break-words rounded bg-mineshaft-600 p-2">{data.tokenReviewerJwt}</p>
|
||||
}
|
||||
>
|
||||
<div className="w-min">
|
||||
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
<span>Reveal</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{data.tokenReviewerJwt ? (
|
||||
<Tooltip
|
||||
side="right"
|
||||
className="max-w-xl p-2"
|
||||
content={
|
||||
<p className="break-words rounded bg-mineshaft-600 p-2">
|
||||
{data.tokenReviewerJwt || "Not provided"}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<div className="w-min">
|
||||
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
<span>Reveal</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p className="text-base italic leading-4 text-bunker-400">Not set</p>
|
||||
)}
|
||||
</IdentityAuthFieldDisplay>
|
||||
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Service Account Names">
|
||||
{data.allowedNames
|
||||
|
||||
+30
-2
@@ -5,6 +5,8 @@ import { OrgPermissionSubjects } from "@app/context";
|
||||
import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionAppConnectionActions,
|
||||
OrgPermissionGroupActions,
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionKmipActions,
|
||||
OrgPermissionSecretShareAction
|
||||
} from "@app/context/OrgPermissionContext/types";
|
||||
@@ -36,6 +38,32 @@ const kmipPermissionSchema = z
|
||||
})
|
||||
.optional();
|
||||
|
||||
const identityPermissionSchema = z
|
||||
.object({
|
||||
[OrgPermissionIdentityActions.Read]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.Edit]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.Delete]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.Create]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.GrantPrivileges]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.RevokeAuth]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.CreateToken]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.GetToken]: z.boolean().optional(),
|
||||
[OrgPermissionIdentityActions.DeleteToken]: z.boolean().optional()
|
||||
})
|
||||
.optional();
|
||||
|
||||
const groupPermissionSchema = z
|
||||
.object({
|
||||
[OrgPermissionGroupActions.Read]: z.boolean().optional(),
|
||||
[OrgPermissionGroupActions.Create]: z.boolean().optional(),
|
||||
[OrgPermissionGroupActions.Edit]: z.boolean().optional(),
|
||||
[OrgPermissionGroupActions.Delete]: z.boolean().optional(),
|
||||
[OrgPermissionGroupActions.GrantPrivileges]: z.boolean().optional(),
|
||||
[OrgPermissionGroupActions.AddMembers]: z.boolean().optional(),
|
||||
[OrgPermissionGroupActions.RemoveMembers]: z.boolean().optional()
|
||||
})
|
||||
.optional();
|
||||
|
||||
const orgGatewayPermissionSchema = z
|
||||
.object({
|
||||
[OrgGatewayPermissionActions.ListGateways]: z.boolean().optional(),
|
||||
@@ -74,7 +102,7 @@ export const formSchema = z.object({
|
||||
|
||||
"audit-logs": generalPermissionSchema,
|
||||
member: generalPermissionSchema,
|
||||
groups: generalPermissionSchema,
|
||||
groups: groupPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
settings: generalPermissionSchema,
|
||||
"service-account": generalPermissionSchema,
|
||||
@@ -84,7 +112,7 @@ export const formSchema = z.object({
|
||||
scim: generalPermissionSchema,
|
||||
ldap: generalPermissionSchema,
|
||||
billing: generalPermissionSchema,
|
||||
identity: generalPermissionSchema,
|
||||
identity: identityPermissionSchema,
|
||||
"organization-admin-console": adminConsolePermissionSchmea,
|
||||
[OrgPermissionSubjects.Kms]: generalPermissionSchema,
|
||||
[OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema,
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
||||
import { OrgPermissionGroupActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
import { TFormSchema } from "../OrgRoleModifySection.utils";
|
||||
|
||||
const PERMISSION_ACTIONS = [
|
||||
{ action: OrgPermissionGroupActions.Read, label: "Read Groups" },
|
||||
{ action: OrgPermissionGroupActions.Create, label: "Create Groups" },
|
||||
{ action: OrgPermissionGroupActions.Edit, label: "Edit Groups" },
|
||||
{ action: OrgPermissionGroupActions.Delete, label: "Delete Groups" },
|
||||
{ action: OrgPermissionGroupActions.GrantPrivileges, label: "Grant Privileges" },
|
||||
{ action: OrgPermissionGroupActions.AddMembers, label: "Add Members" },
|
||||
{ action: OrgPermissionGroupActions.RemoveMembers, label: "Remove Members" }
|
||||
] as const;
|
||||
|
||||
type Props = {
|
||||
isEditable: boolean;
|
||||
setValue: UseFormSetValue<TFormSchema>;
|
||||
control: Control<TFormSchema>;
|
||||
};
|
||||
|
||||
enum Permission {
|
||||
NoAccess = "no-access",
|
||||
ReadOnly = "read-only",
|
||||
FullAccess = "full-access",
|
||||
Custom = "custom"
|
||||
}
|
||||
|
||||
export const OrgPermissionGroupRow = ({ isEditable, control, setValue }: Props) => {
|
||||
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||
const [isCustom, setIsCustom] = useToggle();
|
||||
|
||||
const rule = useWatch({
|
||||
control,
|
||||
name: "permissions.groups"
|
||||
});
|
||||
|
||||
const selectedPermissionCategory = useMemo(() => {
|
||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||
const totalActions = PERMISSION_ACTIONS.length;
|
||||
const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||
|
||||
if (isCustom) return Permission.Custom;
|
||||
if (score === 0) return Permission.NoAccess;
|
||||
if (score === totalActions) return Permission.FullAccess;
|
||||
if (score === 1 && rule?.read) return Permission.ReadOnly;
|
||||
|
||||
return Permission.Custom;
|
||||
}, [rule, isCustom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||
else setIsCustom.off();
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
const isRowCustom = selectedPermissionCategory === Permission.Custom;
|
||||
if (isRowCustom) {
|
||||
setIsRowExpanded.on();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if (val === Permission.Custom) {
|
||||
setIsRowExpanded.on();
|
||||
setIsCustom.on();
|
||||
return;
|
||||
}
|
||||
setIsCustom.off();
|
||||
|
||||
switch (val) {
|
||||
case Permission.NoAccess:
|
||||
setValue(
|
||||
"permissions.groups",
|
||||
{
|
||||
[OrgPermissionGroupActions.Read]: false,
|
||||
[OrgPermissionGroupActions.Create]: false,
|
||||
[OrgPermissionGroupActions.Edit]: false,
|
||||
[OrgPermissionGroupActions.Delete]: false,
|
||||
[OrgPermissionGroupActions.GrantPrivileges]: false,
|
||||
[OrgPermissionGroupActions.AddMembers]: false,
|
||||
[OrgPermissionGroupActions.RemoveMembers]: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
case Permission.FullAccess:
|
||||
setValue(
|
||||
"permissions.groups",
|
||||
{
|
||||
[OrgPermissionGroupActions.Read]: true,
|
||||
[OrgPermissionGroupActions.Create]: true,
|
||||
[OrgPermissionGroupActions.Edit]: true,
|
||||
[OrgPermissionGroupActions.Delete]: true,
|
||||
[OrgPermissionGroupActions.GrantPrivileges]: true,
|
||||
[OrgPermissionGroupActions.AddMembers]: true,
|
||||
[OrgPermissionGroupActions.RemoveMembers]: true
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
case Permission.ReadOnly:
|
||||
setValue(
|
||||
"permissions.groups",
|
||||
{
|
||||
[OrgPermissionGroupActions.Read]: true,
|
||||
[OrgPermissionGroupActions.Edit]: false,
|
||||
[OrgPermissionGroupActions.Create]: false,
|
||||
[OrgPermissionGroupActions.Delete]: false,
|
||||
[OrgPermissionGroupActions.GrantPrivileges]: false,
|
||||
[OrgPermissionGroupActions.AddMembers]: false,
|
||||
[OrgPermissionGroupActions.RemoveMembers]: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
default:
|
||||
setValue(
|
||||
"permissions.groups",
|
||||
{
|
||||
[OrgPermissionGroupActions.Read]: false,
|
||||
[OrgPermissionGroupActions.Edit]: false,
|
||||
[OrgPermissionGroupActions.Create]: false,
|
||||
[OrgPermissionGroupActions.Delete]: false,
|
||||
[OrgPermissionGroupActions.GrantPrivileges]: false,
|
||||
[OrgPermissionGroupActions.AddMembers]: false,
|
||||
[OrgPermissionGroupActions.RemoveMembers]: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Group Management</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
</Select>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`permissions.groups.${action}`}
|
||||
key={`permissions.groups.${action}`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={(e) => {
|
||||
if (!isEditable) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to update default role"
|
||||
});
|
||||
return;
|
||||
}
|
||||
field.onChange(e);
|
||||
}}
|
||||
id={`permissions.groups.${action}`}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
||||
import { OrgPermissionIdentityActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
import { TFormSchema } from "../OrgRoleModifySection.utils";
|
||||
|
||||
const PERMISSION_ACTIONS = [
|
||||
{ action: OrgPermissionIdentityActions.Read, label: "Read Identities" },
|
||||
{ action: OrgPermissionIdentityActions.Create, label: "Create Identities" },
|
||||
{ action: OrgPermissionIdentityActions.Edit, label: "Edit Identities" },
|
||||
{ action: OrgPermissionIdentityActions.Delete, label: "Delete Identities" },
|
||||
{ action: OrgPermissionIdentityActions.GrantPrivileges, label: "Grant Privileges" },
|
||||
{ action: OrgPermissionIdentityActions.RevokeAuth, label: "Revoke Auth" },
|
||||
{ action: OrgPermissionIdentityActions.CreateToken, label: "Create Token" },
|
||||
{ action: OrgPermissionIdentityActions.GetToken, label: "Get Token" },
|
||||
{ action: OrgPermissionIdentityActions.DeleteToken, label: "Delete Token" }
|
||||
] as const;
|
||||
|
||||
type Props = {
|
||||
isEditable: boolean;
|
||||
setValue: UseFormSetValue<TFormSchema>;
|
||||
control: Control<TFormSchema>;
|
||||
};
|
||||
|
||||
enum Permission {
|
||||
NoAccess = "no-access",
|
||||
ReadOnly = "read-only",
|
||||
FullAccess = "full-access",
|
||||
Custom = "custom"
|
||||
}
|
||||
|
||||
export const OrgPermissionIdentityRow = ({ isEditable, control, setValue }: Props) => {
|
||||
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||
const [isCustom, setIsCustom] = useToggle();
|
||||
|
||||
const rule = useWatch({
|
||||
control,
|
||||
name: "permissions.identity"
|
||||
});
|
||||
|
||||
const selectedPermissionCategory = useMemo(() => {
|
||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||
const totalActions = PERMISSION_ACTIONS.length;
|
||||
const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||
|
||||
if (isCustom) return Permission.Custom;
|
||||
if (score === 0) return Permission.NoAccess;
|
||||
if (score === totalActions) return Permission.FullAccess;
|
||||
if (score === 1 && rule?.read) return Permission.ReadOnly;
|
||||
|
||||
return Permission.Custom;
|
||||
}, [rule, isCustom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||
else setIsCustom.off();
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
const isRowCustom = selectedPermissionCategory === Permission.Custom;
|
||||
if (isRowCustom) {
|
||||
setIsRowExpanded.on();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
if (val === Permission.Custom) {
|
||||
setIsRowExpanded.on();
|
||||
setIsCustom.on();
|
||||
return;
|
||||
}
|
||||
setIsCustom.off();
|
||||
|
||||
switch (val) {
|
||||
case Permission.NoAccess:
|
||||
setValue(
|
||||
"permissions.identity",
|
||||
{
|
||||
[OrgPermissionIdentityActions.Read]: false,
|
||||
[OrgPermissionIdentityActions.Edit]: false,
|
||||
[OrgPermissionIdentityActions.Create]: false,
|
||||
[OrgPermissionIdentityActions.Delete]: false,
|
||||
[OrgPermissionIdentityActions.GrantPrivileges]: false,
|
||||
[OrgPermissionIdentityActions.RevokeAuth]: false,
|
||||
[OrgPermissionIdentityActions.CreateToken]: false,
|
||||
[OrgPermissionIdentityActions.GetToken]: false,
|
||||
[OrgPermissionIdentityActions.DeleteToken]: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
case Permission.FullAccess:
|
||||
setValue(
|
||||
"permissions.identity",
|
||||
{
|
||||
[OrgPermissionIdentityActions.Read]: true,
|
||||
[OrgPermissionIdentityActions.Edit]: true,
|
||||
[OrgPermissionIdentityActions.Create]: true,
|
||||
[OrgPermissionIdentityActions.Delete]: true,
|
||||
[OrgPermissionIdentityActions.GrantPrivileges]: true,
|
||||
[OrgPermissionIdentityActions.RevokeAuth]: true,
|
||||
[OrgPermissionIdentityActions.CreateToken]: true,
|
||||
[OrgPermissionIdentityActions.GetToken]: true,
|
||||
[OrgPermissionIdentityActions.DeleteToken]: true
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
case Permission.ReadOnly:
|
||||
setValue(
|
||||
"permissions.identity",
|
||||
{
|
||||
[OrgPermissionIdentityActions.Read]: true,
|
||||
[OrgPermissionIdentityActions.Edit]: false,
|
||||
[OrgPermissionIdentityActions.Create]: false,
|
||||
[OrgPermissionIdentityActions.Delete]: false,
|
||||
[OrgPermissionIdentityActions.GrantPrivileges]: false,
|
||||
[OrgPermissionIdentityActions.RevokeAuth]: false,
|
||||
[OrgPermissionIdentityActions.CreateToken]: false,
|
||||
[OrgPermissionIdentityActions.GetToken]: false,
|
||||
[OrgPermissionIdentityActions.DeleteToken]: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
default:
|
||||
setValue(
|
||||
"permissions.identity",
|
||||
{
|
||||
[OrgPermissionIdentityActions.Read]: false,
|
||||
[OrgPermissionIdentityActions.Edit]: false,
|
||||
[OrgPermissionIdentityActions.Create]: false,
|
||||
[OrgPermissionIdentityActions.Delete]: false,
|
||||
[OrgPermissionIdentityActions.GrantPrivileges]: false,
|
||||
[OrgPermissionIdentityActions.RevokeAuth]: false,
|
||||
[OrgPermissionIdentityActions.CreateToken]: false,
|
||||
[OrgPermissionIdentityActions.GetToken]: false,
|
||||
[OrgPermissionIdentityActions.DeleteToken]: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Machine Identity Management</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
</Select>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`permissions.identity.${action}`}
|
||||
key={`permissions.identity.${action}`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={(e) => {
|
||||
if (!isEditable) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to update default role"
|
||||
});
|
||||
return;
|
||||
}
|
||||
field.onChange(e);
|
||||
}}
|
||||
id={`permissions.identity.${action}`}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+12
-8
@@ -15,6 +15,8 @@ import {
|
||||
} from "../OrgRoleModifySection.utils";
|
||||
import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow";
|
||||
import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow";
|
||||
import { OrgPermissionGroupRow } from "./OrgPermissionGroupRow";
|
||||
import { OrgPermissionIdentityRow } from "./OrgPermissionIdentityRow";
|
||||
import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow";
|
||||
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
|
||||
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
|
||||
@@ -25,14 +27,6 @@ const SIMPLE_PERMISSION_OPTIONS = [
|
||||
title: "User Management",
|
||||
formName: "member"
|
||||
},
|
||||
{
|
||||
title: "Group Management",
|
||||
formName: "groups"
|
||||
},
|
||||
{
|
||||
title: "Machine Identity Management",
|
||||
formName: "identity"
|
||||
},
|
||||
{
|
||||
title: "Usage & Billing",
|
||||
formName: "billing"
|
||||
@@ -168,6 +162,16 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<OrgPermissionIdentityRow
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
isEditable={isCustomRole}
|
||||
/>
|
||||
<OrgPermissionGroupRow
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
isEditable={isCustomRole}
|
||||
/>
|
||||
<OrgPermissionAppConnectionRow
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
|
||||
@@ -140,7 +140,7 @@ const Page = withPermission(
|
||||
<DropdownMenuContent align="end" className="p-1">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
a={OrgPermissionSubjects.Member}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
|
||||
+2
-2
@@ -82,7 +82,7 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">User Details</h3>
|
||||
{userId !== membership.user.id && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Member}>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Tooltip content="Edit Membership">
|
||||
@@ -196,7 +196,7 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>
|
||||
(membership.status === "invited" || membership.status === "verified") &&
|
||||
membership.user.email &&
|
||||
serverDetails?.emailConfigured && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Member}>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Button
|
||||
|
||||
-1
@@ -45,7 +45,6 @@ export const UserAuditLogsSection = withPermission(
|
||||
presets={{
|
||||
actorId: orgMembership.user.id
|
||||
}}
|
||||
isOrgAuditLogs
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
|
||||
@@ -16,73 +15,67 @@ import {
|
||||
ServiceTokenTab
|
||||
} from "./components";
|
||||
|
||||
const Page = withProjectPermission(
|
||||
() => {
|
||||
const navigate = useNavigate();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const selectedTab = useSearch({
|
||||
strict: false,
|
||||
select: (el) => el.selectedTab
|
||||
const Page = () => {
|
||||
const navigate = useNavigate();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const selectedTab = useSearch({
|
||||
strict: false,
|
||||
select: (el) => el.selectedTab
|
||||
});
|
||||
|
||||
const updateSelectedTab = (tab: string) => {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
search: (prev) => ({ ...prev, selectedTab: tab }),
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const updateSelectedTab = (tab: string) => {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
search: (prev) => ({ ...prev, selectedTab: tab }),
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
title="Access Control"
|
||||
description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
|
||||
/>
|
||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Groups}>Groups</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Identities}>
|
||||
<div className="flex items-center">
|
||||
<p>Machine Identities</p>
|
||||
</div>
|
||||
</Tab>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab>
|
||||
)}
|
||||
<Tab value={ProjectAccessControlTabs.Roles}>Project Roles</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={ProjectAccessControlTabs.Member}>
|
||||
<MembersTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={ProjectAccessControlTabs.Groups}>
|
||||
<GroupsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={ProjectAccessControlTabs.Identities}>
|
||||
<IdentityTab />
|
||||
</TabPanel>
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
title="Access Control"
|
||||
description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
|
||||
/>
|
||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Groups}>Groups</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Identities}>
|
||||
<div className="flex items-center">
|
||||
<p>Machine Identities</p>
|
||||
</div>
|
||||
</Tab>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<TabPanel value={ProjectAccessControlTabs.ServiceTokens}>
|
||||
<ServiceTokenTab />
|
||||
</TabPanel>
|
||||
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab>
|
||||
)}
|
||||
<TabPanel value={ProjectAccessControlTabs.Roles}>
|
||||
<ProjectRoleListTab />
|
||||
<Tab value={ProjectAccessControlTabs.Roles}>Project Roles</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={ProjectAccessControlTabs.Member}>
|
||||
<MembersTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={ProjectAccessControlTabs.Groups}>
|
||||
<GroupsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={ProjectAccessControlTabs.Identities}>
|
||||
<IdentityTab />
|
||||
</TabPanel>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<TabPanel value={ProjectAccessControlTabs.ServiceTokens}>
|
||||
<ServiceTokenTab />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
<TabPanel value={ProjectAccessControlTabs.Roles}>
|
||||
<ProjectRoleListTab />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
subject: ProjectPermissionSub.Member
|
||||
}
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccessControlPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
|
||||
import { GroupsSection } from "./components";
|
||||
|
||||
export const GroupsTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-groups"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<GroupsSection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
export const GroupsTab = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-groups"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<GroupsSection />
|
||||
</motion.div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
subject: ProjectPermissionSub.Groups
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
|
||||
import { MembersSection } from "./components";
|
||||
|
||||
export const MembersTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-project-members"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<MembersSection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
export const MembersTab = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-project-members"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<MembersSection />
|
||||
</motion.div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
subject: ProjectPermissionSub.Member
|
||||
}
|
||||
);
|
||||
|
||||
+102
-18
@@ -12,7 +12,10 @@ import {
|
||||
import {
|
||||
PermissionConditionOperators,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionGroupActions,
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSecretSyncActions,
|
||||
TPermissionCondition,
|
||||
@@ -71,6 +74,30 @@ const KmipPolicyActionSchema = z.object({
|
||||
[ProjectPermissionKmipActions.GenerateClientCertificates]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const MemberPolicyActionSchema = z.object({
|
||||
[ProjectPermissionMemberActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionMemberActions.Create]: z.boolean().optional(),
|
||||
[ProjectPermissionMemberActions.Edit]: z.boolean().optional(),
|
||||
[ProjectPermissionMemberActions.Delete]: z.boolean().optional(),
|
||||
[ProjectPermissionMemberActions.GrantPrivileges]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const IdentityPolicyActionSchema = z.object({
|
||||
[ProjectPermissionIdentityActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionIdentityActions.Create]: z.boolean().optional(),
|
||||
[ProjectPermissionIdentityActions.Edit]: z.boolean().optional(),
|
||||
[ProjectPermissionIdentityActions.Delete]: z.boolean().optional(),
|
||||
[ProjectPermissionIdentityActions.GrantPrivileges]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const GroupPolicyActionSchema = z.object({
|
||||
[ProjectPermissionGroupActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionGroupActions.Create]: z.boolean().optional(),
|
||||
[ProjectPermissionGroupActions.Edit]: z.boolean().optional(),
|
||||
[ProjectPermissionGroupActions.Delete]: z.boolean().optional(),
|
||||
[ProjectPermissionGroupActions.GrantPrivileges]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const SecretRollbackPolicyActionSchema = z.object({
|
||||
read: z.boolean().optional(),
|
||||
create: z.boolean().optional()
|
||||
@@ -152,14 +179,14 @@ export const projectRoleFormSchema = z.object({
|
||||
})
|
||||
.array()
|
||||
.default([]),
|
||||
[ProjectPermissionSub.Identity]: GeneralPolicyActionSchema.extend({
|
||||
[ProjectPermissionSub.Identity]: IdentityPolicyActionSchema.extend({
|
||||
inverted: z.boolean().optional(),
|
||||
conditions: ConditionSchema
|
||||
})
|
||||
.array()
|
||||
.default([]),
|
||||
[ProjectPermissionSub.Member]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Groups]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Member]: MemberPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Groups]: GroupPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Role]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Integrations]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Webhooks]: GeneralPolicyActionSchema.array().default([]),
|
||||
@@ -248,9 +275,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
ProjectPermissionSub.DynamicSecrets,
|
||||
ProjectPermissionSub.SecretFolders,
|
||||
ProjectPermissionSub.SecretImports,
|
||||
ProjectPermissionSub.Member,
|
||||
ProjectPermissionSub.Groups,
|
||||
ProjectPermissionSub.Identity,
|
||||
ProjectPermissionSub.Role,
|
||||
ProjectPermissionSub.Integrations,
|
||||
ProjectPermissionSub.Webhooks,
|
||||
@@ -432,6 +456,63 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.Member) {
|
||||
const canRead = action.includes(ProjectPermissionMemberActions.Read);
|
||||
const canCreate = action.includes(ProjectPermissionMemberActions.Create);
|
||||
const canEdit = action.includes(ProjectPermissionMemberActions.Edit);
|
||||
const canDelete = action.includes(ProjectPermissionMemberActions.Delete);
|
||||
const canGrantPrivileges = action.includes(ProjectPermissionMemberActions.GrantPrivileges);
|
||||
|
||||
if (!formVal[subject]) formVal[subject] = [{}];
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
if (canRead) formVal[subject]![0][ProjectPermissionMemberActions.Read] = true;
|
||||
if (canCreate) formVal[subject]![0][ProjectPermissionMemberActions.Create] = true;
|
||||
if (canEdit) formVal[subject]![0][ProjectPermissionMemberActions.Edit] = true;
|
||||
if (canDelete) formVal[subject]![0][ProjectPermissionMemberActions.Delete] = true;
|
||||
if (canGrantPrivileges)
|
||||
formVal[subject]![0][ProjectPermissionMemberActions.GrantPrivileges] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.Identity) {
|
||||
const canRead = action.includes(ProjectPermissionIdentityActions.Read);
|
||||
const canCreate = action.includes(ProjectPermissionIdentityActions.Create);
|
||||
const canEdit = action.includes(ProjectPermissionIdentityActions.Edit);
|
||||
const canDelete = action.includes(ProjectPermissionIdentityActions.Delete);
|
||||
const canGrantPrivileges = action.includes(ProjectPermissionIdentityActions.GrantPrivileges);
|
||||
|
||||
if (!formVal[subject]) formVal[subject] = [{ conditions: [] }];
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
if (canRead) formVal[subject]![0][ProjectPermissionIdentityActions.Read] = true;
|
||||
if (canCreate) formVal[subject]![0][ProjectPermissionIdentityActions.Create] = true;
|
||||
if (canEdit) formVal[subject]![0][ProjectPermissionIdentityActions.Edit] = true;
|
||||
if (canDelete) formVal[subject]![0][ProjectPermissionIdentityActions.Delete] = true;
|
||||
if (canGrantPrivileges)
|
||||
formVal[subject]![0][ProjectPermissionIdentityActions.GrantPrivileges] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.Groups) {
|
||||
const canRead = action.includes(ProjectPermissionGroupActions.Read);
|
||||
const canCreate = action.includes(ProjectPermissionGroupActions.Create);
|
||||
const canEdit = action.includes(ProjectPermissionGroupActions.Edit);
|
||||
const canDelete = action.includes(ProjectPermissionGroupActions.Delete);
|
||||
const canGrantPrivileges = action.includes(ProjectPermissionGroupActions.GrantPrivileges);
|
||||
|
||||
if (!formVal[subject]) formVal[subject] = [{}];
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
if (canRead) formVal[subject]![0][ProjectPermissionGroupActions.Read] = true;
|
||||
if (canCreate) formVal[subject]![0][ProjectPermissionGroupActions.Create] = true;
|
||||
if (canEdit) formVal[subject]![0][ProjectPermissionGroupActions.Edit] = true;
|
||||
if (canDelete) formVal[subject]![0][ProjectPermissionGroupActions.Delete] = true;
|
||||
if (canGrantPrivileges)
|
||||
formVal[subject]![0][ProjectPermissionGroupActions.GrantPrivileges] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.SecretSyncs) {
|
||||
const canRead = action.includes(ProjectPermissionSecretSyncActions.Read);
|
||||
const canEdit = action.includes(ProjectPermissionSecretSyncActions.Edit);
|
||||
@@ -637,28 +718,31 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
||||
[ProjectPermissionSub.Member]: {
|
||||
title: "User Management",
|
||||
actions: [
|
||||
{ label: "View all members", value: "read" },
|
||||
{ label: "Invite members", value: "create" },
|
||||
{ label: "Edit members", value: "edit" },
|
||||
{ label: "Remove members", value: "delete" }
|
||||
{ label: "Read", value: ProjectPermissionMemberActions.Read },
|
||||
{ label: "Add", value: ProjectPermissionMemberActions.Create },
|
||||
{ label: "Modify", value: ProjectPermissionMemberActions.Edit },
|
||||
{ label: "Remove", value: ProjectPermissionMemberActions.Delete },
|
||||
{ label: "Grant Privileges", value: ProjectPermissionMemberActions.GrantPrivileges }
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.Identity]: {
|
||||
title: "Machine Identity Management",
|
||||
actions: [
|
||||
{ label: "Read", value: "read" },
|
||||
{ label: "Add", value: "create" },
|
||||
{ label: "Modify", value: "edit" },
|
||||
{ label: "Remove", value: "delete" }
|
||||
{ label: "Read", value: ProjectPermissionIdentityActions.Read },
|
||||
{ label: "Add", value: ProjectPermissionIdentityActions.Create },
|
||||
{ label: "Modify", value: ProjectPermissionIdentityActions.Edit },
|
||||
{ label: "Remove", value: ProjectPermissionIdentityActions.Delete },
|
||||
{ label: "Grant Privileges", value: ProjectPermissionIdentityActions.GrantPrivileges }
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.Groups]: {
|
||||
title: "Group Management",
|
||||
actions: [
|
||||
{ label: "Read", value: "read" },
|
||||
{ label: "Create", value: "create" },
|
||||
{ label: "Modify", value: "edit" },
|
||||
{ label: "Remove", value: "delete" }
|
||||
{ label: "Read", value: ProjectPermissionGroupActions.Read },
|
||||
{ label: "Create", value: ProjectPermissionGroupActions.Create },
|
||||
{ label: "Modify", value: ProjectPermissionGroupActions.Edit },
|
||||
{ label: "Remove", value: ProjectPermissionGroupActions.Delete },
|
||||
{ label: "Grant Privileges", value: ProjectPermissionGroupActions.GrantPrivileges }
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.Webhooks]: {
|
||||
|
||||
-3
@@ -29,9 +29,6 @@ export const IntegrationAuditLogsSection = ({ integration }: Props) => {
|
||||
</div>
|
||||
<LogsSection
|
||||
refetchInterval={4000}
|
||||
remappedHeaders={{
|
||||
Metadata: "Sync Status"
|
||||
}}
|
||||
showFilters={false}
|
||||
presets={{
|
||||
eventMetadata: { integrationId: integration.id },
|
||||
|
||||
+9
@@ -192,6 +192,15 @@ export const IntegrationConnectionSection = ({ integration }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (integration.integration === "windmill" && integration.url) {
|
||||
return (
|
||||
<div>
|
||||
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Instance URL" />
|
||||
<div className="text-sm text-mineshaft-300">{integration.url}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
faAngleDown,
|
||||
faArrowDown,
|
||||
faArrowUp,
|
||||
faFileImport,
|
||||
faFingerprint,
|
||||
faFolder,
|
||||
faFolderBlank,
|
||||
@@ -97,7 +98,8 @@ export enum EntryType {
|
||||
enum RowType {
|
||||
Folder = "folder",
|
||||
DynamicSecret = "dynamic",
|
||||
Secret = "secret"
|
||||
Secret = "secret",
|
||||
Import = "import"
|
||||
}
|
||||
|
||||
type Filter = {
|
||||
@@ -107,7 +109,8 @@ type Filter = {
|
||||
const DEFAULT_FILTER_STATE = {
|
||||
[RowType.Folder]: true,
|
||||
[RowType.DynamicSecret]: true,
|
||||
[RowType.Secret]: true
|
||||
[RowType.Secret]: true,
|
||||
[RowType.Import]: true
|
||||
};
|
||||
|
||||
export const OverviewPage = () => {
|
||||
@@ -199,12 +202,16 @@ export const OverviewPage = () => {
|
||||
setVisibleEnvs(userAvailableEnvs);
|
||||
}, [userAvailableEnvs]);
|
||||
|
||||
const { isImportedSecretPresentInEnv, getImportedSecretByKey, getEnvImportedSecretKeyCount } =
|
||||
useGetImportedSecretsAllEnvs({
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
|
||||
});
|
||||
const {
|
||||
secretImports,
|
||||
isImportedSecretPresentInEnv,
|
||||
getImportedSecretByKey,
|
||||
getEnvImportedSecretKeyCount
|
||||
} = useGetImportedSecretsAllEnvs({
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
|
||||
});
|
||||
|
||||
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
|
||||
{
|
||||
@@ -216,6 +223,7 @@ export const OverviewPage = () => {
|
||||
includeFolders: filter.folder,
|
||||
includeDynamicSecrets: filter.dynamic,
|
||||
includeSecrets: filter.secret,
|
||||
includeImports: filter.import,
|
||||
search: debouncedSearchFilter,
|
||||
limit,
|
||||
offset
|
||||
@@ -230,12 +238,29 @@ export const OverviewPage = () => {
|
||||
totalFolderCount,
|
||||
totalSecretCount,
|
||||
totalDynamicSecretCount,
|
||||
totalImportCount,
|
||||
totalCount = 0,
|
||||
totalUniqueFoldersInPage,
|
||||
totalUniqueSecretsInPage,
|
||||
totalUniqueSecretImportsInPage,
|
||||
totalUniqueDynamicSecretsInPage
|
||||
} = overview ?? {};
|
||||
|
||||
const secretImportsShaped = secretImports
|
||||
?.flatMap(({ data }) => data)
|
||||
.filter(Boolean)
|
||||
.flatMap((item) => item?.secrets || []);
|
||||
|
||||
const handleIsImportedSecretPresentInEnv = (envSlug: string, secretName: string) => {
|
||||
if (secrets?.some((s) => s.key === secretName && s.env === envSlug)) {
|
||||
return false;
|
||||
}
|
||||
if (secretImportsShaped.some((s) => s.key === secretName && s.sourceEnv === envSlug)) {
|
||||
return true;
|
||||
}
|
||||
return isImportedSecretPresentInEnv(envSlug, secretName);
|
||||
};
|
||||
|
||||
useResetPageHelper({
|
||||
totalCount,
|
||||
offset,
|
||||
@@ -248,7 +273,18 @@ export const OverviewPage = () => {
|
||||
const { dynamicSecretNames, isDynamicSecretPresentInEnv } =
|
||||
useDynamicSecretOverview(dynamicSecrets);
|
||||
|
||||
const { secKeys, getSecretByKey, getEnvSecretKeyCount } = useSecretOverview(secrets);
|
||||
const { secKeys, getEnvSecretKeyCount } = useSecretOverview(
|
||||
secrets?.concat(secretImportsShaped) || []
|
||||
);
|
||||
|
||||
const getSecretByKey = useCallback(
|
||||
(env: string, key: string) => {
|
||||
const sec = secrets?.find((s) => s.env === env && s.key === key);
|
||||
return sec;
|
||||
},
|
||||
[secrets]
|
||||
);
|
||||
|
||||
const { data: tags } = useGetWsTags(
|
||||
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? workspaceId : ""
|
||||
);
|
||||
@@ -678,7 +714,6 @@ export const OverviewPage = () => {
|
||||
<SecretV2MigrationSection />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<Helmet>
|
||||
@@ -767,6 +802,19 @@ export const OverviewPage = () => {
|
||||
</Button>
|
||||
</DropdownMenuItem> */}
|
||||
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleToggleRowType(RowType.Import);
|
||||
}}
|
||||
icon={filter[RowType.Import] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faFileImport} className="text-green-700" />
|
||||
<span>Imports</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -1099,7 +1147,7 @@ export const OverviewPage = () => {
|
||||
onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)}
|
||||
secretPath={secretPath}
|
||||
getImportedSecretByKey={getImportedSecretByKey}
|
||||
isImportedSecretPresentInEnv={isImportedSecretPresentInEnv}
|
||||
isImportedSecretPresentInEnv={handleIsImportedSecretPresentInEnv}
|
||||
onSecretCreate={handleSecretCreate}
|
||||
onSecretDelete={handleSecretDelete}
|
||||
onSecretUpdate={handleSecretUpdate}
|
||||
@@ -1116,7 +1164,8 @@ export const OverviewPage = () => {
|
||||
(page * perPage > totalCount ? totalCount % perPage : perPage) -
|
||||
(totalUniqueFoldersInPage || 0) -
|
||||
(totalUniqueDynamicSecretsInPage || 0) -
|
||||
(totalUniqueSecretsInPage || 0),
|
||||
(totalUniqueSecretsInPage || 0) -
|
||||
(totalUniqueSecretImportsInPage || 0),
|
||||
0
|
||||
)}
|
||||
/>
|
||||
@@ -1156,6 +1205,7 @@ export const OverviewPage = () => {
|
||||
dynamicSecretCount={totalDynamicSecretCount}
|
||||
secretCount={totalSecretCount}
|
||||
folderCount={totalFolderCount}
|
||||
importCount={totalImportCount}
|
||||
/>
|
||||
}
|
||||
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isReadOnly={isReadOnly}
|
||||
isReadOnly={isReadOnly || secrets.filter(Boolean).length === 0}
|
||||
autoCapitalization={currentWorkspace?.autoCapitalization}
|
||||
variant="plain"
|
||||
isDisabled={isOverriden}
|
||||
|
||||
+10
-8
@@ -152,7 +152,7 @@ export const AccessApprovalRequest = ({
|
||||
const isAccepted = request.isApproved;
|
||||
const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||
const isRequestedByCurrentUser = request.requestedByUserId === user.id;
|
||||
|
||||
const isSelfApproveAllowed = request.policy.allowedSelfApprovals;
|
||||
const userReviewStatus = request.reviewers.find(({ member }) => member === user.id)?.status;
|
||||
|
||||
let displayData: { label: string; type: "primary" | "danger" | "success" } = {
|
||||
@@ -189,7 +189,8 @@ export const AccessApprovalRequest = ({
|
||||
userReviewStatus,
|
||||
isAccepted,
|
||||
isSoftEnforcement,
|
||||
isRequestedByCurrentUser
|
||||
isRequestedByCurrentUser,
|
||||
isSelfApproveAllowed
|
||||
};
|
||||
};
|
||||
|
||||
@@ -342,15 +343,16 @@ export const AccessApprovalRequest = ({
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
if (
|
||||
(!details.isApprover ||
|
||||
((!details.isApprover ||
|
||||
details.isReviewedByUser ||
|
||||
details.isRejectedByAnyone ||
|
||||
details.isAccepted) &&
|
||||
!(
|
||||
details.isSoftEnforcement &&
|
||||
details.isRequestedByCurrentUser &&
|
||||
!details.isAccepted
|
||||
)
|
||||
!(
|
||||
details.isSoftEnforcement &&
|
||||
details.isRequestedByCurrentUser &&
|
||||
!details.isAccepted
|
||||
)) ||
|
||||
(request.requestedByUserId === user.id && !details.isSelfApproveAllowed)
|
||||
)
|
||||
return;
|
||||
if (membersGroupById?.[request.requestedByUserId].user) {
|
||||
|
||||
+27
-3
@@ -12,7 +12,8 @@ import {
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
SelectItem,
|
||||
Switch
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { getMemberLabel } from "@app/helpers/members";
|
||||
@@ -54,7 +55,8 @@ const formSchema = z
|
||||
.array()
|
||||
.default([]),
|
||||
policyType: z.nativeEnum(PolicyType),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel)
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel),
|
||||
allowedSelfApprovals: z.boolean().default(true)
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (!(data.groupApprovers.length || data.userApprovers.length)) {
|
||||
@@ -101,7 +103,8 @@ export const AccessPolicyForm = ({
|
||||
editValues?.approvers
|
||||
?.filter((approver) => approver.type === ApproverType.Group)
|
||||
.map(({ id, type }) => ({ id, type: type as ApproverType.Group })) || [],
|
||||
approvals: editValues?.approvals
|
||||
approvals: editValues?.approvals,
|
||||
allowedSelfApprovals: editValues?.allowedSelfApprovals
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
@@ -441,6 +444,27 @@ export const AccessPolicyForm = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="allowedSelfApprovals"
|
||||
defaultValue
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Self Approvals"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Switch
|
||||
id="self-approvals"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
Allow approvers to review their own requests
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center space-x-4">
|
||||
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
|
||||
Save
|
||||
|
||||
+152
-143
@@ -127,7 +127,9 @@ export const SecretApprovalRequestChanges = ({
|
||||
} = useForm<TReviewFormSchema>({
|
||||
resolver: zodResolver(reviewFormSchema)
|
||||
});
|
||||
|
||||
const shouldBlockSelfReview =
|
||||
secretApprovalRequestDetails?.policy?.allowedSelfApprovals === false &&
|
||||
secretApprovalRequestDetails?.committerUserId === userSession.id;
|
||||
const isApproving = variables?.status === ApprovalStatus.APPROVED && isUpdatingRequestStatus;
|
||||
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
|
||||
|
||||
@@ -245,117 +247,119 @@ export const SecretApprovalRequestChanges = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!hasMerged && secretApprovalRequestDetails.status === "open" && (
|
||||
<DropdownMenu
|
||||
open={popUp.reviewChanges.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
|
||||
>
|
||||
Review
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" asChild className="mt-3">
|
||||
<form onSubmit={handleSubmit(handleSubmitReview)}>
|
||||
<div className="flex w-[400px] flex-col space-y-2 p-5">
|
||||
<div className="text-lg font-medium">Finish your review</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="comment"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<TextArea
|
||||
{...field}
|
||||
placeholder="Leave a comment..."
|
||||
reSize="none"
|
||||
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="status"
|
||||
defaultValue={ApprovalStatus.APPROVED}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<RadioGroup
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="mb-4 space-y-2"
|
||||
aria-label="Status"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="approve"
|
||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.APPROVED}
|
||||
aria-labelledby="approve-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="approve-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.APPROVED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Approve
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="reject"
|
||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.REJECTED}
|
||||
aria-labelledby="reject-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="reject-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.REJECTED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Reject
|
||||
</span>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isApproving || isRejecting || isSubmitting}
|
||||
variant="outline_bg"
|
||||
>
|
||||
Submit Review
|
||||
</Button>
|
||||
{!hasMerged &&
|
||||
secretApprovalRequestDetails.status === "open" &&
|
||||
!shouldBlockSelfReview && (
|
||||
<DropdownMenu
|
||||
open={popUp.reviewChanges.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
|
||||
>
|
||||
Review
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" asChild className="mt-3">
|
||||
<form onSubmit={handleSubmit(handleSubmitReview)}>
|
||||
<div className="flex w-[400px] flex-col space-y-2 p-5">
|
||||
<div className="text-lg font-medium">Finish your review</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="comment"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<TextArea
|
||||
{...field}
|
||||
placeholder="Leave a comment..."
|
||||
reSize="none"
|
||||
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="status"
|
||||
defaultValue={ApprovalStatus.APPROVED}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<RadioGroup
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="mb-4 space-y-2"
|
||||
aria-label="Status"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="approve"
|
||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.APPROVED}
|
||||
aria-labelledby="approve-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="approve-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.APPROVED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Approve
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="reject"
|
||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.REJECTED}
|
||||
aria-labelledby="reject-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="reject-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.REJECTED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Reject
|
||||
</span>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isApproving || isRejecting || isSubmitting}
|
||||
variant="outline_bg"
|
||||
>
|
||||
Submit Review
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</form>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
{secretApprovalRequestDetails.commits.map(
|
||||
@@ -422,40 +426,45 @@ export const SecretApprovalRequestChanges = ({
|
||||
<div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}>
|
||||
<div className="text-sm text-bunker-300">Reviewers</div>
|
||||
<div className="mt-2 flex flex-col space-y-2 text-sm">
|
||||
{secretApprovalRequestDetails?.policy?.approvers.map((requiredApprover) => {
|
||||
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
||||
return (
|
||||
<div
|
||||
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
||||
key={`required-approver-${requiredApprover.userId}`}
|
||||
>
|
||||
<div className="flex-grow text-sm">
|
||||
<Tooltip
|
||||
content={`${requiredApprover.firstName || ""} ${
|
||||
requiredApprover.lastName || ""
|
||||
}`}
|
||||
>
|
||||
<span>{requiredApprover?.email} </span>
|
||||
</Tooltip>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
<div>
|
||||
{reviewer?.comment && (
|
||||
<Tooltip content={reviewer.comment}>
|
||||
<FontAwesomeIcon
|
||||
icon={faComment}
|
||||
size="xs"
|
||||
className="mr-1 text-mineshaft-300"
|
||||
/>
|
||||
{secretApprovalRequestDetails?.policy?.approvers
|
||||
.filter(
|
||||
(requiredApprover) =>
|
||||
!(shouldBlockSelfReview && requiredApprover.userId === userSession.id)
|
||||
)
|
||||
.map((requiredApprover) => {
|
||||
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
||||
return (
|
||||
<div
|
||||
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
||||
key={`required-approver-${requiredApprover.userId}`}
|
||||
>
|
||||
<div className="flex-grow text-sm">
|
||||
<Tooltip
|
||||
content={`${requiredApprover.firstName || ""} ${
|
||||
requiredApprover.lastName || ""
|
||||
}`}
|
||||
>
|
||||
<span>{requiredApprover?.email} </span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={reviewer?.status || ApprovalStatus.PENDING}>
|
||||
{getReviewedStatusSymbol(reviewer?.status)}
|
||||
</Tooltip>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
<div>
|
||||
{reviewer?.comment && (
|
||||
<Tooltip content={reviewer.comment}>
|
||||
<FontAwesomeIcon
|
||||
icon={faComment}
|
||||
size="xs"
|
||||
className="mr-1 text-mineshaft-300"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={reviewer?.status || ApprovalStatus.PENDING}>
|
||||
{getReviewedStatusSymbol(reviewer?.status)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
{secretApprovalRequestDetails?.reviewers
|
||||
.filter(
|
||||
(reviewer) =>
|
||||
|
||||
@@ -250,7 +250,11 @@ const Page = () => {
|
||||
limit: 10
|
||||
});
|
||||
|
||||
const { data: snapshotCount, isPending: isSnapshotCountLoading } = useGetWsSnapshotCount({
|
||||
const {
|
||||
data: snapshotCount,
|
||||
isPending: isSnapshotCountLoading,
|
||||
isFetching: isSnapshotCountFetching
|
||||
} = useGetWsSnapshotCount({
|
||||
workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
@@ -414,7 +418,7 @@ const Page = () => {
|
||||
onSearchChange={handleSearchChange}
|
||||
onToggleTagFilter={handleTagToggle}
|
||||
snapshotCount={snapshotCount || 0}
|
||||
isSnapshotCountLoading={isSnapshotCountLoading}
|
||||
isSnapshotCountLoading={isSnapshotCountLoading && isSnapshotCountFetching}
|
||||
onToggleRowType={handleToggleRowType}
|
||||
onClickRollbackMode={() => handlePopUpToggle("snapshots", true)}
|
||||
protectedBranchPolicyName={boardPolicy?.name}
|
||||
|
||||
-3
@@ -35,9 +35,6 @@ export const SecretSyncAuditLogsSection = ({ secretSync }: Props) => {
|
||||
{subscription.auditLogs ? (
|
||||
<LogsSection
|
||||
refetchInterval={4000}
|
||||
remappedHeaders={{
|
||||
Metadata: "Sync Status"
|
||||
}}
|
||||
showFilters={false}
|
||||
presets={{
|
||||
eventMetadata: { syncId: secretSync.id },
|
||||
|
||||
+58
-2
@@ -3,31 +3,75 @@ import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
||||
|
||||
export const WindmillAuthorizePage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
const [apiKeyErrorText, setApiKeyErrorText] = useState("");
|
||||
const [apiUrl, setApiUrl] = useState<string | null>(null);
|
||||
const [apiUrlErrorText, setApiUrlErrorText] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const isLocalOrPrivateIpAddress = (url: string): boolean => {
|
||||
try {
|
||||
const validUrl = new URL(url);
|
||||
// Check for localhost
|
||||
if (validUrl.hostname === "localhost" || validUrl.hostname === "127.0.0.1") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for 10.x.x.x
|
||||
if (validUrl.hostname.match(/^10\.\d+\.\d+\.\d+/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for host.docker.internal
|
||||
if (validUrl.hostname === "host.docker.internal") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for 192.168.x.x
|
||||
if (validUrl.hostname.match(/^192\.168\.\d+\.\d+/)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setApiKeyErrorText("");
|
||||
setApiUrlErrorText("");
|
||||
if (apiKey.length === 0) {
|
||||
setApiKeyErrorText("API Key cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiUrl) {
|
||||
if (!apiUrl.startsWith("http://") && !apiUrl.startsWith("https://")) {
|
||||
setApiUrlErrorText("API URL must start with http:// or https://");
|
||||
return;
|
||||
}
|
||||
if (isInfisicalCloud() && isLocalOrPrivateIpAddress(apiUrl)) {
|
||||
setApiUrlErrorText("Local IPs not allowed as URL");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: currentWorkspace.id,
|
||||
integration: "windmill",
|
||||
accessToken: apiKey
|
||||
accessToken: apiKey,
|
||||
url: apiUrl ?? undefined
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
@@ -57,6 +101,18 @@ export const WindmillAuthorizePage = () => {
|
||||
>
|
||||
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Windmill Instance URL"
|
||||
errorText={apiUrlErrorText}
|
||||
isError={apiUrlErrorText !== ""}
|
||||
tooltipText="If you are using a custom domain, enter it here. Otherwise, leave it blank."
|
||||
>
|
||||
<Input
|
||||
value={apiUrl ?? ""}
|
||||
onChange={(e) => setApiUrl(e.target.value.trim() === "" ? null : e.target.value.trim())}
|
||||
placeholder="https://xxxx.windmill.dev"
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
|
||||
+2
-1
@@ -67,7 +67,8 @@ export const WindmillConfigurePage = () => {
|
||||
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
||||
)?.appId,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
secretPath
|
||||
secretPath,
|
||||
url: integrationAuth.url ?? undefined
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -26,21 +26,6 @@ import {
|
||||
} from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
// Validates usernames or wildcard (*)
|
||||
export const isValidUserPattern = (value: string): boolean => {
|
||||
// Matches valid Linux usernames or a wildcard (*)
|
||||
const userRegex = /^(?:\*|[a-z_][a-z0-9_-]{0,31})$/;
|
||||
return userRegex.test(value);
|
||||
};
|
||||
|
||||
// Validates hostnames, wildcard domains, or IP addresses
|
||||
export const isValidHostPattern = (value: string): boolean => {
|
||||
// Matches FQDNs, wildcard domains (*.example.com), IPv4, and IPv6 addresses
|
||||
const hostRegex =
|
||||
/^(?:\*|\*\.[a-z0-9-]+(?:\.[a-z0-9-]+)*|[a-z0-9-]+(?:\.[a-z0-9-]+)*|\d{1,3}(\.\d{1,3}){3}|([a-fA-F0-9:]+:+)+[a-fA-F0-9]+(?:%[a-zA-Z0-9]+)?)$/;
|
||||
return hostRegex.test(value);
|
||||
};
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
sshCaId: z.string(),
|
||||
@@ -69,28 +54,8 @@ const schema = z
|
||||
"Max TTL must be a valid time string such as 2 days, 1d, 2h 1y, ..."
|
||||
)
|
||||
.default("30d"),
|
||||
allowedUsers: z.string().refine(
|
||||
(val) => {
|
||||
const trimmed = val.trim();
|
||||
if (trimmed === "") return true;
|
||||
const users = trimmed.split(",").map((u) => u.trim());
|
||||
return users.every(isValidUserPattern);
|
||||
},
|
||||
{
|
||||
message: "Invalid user pattern in allowedUsers"
|
||||
}
|
||||
),
|
||||
allowedHosts: z.string().refine(
|
||||
(val) => {
|
||||
const trimmed = val.trim();
|
||||
if (trimmed === "") return true;
|
||||
const users = trimmed.split(",").map((u) => u.trim());
|
||||
return users.every(isValidHostPattern);
|
||||
},
|
||||
{
|
||||
message: "Invalid host pattern in allowedHosts"
|
||||
}
|
||||
),
|
||||
allowedUsers: z.string(),
|
||||
allowedHosts: z.string(),
|
||||
allowUserCertificates: z.boolean().optional().default(false),
|
||||
allowHostCertificates: z.boolean().optional().default(false),
|
||||
allowCustomKeyIds: z.boolean().optional().default(false)
|
||||
|
||||
Reference in New Issue
Block a user