Compare commits

...

13 Commits

Author SHA1 Message Date
fb030401ab doc: add ab-initio docs 2025-02-26 13:37:19 +09:00
f4bd48fd1d Merge pull request #3142 from Infisical/sidebar-update
improve sidebars
2025-02-25 13:20:53 +04:00
177ccf6c9e Update SecretDetailSidebar.tsx 2025-02-25 18:15:27 +09:00
9200137d6c Merge pull request #3144 from Infisical/revert-3130-snyk-fix-9bc3e8652a6384afdd415f17c0d6ac68
Revert "[Snyk] Fix for 4 vulnerabilities"
2025-02-25 18:12:32 +09:00
a196028064 Revert "[Snyk] Fix for 4 vulnerabilities" 2025-02-25 18:12:12 +09:00
0c0e20f00e Merge pull request #3143 from Infisical/revert-3129-snyk-fix-e021ef688dc4b4af03b9ad04389eee3f
Revert "[Snyk] Security upgrade @octokit/rest from 21.0.2 to 21.1.1"
2025-02-25 18:11:56 +09:00
710429c805 Revert "[Snyk] Security upgrade @octokit/rest from 21.0.2 to 21.1.1" 2025-02-25 18:10:30 +09:00
c121bd930b fix nav 2025-02-25 18:03:13 +09:00
87d383a9c4 Update SecretDetailSidebar.tsx 2025-02-25 17:44:55 +09:00
6e590a78a0 fix lint issues 2025-02-25 17:30:15 +09:00
ab4b6c17b3 fix lint issues 2025-02-25 17:23:05 +09:00
27cd40c8ce fix lint issues 2025-02-25 17:20:52 +09:00
5f089e0b9d improve sidebars 2025-02-25 17:07:53 +09:00
12 changed files with 926 additions and 1067 deletions

File diff suppressed because it is too large Load Diff

View File

@ -139,9 +139,9 @@
"@fastify/swagger-ui": "^2.1.0", "@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0", "@google-cloud/kms": "^4.5.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/auth-app": "^7.1.5", "@octokit/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^7.1.4", "@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1", "@octokit/webhooks-types": "^7.3.1",
"@octopusdeploy/api-client": "^3.4.1", "@octopusdeploy/api-client": "^3.4.1",
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",

View File

@ -0,0 +1,32 @@
---
title: "AB Initio"
description: "How to use Infisical secrets in AB Initio."
---
## Prerequisites
- Set up and add envars to [Infisical](https://app.infisical.com).
- Install the [Infisical CLI](https://infisical.com/docs/cli/overview) to your server.
## Setup
<Steps>
<Step title="Authorize Infisical for AB Initio">
Create a [machine identity](https://infisical.com/docs/documentation/platform/identities/machine-identities#machine-identities) in Infisical and give it the appropriate read permissions for the desired project and secret paths.
</Step>
<Step title="Add Infisical CLI to your workflow">
Update your AB Initio workflows to use Infisical CLI to inject Infisical secrets as environment variables.
```bash
# Login using the machine identity. Modify this accordingly based on the authentication method used.
export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=$INFISICAL_CLIENT_ID --client-secret=$INFISICAL_CLIENT_SECRET --silent --plain)
# Fetch secrets from Infisical
infisical export --projectId="<>" --env="prod" > infisical.env
# Inject secrets as environment variables
source infisical.env
```
</Step>
</Steps>

View File

@ -515,7 +515,8 @@
"integrations/frameworks/laravel", "integrations/frameworks/laravel",
"integrations/frameworks/rails", "integrations/frameworks/rails",
"integrations/frameworks/dotnet", "integrations/frameworks/dotnet",
"integrations/platforms/pm2" "integrations/platforms/pm2",
"integrations/frameworks/ab-initio"
] ]
} }
] ]

View File

@ -23,7 +23,7 @@
"@headlessui/react": "^1.7.19", "@headlessui/react": "^1.7.19",
"@hookform/resolvers": "^3.9.1", "@hookform/resolvers": "^3.9.1",
"@lottiefiles/dotlottie-react": "^0.12.0", "@lottiefiles/dotlottie-react": "^0.12.0",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^21.0.2",
"@peculiar/x509": "^1.12.3", "@peculiar/x509": "^1.12.3",
"@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.3", "@radix-ui/react-alert-dialog": "^1.1.3",
@ -1605,16 +1605,16 @@
} }
}, },
"node_modules/@octokit/core": { "node_modules/@octokit/core": {
"version": "6.1.4", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz",
"integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/auth-token": "^5.0.0", "@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.1.2", "@octokit/graphql": "^8.0.0",
"@octokit/request": "^9.2.1", "@octokit/request": "^9.0.0",
"@octokit/request-error": "^6.1.7", "@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.6.2", "@octokit/types": "^13.0.0",
"before-after-hook": "^3.0.2", "before-after-hook": "^3.0.2",
"universal-user-agent": "^7.0.0" "universal-user-agent": "^7.0.0"
}, },
@ -1623,12 +1623,12 @@
} }
}, },
"node_modules/@octokit/endpoint": { "node_modules/@octokit/endpoint": {
"version": "10.1.3", "version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==", "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^13.6.2", "@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2" "universal-user-agent": "^7.0.2"
}, },
"engines": { "engines": {
@ -1636,13 +1636,13 @@
} }
}, },
"node_modules/@octokit/graphql": { "node_modules/@octokit/graphql": {
"version": "8.2.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz",
"integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==", "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/request": "^9.2.2", "@octokit/request": "^9.0.0",
"@octokit/types": "^13.8.0", "@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0" "universal-user-agent": "^7.0.0"
}, },
"engines": { "engines": {
@ -1650,18 +1650,18 @@
} }
}, },
"node_modules/@octokit/openapi-types": { "node_modules/@octokit/openapi-types": {
"version": "23.0.1", "version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@octokit/plugin-paginate-rest": { "node_modules/@octokit/plugin-paginate-rest": {
"version": "11.4.2", "version": "11.3.6",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.6.tgz",
"integrity": "sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==", "integrity": "sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^13.7.0" "@octokit/types": "^13.6.2"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
@ -1683,12 +1683,12 @@
} }
}, },
"node_modules/@octokit/plugin-rest-endpoint-methods": { "node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "13.3.1", "version": "13.2.6",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.6.tgz",
"integrity": "sha512-o8uOBdsyR+WR8MK9Cco8dCgvG13H1RlM1nWnK/W7TEACQBFux/vPREgKucxUfuDQ5yi1T3hGf4C5ZmZXAERgwQ==", "integrity": "sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^13.8.0" "@octokit/types": "^13.6.1"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
@ -1698,15 +1698,14 @@
} }
}, },
"node_modules/@octokit/request": { "node_modules/@octokit/request": {
"version": "9.2.2", "version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==", "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/endpoint": "^10.1.3", "@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.1.7", "@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.6.2", "@octokit/types": "^13.1.0",
"fast-content-type-parse": "^2.0.0",
"universal-user-agent": "^7.0.2" "universal-user-agent": "^7.0.2"
}, },
"engines": { "engines": {
@ -1714,39 +1713,39 @@
} }
}, },
"node_modules/@octokit/request-error": { "node_modules/@octokit/request-error": {
"version": "6.1.7", "version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==", "integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/types": "^13.6.2" "@octokit/types": "^13.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/@octokit/rest": { "node_modules/@octokit/rest": {
"version": "21.1.1", "version": "21.0.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.0.2.tgz",
"integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", "integrity": "sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/core": "^6.1.4", "@octokit/core": "^6.1.2",
"@octokit/plugin-paginate-rest": "^11.4.2", "@octokit/plugin-paginate-rest": "^11.0.0",
"@octokit/plugin-request-log": "^5.3.1", "@octokit/plugin-request-log": "^5.3.1",
"@octokit/plugin-rest-endpoint-methods": "^13.3.0" "@octokit/plugin-rest-endpoint-methods": "^13.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/@octokit/types": { "node_modules/@octokit/types": {
"version": "13.8.0", "version": "13.6.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/openapi-types": "^23.0.1" "@octokit/openapi-types": "^22.2.0"
} }
}, },
"node_modules/@peculiar/asn1-cms": { "node_modules/@peculiar/asn1-cms": {
@ -6906,22 +6905,6 @@
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
} }
}, },
"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": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",

View File

@ -27,7 +27,7 @@
"@headlessui/react": "^1.7.19", "@headlessui/react": "^1.7.19",
"@hookform/resolvers": "^3.9.1", "@hookform/resolvers": "^3.9.1",
"@lottiefiles/dotlottie-react": "^0.12.0", "@lottiefiles/dotlottie-react": "^0.12.0",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^21.0.2",
"@peculiar/x509": "^1.12.3", "@peculiar/x509": "^1.12.3",
"@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.3", "@radix-ui/react-alert-dialog": "^1.1.3",

View File

@ -20,7 +20,7 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
return ( return (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
sideOffset={10} sideOffset={-8}
{...props} {...props}
ref={forwardedRef} ref={forwardedRef}
className={twMerge( className={twMerge(

View File

@ -20,12 +20,13 @@ export const MenuIconButton = <T extends ElementType = "button">({
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => { ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null); const iconRef = useRef<DotLottie | null>(null);
return ( return (
<div className={!isSelected ? "hover:px-1" : ""}>
<Item <Item
type="button" type="button"
role="menuitem" role="menuitem"
className={twMerge( className={twMerge(
"group relative flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700", "group relative flex w-full cursor-pointer flex-col items-center justify-center rounded my-1 p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
isSelected && "bg-bunker-800 hover:bg-mineshaft-600", isSelected && "bg-bunker-800 hover:bg-mineshaft-600 rounded-none",
isDisabled && "cursor-not-allowed hover:bg-transparent", isDisabled && "cursor-not-allowed hover:bg-transparent",
className className
)} )}
@ -37,7 +38,7 @@ export const MenuIconButton = <T extends ElementType = "button">({
<div <div
className={`${ className={`${
isSelected ? "opacity-100" : "opacity-0" isSelected ? "opacity-100" : "opacity-0"
} absolute -left-[0.28rem] h-full w-1 rounded-md bg-primary transition-all duration-150`} } absolute left-0 h-full w-1 bg-primary transition-all duration-150`}
/> />
{icon && ( {icon && (
<div className="my-auto mb-2 h-6 w-6"> <div className="my-auto mb-2 h-6 w-6">
@ -59,5 +60,6 @@ export const MenuIconButton = <T extends ElementType = "button">({
{children} {children}
</div> </div>
</Item> </Item>
</div>
); );
}; };

View File

@ -84,6 +84,10 @@ export const MinimizedOrgSidebar = () => {
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL); const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const [open, setOpen] = useState(false);
const [openSupport, setOpenSupport] = useState(false);
const [openUser, setOpenUser] = useState(false);
const [openOrg, setOpenOrg] = useState(false);
const { user } = useUser(); const { user } = useUser();
const { mutateAsync } = useGetOrgTrialUrl(); const { mutateAsync } = useGetOrgTrialUrl();
@ -160,23 +164,29 @@ export const MinimizedOrgSidebar = () => {
> >
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]"> <nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div> <div>
<div className="flex cursor-pointer items-center p-2 pt-4 hover:bg-mineshaft-700"> <div className="flex items-center hover:bg-mineshaft-700">
<DropdownMenu modal> <DropdownMenu open={openOrg} onOpenChange={setOpenOrg} modal>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger
<div className="flex w-full items-center justify-center rounded-md border border-none border-mineshaft-600 p-1 transition-all"> onMouseEnter={() => setOpenOrg(true)}
onMouseLeave={() => setOpenOrg(false)}
asChild
>
<div className="flex w-full items-center justify-center rounded-md border border-none border-mineshaft-600 p-3 pb-5 pt-6 transition-all">
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary"> <div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary">
{currentOrg?.name.charAt(0)} {currentOrg?.name.charAt(0)}
</div> </div>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
onMouseEnter={() => setOpenOrg(true)}
onMouseLeave={() => setOpenOrg(false)}
align="start" align="start"
side="right" side="right"
className="p-1 shadow-mineshaft-600 drop-shadow-md" className="mt-6 cursor-default p-1 shadow-mineshaft-600 drop-shadow-md"
style={{ minWidth: "320px" }} style={{ minWidth: "220px" }}
> >
<div className="px-2 py-1"> <div className="px-0.5 py-1">
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 p-1 transition-all duration-150 hover:bg-mineshaft-700"> <div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-gradient-to-tr from-primary-500/5 to-mineshaft-800 p-1 transition-all duration-300">
<div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary text-black"> <div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary text-black">
{currentOrg?.name.charAt(0)} {currentOrg?.name.charAt(0)}
</div> </div>
@ -241,7 +251,7 @@ export const MinimizedOrgSidebar = () => {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
<div className="space-y-1 px-1"> <div className="space-y-1">
<Link to="/organization/secret-manager/overview"> <Link to="/organization/secret-manager/overview">
{({ isActive }) => ( {({ isActive }) => (
<MenuIconButton <MenuIconButton
@ -251,7 +261,7 @@ export const MinimizedOrgSidebar = () => {
} }
icon="sliding-carousel" icon="sliding-carousel"
> >
Secret Manager Secrets
</MenuIconButton> </MenuIconButton>
)} )}
</Link> </Link>
@ -264,7 +274,7 @@ export const MinimizedOrgSidebar = () => {
} }
icon="note" icon="note"
> >
Cert Manager PKI
</MenuIconButton> </MenuIconButton>
)} )}
</Link> </Link>
@ -296,31 +306,41 @@ export const MinimizedOrgSidebar = () => {
<Link to="/organization/secret-scanning"> <Link to="/organization/secret-scanning">
{({ isActive }) => ( {({ isActive }) => (
<MenuIconButton isSelected={isActive} icon="secret-scan"> <MenuIconButton isSelected={isActive} icon="secret-scan">
Secret Scanning Scanner
</MenuIconButton> </MenuIconButton>
)} )}
</Link> </Link>
<Link to="/organization/secret-sharing"> <Link to="/organization/secret-sharing">
{({ isActive }) => ( {({ isActive }) => (
<MenuIconButton isSelected={isActive} icon="lock-closed"> <MenuIconButton isSelected={isActive} icon="lock-closed">
Secret Sharing Share
</MenuIconButton> </MenuIconButton>
)} )}
</Link> </Link>
<div className="my-1 w-full bg-mineshaft-500" style={{ height: "1px" }} /> <div className="my-1 w-full bg-mineshaft-500" style={{ height: "1px" }} />
<DropdownMenu> <DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
asChild
>
<div className="w-full"> <div className="w-full">
<MenuIconButton <MenuIconButton
lottieIconMode="reverse" lottieIconMode="reverse"
icon="settings-cog" icon="settings-cog"
isSelected={isMoreSelected} isSelected={isMoreSelected}
> >
Org Controls Admin
</MenuIconButton> </MenuIconButton>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" side="right" className="p-1"> <DropdownMenuContent
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
align="start"
side="right"
className="p-1"
>
<DropdownMenuLabel>Organization Options</DropdownMenuLabel> <DropdownMenuLabel>Organization Options</DropdownMenuLabel>
<Link to="/organization/access-management"> <Link to="/organization/access-management">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faUsers} />}> <DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faUsers} />}>
@ -364,14 +384,24 @@ export const MinimizedOrgSidebar = () => {
: "mb-4" : "mb-4"
} flex w-full cursor-default flex-col items-center px-1 text-sm text-mineshaft-400`} } flex w-full cursor-default flex-col items-center px-1 text-sm text-mineshaft-400`}
> >
<DropdownMenu> <DropdownMenu open={openSupport} onOpenChange={setOpenSupport}>
<DropdownMenuTrigger className="w-full"> <DropdownMenuTrigger
onMouseEnter={() => setOpenSupport(true)}
onMouseLeave={() => setOpenSupport(false)}
className="w-full"
>
<MenuIconButton> <MenuIconButton>
<FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" /> <FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" />
Support Support
</MenuIconButton> </MenuIconButton>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent
onMouseEnter={() => setOpenSupport(true)}
onMouseLeave={() => setOpenSupport(false)}
align="end"
side="right"
className="p-1"
>
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => ( {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
<DropdownMenuItem key={url as string}> <DropdownMenuItem key={url as string}>
<a <a
@ -419,17 +449,28 @@ export const MinimizedOrgSidebar = () => {
</button> </button>
</Tooltip> </Tooltip>
)} )}
<DropdownMenu> <DropdownMenu open={openUser} onOpenChange={setOpenUser}>
<DropdownMenuTrigger className="w-full" asChild> <DropdownMenuTrigger
onMouseEnter={() => setOpenUser(true)}
onMouseLeave={() => setOpenUser(false)}
className="w-full"
asChild
>
<div> <div>
<MenuIconButton icon="user">User</MenuIconButton> <MenuIconButton icon="user">User</MenuIconButton>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent
<div className="px-2 py-1"> onMouseEnter={() => setOpenUser(true)}
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 p-1 transition-all duration-150 hover:bg-mineshaft-700"> onMouseLeave={() => setOpenUser(false)}
<div className="p-2"> side="right"
<FontAwesomeIcon icon={faUser} className="text-mineshaft-400" /> align="end"
className="p-1"
>
<div className="cursor-default px-1 py-1">
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-gradient-to-tr from-primary-500/10 to-mineshaft-800 p-1 px-2 transition-all duration-150">
<div className="p-1 pr-3">
<FontAwesomeIcon icon={faUser} className="text-xl text-mineshaft-400" />
</div> </div>
<div className="flex flex-grow flex-col text-white"> <div className="flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize"> <div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
@ -477,11 +518,11 @@ export const MinimizedOrgSidebar = () => {
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
)} )}
<div className="mt-1 border-t border-mineshaft-600 pt-1">
<Link to="/organization/admin"> <Link to="/organization/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600"> <DropdownMenuItem>Organization Admin Console</DropdownMenuItem>
Organization Admin Console
</DropdownMenuItem>
</Link> </Link>
</div>
<div className="mt-1 h-1 border-t border-mineshaft-600" /> <div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem onClick={logOutUser} icon={<FontAwesomeIcon icon={faSignOut} />}> <DropdownMenuItem onClick={logOutUser} icon={<FontAwesomeIcon icon={faSignOut} />}>
Log Out Log Out

View File

@ -120,7 +120,7 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => {
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<Input {...field} placeholder="API Key" type="text" /> <Input {...field} placeholder="API Key" type="text" autoComplete="off" autoCorrect="off" spellCheck="false" />
</FormControl> </FormControl>
)} )}
/> />
@ -155,7 +155,7 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => {
errorText={error?.message} errorText={error?.message}
isOptional isOptional
> >
<Input {...field} placeholder="Password" type="password" /> <Input {...field} placeholder="Password" type="password" autoComplete="new-password" autoCorrect="off" spellCheck="false" aria-autocomplete="none" data-form-type="other" />
</FormControl> </FormControl>
)} )}
/> />

View File

@ -59,7 +59,12 @@ export const PitDrawer = ({
onClick={() => onSelectSnapshot(id)} onClick={() => onSelectSnapshot(id)}
> >
<div className="flex w-full justify-between"> <div className="flex w-full justify-between">
<div>{formatDistance(new Date(createdAt), new Date())}</div> <div>
{(() => {
const distance = formatDistance(new Date(createdAt), new Date());
return distance.charAt(0).toUpperCase() + distance.slice(1) + " ago";
})()}
</div>
<div>{getButtonLabel(i === 0 && index === 0, snapshotId === id)}</div> <div>{getButtonLabel(i === 0 && index === 0, snapshotId === id)}</div>
</div> </div>
</Button> </Button>
@ -70,7 +75,7 @@ export const PitDrawer = ({
<Button <Button
className="mt-8 px-4 py-3 text-sm" className="mt-8 px-4 py-3 text-sm"
isFullWidth isFullWidth
variant="star" variant="outline_bg"
isLoading={isFetchingNextPage} isLoading={isFetchingNextPage}
isDisabled={isFetchingNextPage || !hasNextPage} isDisabled={isFetchingNextPage || !hasNextPage}
onClick={fetchNextPage} onClick={fetchNextPage}

View File

@ -1,11 +1,11 @@
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons"; import { faCircleQuestion, faEye } from "@fortawesome/free-regular-svg-icons";
import { import {
faArrowRotateRight,
faCheckCircle, faCheckCircle,
faCircle,
faCircleDot,
faClock, faClock,
faEyeSlash,
faPlus, faPlus,
faShare, faShare,
faTag, faTag,
@ -236,12 +236,11 @@ export const SecretDetailSidebar = ({
}} }}
isOpen={isOpen} isOpen={isOpen}
> >
<DrawerContent title="Secret"> <DrawerContent title={`Secret ${secret?.key}`} className="thin-scrollbar">
<form onSubmit={handleSubmit(handleFormSubmit)} className="h-full"> <form onSubmit={handleSubmit(handleFormSubmit)} className="h-full">
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<FormControl label="Key"> <div className="flex flex-row">
<Input isDisabled {...register("key")} /> <div className="w-full">
</FormControl>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { a={subject(ProjectPermissionSub.Secrets, {
@ -273,7 +272,29 @@ export const SecretDetailSidebar = ({
/> />
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<div className="mb-2 border-b border-mineshaft-600 pb-4"> </div>
<div className="ml-1 mt-1.5 flex items-center">
<Button
className="w-full px-2 py-[0.43rem] font-normal"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => {
const value = secret?.valueOverride ?? secret?.value;
if (value) {
handleSecretShare(value);
}
}}
>
Share
</Button>
</div>
</div>
<div className="mb-2 rounded border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
<div className="mb-4 px-4">
<Controller
control={control}
name="skipMultilineEncoding"
render={({ field: { value, onChange, onBlur } }) => (
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { a={subject(ProjectPermissionSub.Secrets, {
@ -284,23 +305,69 @@ export const SecretDetailSidebar = ({
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<div className="flex items-center justify-between">
<span className="w-max text-sm text-mineshaft-300">
Multi-line encoding
<Tooltip
content="When enabled, multiline secrets will be handled by escaping newlines and enclosing the entire value in double quotes."
className="z-[100]"
>
<FontAwesomeIcon icon={faCircleQuestion} className="ml-2" />
</Tooltip>
</span>
<Switch
id="skipmultiencoding-option"
onCheckedChange={(isChecked) => onChange(isChecked)}
isChecked={value}
onBlur={onBlur}
isDisabled={!isAllowed}
className="items-center justify-between"
/>
</div>
)}
</ProjectPermissionCan>
)}
/>
</div>
<div
className={`mb-4 w-full border-t border-mineshaft-600 ${isOverridden ? "block" : "hidden"}`}
/>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<div className="flex items-center justify-between px-4 pb-4">
<span className="w-max text-sm text-mineshaft-300">
Override with a personal value
<Tooltip
content="Override the secret value with a personal value that does not get shared with other users and machines."
className="z-[100]"
>
<FontAwesomeIcon icon={faCircleQuestion} className="ml-2" />
</Tooltip>
</span>
<Switch <Switch
isDisabled={!isAllowed} isDisabled={!isAllowed}
id="personal-override" id="personal-override"
onCheckedChange={handleOverrideClick} onCheckedChange={handleOverrideClick}
isChecked={isOverridden} isChecked={isOverridden}
> className="justify-start"
Override with a personal value />
</Switch> </div>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div>
{isOverridden && ( {isOverridden && (
<Controller <Controller
name="valueOverride" name="valueOverride"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<FormControl label="Value Override"> <FormControl label="Override Value" className="px-4">
<InfisicalSecretInput <InfisicalSecretInput
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
environment={environment} environment={environment}
@ -312,7 +379,129 @@ export const SecretDetailSidebar = ({
)} )}
/> />
)} )}
<FormControl label="Metadata"> </div>
<div className="mb-4 mt-2 flex flex-col rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
<div
className={`flex justify-between px-4 text-mineshaft-100 ${tagFields.fields.length > 0 ? "flex-col" : "flex-row"}`}
>
<div
className={`text-sm text-mineshaft-300 ${tagFields.fields.length > 0 ? "mb-2" : "mt-0.5"}`}
>
Tags
</div>
<div>
<FormControl>
<div
className={`grid auto-cols-min grid-flow-col gap-2 overflow-hidden ${tagFields.fields.length > 0 ? "pt-2" : ""}`}
>
{tagFields.fields.map(({ tagColor, id: formId, slug, id }) => (
<Tag
className="flex w-min items-center space-x-2"
key={formId}
onClose={() => {
if (cannotEditSecret) {
createNotification({ type: "error", text: "Access denied" });
return;
}
const tag = tags?.find(({ id: tagId }) => id === tagId);
if (tag) handleTagSelect(tag);
}}
>
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: tagColor || "#bec2c8" }}
/>
<div className="text-sm">{slug}</div>
</Tag>
))}
<DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="add"
variant="outline_bg"
size="xs"
className="rounded-md"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faPlus} />
</IconButton>
</DropdownMenuTrigger>
)}
</ProjectPermissionCan>
<DropdownMenuContent align="start" side="right" className="z-[100]">
<DropdownMenuLabel className="pl-2">
Add tags to this secret
</DropdownMenuLabel>
{tags.map((tag) => {
const { id: tagId, slug, color } = tag;
const isSelected = selectedTagsGroupById?.[tagId];
return (
<DropdownMenuItem
onClick={() => handleTagSelect(tag)}
key={tagId}
icon={isSelected && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center">
<div
className="mr-2 h-2 w-2 rounded-full"
style={{ background: color || "#bec2c8" }}
/>
{slug}
</div>
</DropdownMenuItem>
);
})}
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Tags}
>
{(isAllowed) => (
<div className="p-2">
<Button
size="xs"
className="w-full"
colorSchema="primary"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faTag} />}
onClick={onCreateTag}
isDisabled={!isAllowed}
>
Create a tag
</Button>
</div>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
</FormControl>
</div>
</div>
<div
className={`mb-4 w-full border-t border-mineshaft-600 ${tagFields.fields.length > 0 || metadataFormFields.fields.length > 0 ? "block" : "hidden"}`}
/>
<div
className={`flex justify-between px-4 text-mineshaft-100 ${metadataFormFields.fields.length > 0 ? "flex-col" : "flex-row"}`}
>
<div
className={`text-sm text-mineshaft-300 ${metadataFormFields.fields.length > 0 ? "mb-2" : "mt-0.5"}`}
>
Metadata
</div>
<FormControl>
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => ( {metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
<div key={metadataFieldId} className="flex items-end space-x-2"> <div key={metadataFieldId} className="flex items-end space-x-2">
@ -364,114 +553,32 @@ export const SecretDetailSidebar = ({
</IconButton> </IconButton>
</div> </div>
))} ))}
<div className="mt-2"> <div className={`${metadataFormFields.fields.length > 0 ? "pt-2" : ""}`}>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
variant="outline_bg"
onClick={() => metadataFormFields.append({ key: "", value: "" })}
>
Add Key
</Button>
</div>
</div>
</FormControl>
<FormControl label="Tags" className="">
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
{tagFields.fields.map(({ tagColor, id: formId, slug, id }) => (
<Tag
className="flex w-min items-center space-x-2"
key={formId}
onClose={() => {
if (cannotEditSecret) {
createNotification({ type: "error", text: "Access denied" });
return;
}
const tag = tags?.find(({ id: tagId }) => id === tagId);
if (tag) handleTagSelect(tag);
}}
>
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: tagColor || "#bec2c8" }}
/>
<div className="text-sm">{slug}</div>
</Tag>
))}
<DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<DropdownMenuTrigger asChild>
<IconButton <IconButton
ariaLabel="add" ariaLabel="Add Key"
variant="outline_bg" variant="outline_bg"
size="xs" size="xs"
className="rounded-md" className="rounded-md"
isDisabled={!isAllowed} onClick={() => metadataFormFields.append({ key: "", value: "" })}
> >
<FontAwesomeIcon icon={faPlus} /> <FontAwesomeIcon icon={faPlus} />
</IconButton> </IconButton>
</DropdownMenuTrigger>
)}
</ProjectPermissionCan>
<DropdownMenuContent align="end" className="z-[100]">
<DropdownMenuLabel>Add tags to this secret</DropdownMenuLabel>
{tags.map((tag) => {
const { id: tagId, slug, color } = tag;
const isSelected = selectedTagsGroupById?.[tagId];
return (
<DropdownMenuItem
onClick={() => handleTagSelect(tag)}
key={tagId}
icon={isSelected && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center">
<div
className="mr-2 h-2 w-2 rounded-full"
style={{ background: color || "#bec2c8" }}
/>
{slug}
</div> </div>
</DropdownMenuItem>
);
})}
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Tags}
>
{(isAllowed) => (
<DropdownMenuItem asChild>
<Button
size="xs"
className="w-full"
colorSchema="primary"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faTag} />}
onClick={onCreateTag}
isDisabled={!isAllowed}
>
Create a tag
</Button>
</DropdownMenuItem>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
</FormControl> </FormControl>
<FormControl label="Reminder"> </div>
</div>
<FormControl label="Comments & Notes">
<TextArea
className="border border-mineshaft-600 bg-bunker-800 text-sm"
{...register("comment")}
readOnly={isReadOnly}
rows={5}
/>
</FormControl>
<FormControl>
{secretReminderRepeatDays && secretReminderRepeatDays > 0 ? ( {secretReminderRepeatDays && secretReminderRepeatDays > 0 ? (
<div className="ml-1 mt-2 flex items-center justify-between"> <div className="flex items-center justify-between px-2">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<FontAwesomeIcon className="text-primary-500" icon={faClock} /> <FontAwesomeIcon className="text-primary-500" icon={faClock} />
<span className="text-sm text-bunker-300"> <span className="text-sm text-bunker-300">
@ -490,9 +597,9 @@ export const SecretDetailSidebar = ({
</div> </div>
</div> </div>
) : ( ) : (
<div className="ml-1 mt-2 flex items-center space-x-2"> <div className="ml-1 flex items-center space-x-2">
<Button <Button
className="w-full px-2 py-1" className="w-full px-2 py-2 font-normal"
variant="outline_bg" variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faClock} />} leftIcon={<FontAwesomeIcon icon={faClock} />}
onClick={() => setCreateReminderFormOpen.on()} onClick={() => setCreateReminderFormOpen.on()}
@ -503,92 +610,147 @@ export const SecretDetailSidebar = ({
</div> </div>
)} )}
</FormControl> </FormControl>
<FormControl label="Comments & Notes"> <div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
<TextArea <div className="mb-2 pl-1">Version History</div>
className="border border-mineshaft-600 text-sm" <div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
{...register("comment")} {secretVersion?.map(({ createdAt, secretValue, version, id }) => (
readOnly={isReadOnly} <div className="flex flex-row">
rows={5} <div key={id} className="flex w-full flex-col space-y-1">
/> <div className="flex items-center">
</FormControl> <div className="w-10">
<div className="my-2 mb-4 border-b border-mineshaft-600 pb-4"> <div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
<Controller v{version}
control={control}
name="skipMultilineEncoding"
render={({ field: { value, onChange, onBlur } }) => (
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<Switch
id="skipmultiencoding-option"
onCheckedChange={(isChecked) => onChange(isChecked)}
isChecked={value}
onBlur={onBlur}
isDisabled={!isAllowed}
className="items-center"
>
Multi line encoding
<Tooltip
content="When enabled, multiline secrets will be handled by escaping newlines and enclosing the entire value in double quotes."
className="z-[100]"
>
<FontAwesomeIcon icon={faCircleQuestion} className="ml-1" size="sm" />
</Tooltip>
</Switch>
)}
</ProjectPermissionCan>
)}
/>
</div> </div>
<div className="ml-1 flex items-center space-x-4">
<Button
className="w-full px-2 py-1"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => {
const value = secret?.valueOverride ?? secret?.value;
if (value) {
handleSecretShare(value);
}
}}
>
Share Secret
</Button>
</div>
<div className="dark mb-4 mt-4 flex-grow text-sm text-bunker-300">
<div className="mb-2">Version History</div>
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
{secretVersion?.map(({ createdAt, secretValue, id }, i) => (
<div key={id} className="flex flex-col space-y-1">
<div className="flex items-center space-x-2">
<div>
<FontAwesomeIcon icon={i === 0 ? faCircleDot : faCircle} size="sm" />
</div> </div>
<div>{format(new Date(createdAt), "Pp")}</div> <div>{format(new Date(createdAt), "Pp")}</div>
</div> </div>
<div className="ml-1.5 flex items-center space-x-2 border-l border-bunker-300 pl-4"> <div className="flex w-full cursor-default">
<div className="self-start rounded-sm bg-primary-500/30 px-1">Value:</div> <div className="relative w-10">
<div className="break-all font-mono">{secretValue}</div> <div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
</div>
<div className="flex flex-row">
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
Value:
</div>
<div className="group break-all pl-1 font-mono">
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
<button
type="button"
className="select-none"
onClick={(e) => {
navigator.clipboard.writeText(secretValue || "");
const target = e.currentTarget;
target.style.borderBottom = "1px dashed";
target.style.paddingBottom = "-1px";
// Create and insert popup
const popup = document.createElement("div");
popup.className =
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
popup.textContent = "Copied!";
target.parentElement?.appendChild(popup);
// Remove popup and border after delay
setTimeout(() => {
popup.remove();
target.style.borderBottom = "none";
}, 3000);
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
navigator.clipboard.writeText(secretValue || "");
const target = e.currentTarget;
target.style.borderBottom = "1px dashed";
target.style.paddingBottom = "-1px";
// Create and insert popup
const popup = document.createElement("div");
popup.className =
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
popup.textContent = "Copied!";
target.parentElement?.appendChild(popup);
// Remove popup and border after delay
setTimeout(() => {
popup.remove();
target.style.borderBottom = "none";
}, 3000);
}
}}
>
{secretValue}
</button>
<button
type="button"
className="ml-1 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
e.currentTarget
.closest(".group")
?.classList.remove("show-value");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
e.currentTarget
.closest(".group")
?.classList.remove("show-value");
}
}}
>
<FontAwesomeIcon icon={faEyeSlash} />
</button>
</div>
<span className="group-[.show-value]:hidden">
{secretValue?.replace(/./g, "*")}
<button
type="button"
className="ml-1 cursor-pointer"
onClick={(e) => {
e.currentTarget.closest(".group")?.classList.add("show-value");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.currentTarget
.closest(".group")
?.classList.add("show-value");
}
}}
>
<FontAwesomeIcon icon={faEye} />
</button>
</span>
</div>
</div>
</div>
</div>
<div
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
>
<Tooltip content="Restore Secret Value">
<IconButton
ariaLabel="Restore"
variant="outline_bg"
size="sm"
className="h-8 w-8 rounded-md"
onClick={() => setValue("value", secretValue)}
>
<FontAwesomeIcon icon={faArrowRotateRight} />
</IconButton>
</Tooltip>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
<div className="dark mb-4 flex-grow text-sm text-bunker-300"> <div className="dark mb-4 flex-grow text-sm text-bunker-300">
<div className="mb-2"> <div className="mb-2 mt-4">
Access List Access List
<Tooltip <Tooltip
content="Lists all users, machine identities, and groups that have been granted any permission level (read, create, edit, or delete) for this secret." content="Lists all users, machine identities, and groups that have been granted any permission level (read, create, edit, or delete) for this secret."
className="z-[100]" className="z-[100]"
> >
<FontAwesomeIcon icon={faCircleQuestion} className="ml-1" size="sm" /> <FontAwesomeIcon icon={faCircleQuestion} className="ml-2" />
</Tooltip> </Tooltip>
</div> </div>
{isPending && ( {isPending && (
@ -608,14 +770,22 @@ export const SecretDetailSidebar = ({
</Button> </Button>
)} )}
{!isPending && secretAccessList && ( {!isPending && secretAccessList && (
<div className="flex max-h-72 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]"> <div className="mb-4 flex max-h-72 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
{secretAccessList.users.length > 0 && ( {secretAccessList.users.length > 0 && (
<div className="pb-3"> <div className="pb-3">
<div className="mb-2 font-bold">Users</div> <div className="mb-2 font-bold">Users</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{secretAccessList.users.map((user) => ( {secretAccessList.users.map((user) => (
<div className="rounded-md bg-bunker-500 px-1"> <div className="rounded-md bg-bunker-500">
<Tooltip content={user.allowedActions.join(", ")} className="z-[100]"> <Tooltip
content={user.allowedActions
.map(
(action) =>
action.charAt(0).toUpperCase() + action.slice(1).toLowerCase()
)
.join(", ")}
className="z-[100]"
>
<Link <Link
to={ to={
`/${ProjectType.SecretManager}/$projectId/members/$membershipId` as const `/${ProjectType.SecretManager}/$projectId/members/$membershipId` as const
@ -624,7 +794,7 @@ export const SecretDetailSidebar = ({
projectId: currentWorkspace.id, projectId: currentWorkspace.id,
membershipId: user.membershipId membershipId: user.membershipId
}} }}
className="text-secondary/80 text-sm hover:text-primary" className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-primary"
> >
{user.name} {user.name}
</Link> </Link>
@ -639,9 +809,14 @@ export const SecretDetailSidebar = ({
<div className="mb-2 font-bold">Identities</div> <div className="mb-2 font-bold">Identities</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{secretAccessList.identities.map((identity) => ( {secretAccessList.identities.map((identity) => (
<div className="rounded-md bg-bunker-500 px-1"> <div className="rounded-md bg-bunker-500">
<Tooltip <Tooltip
content={identity.allowedActions.join(", ")} content={identity.allowedActions
.map(
(action) =>
action.charAt(0).toUpperCase() + action.slice(1).toLowerCase()
)
.join(", ")}
className="z-[100]" className="z-[100]"
> >
<Link <Link
@ -652,7 +827,7 @@ export const SecretDetailSidebar = ({
projectId: currentWorkspace.id, projectId: currentWorkspace.id,
identityId: identity.id identityId: identity.id
}} }}
className="text-secondary/80 text-sm hover:text-primary" className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-primary"
> >
{identity.name} {identity.name}
</Link> </Link>
@ -667,9 +842,14 @@ export const SecretDetailSidebar = ({
<div className="mb-2 font-bold">Groups</div> <div className="mb-2 font-bold">Groups</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{secretAccessList.groups.map((group) => ( {secretAccessList.groups.map((group) => (
<div className="rounded-md bg-bunker-500 px-1"> <div className="rounded-md bg-bunker-500">
<Tooltip <Tooltip
content={group.allowedActions.join(", ")} content={group.allowedActions
.map(
(action) =>
action.charAt(0).toUpperCase() + action.slice(1).toLowerCase()
)
.join(", ")}
className="z-[100]" className="z-[100]"
> >
<Link <Link
@ -677,7 +857,7 @@ export const SecretDetailSidebar = ({
params={{ params={{
groupId: group.id groupId: group.id
}} }}
className="text-secondary/80 text-sm hover:text-primary" className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-primary"
> >
{group.name} {group.name}
</Link> </Link>
@ -691,7 +871,7 @@ export const SecretDetailSidebar = ({
)} )}
</div> </div>
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
<div className="mb-2 flex items-center space-x-4"> <div className="mb-4 flex items-center space-x-4">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { a={subject(ProjectPermissionSub.Secrets, {
@ -705,6 +885,7 @@ export const SecretDetailSidebar = ({
<Button <Button
isFullWidth isFullWidth
type="submit" type="submit"
variant="outline_bg"
isDisabled={isSubmitting || !isDirty || !isAllowed} isDisabled={isSubmitting || !isDirty || !isAllowed}
isLoading={isSubmitting} isLoading={isSubmitting}
> >
@ -722,9 +903,17 @@ export const SecretDetailSidebar = ({
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<Button colorSchema="danger" isDisabled={!isAllowed} onClick={onDeleteSecret}> <IconButton
Delete colorSchema="danger"
</Button> ariaLabel="Delete Secret"
className="border border-mineshaft-600 bg-mineshaft-700 hover:border-red-500/70 hover:bg-red-600/20"
isDisabled={!isAllowed}
onClick={onDeleteSecret}
>
<Tooltip content="Delete Secret">
<FontAwesomeIcon icon={faTrash} />
</Tooltip>
</IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>