Merge branch 'Infisical:main' into feat/create-multiple-orgs-under-same-account

This commit is contained in:
Ronit Panda
2023-10-24 05:42:31 +05:30
committed by GitHub
126 changed files with 2887 additions and 1714 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ module.exports = {
env: {
browser: true,
es2021: true,
"es6": true
es6: true
},
extends: [
"airbnb",
+3 -5
View File
@@ -1,8 +1,4 @@
// @ts-check
/**
* @type {import('next').NextConfig}
**/
const path = require("path");
const ContentSecurityPolicy = `
@@ -53,7 +49,9 @@ const securityHeaders = [
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
}
];
/**
* @type {import('next').NextConfig}
**/
module.exports = {
output: "standalone",
i18n: {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "npm-proj-1695919945735-0.225773463026700768rr1Oh",
"name": "frontend",
"lockfileVersion": 2,
"requires": true,
"packages": {
+40 -38
View File
@@ -6,74 +6,75 @@ const integrationSlugNameMapping: Mapping = {
"azure-key-vault": "Azure Key Vault",
"aws-parameter-store": "AWS Parameter Store",
"aws-secret-manager": "AWS Secrets Manager",
"heroku": "Heroku",
"vercel": "Vercel",
"netlify": "Netlify",
"github": "GitHub",
"gitlab": "GitLab",
"render": "Render",
heroku: "Heroku",
vercel: "Vercel",
netlify: "Netlify",
github: "GitHub",
gitlab: "GitLab",
render: "Render",
"laravel-forge": "Laravel Forge",
"railway": "Railway",
"flyio": "Fly.io",
"circleci": "CircleCI",
"travisci": "TravisCI",
"supabase": "Supabase",
"checkly": "Checkly",
"qovery": "Qovery",
railway: "Railway",
flyio: "Fly.io",
circleci: "CircleCI",
travisci: "TravisCI",
supabase: "Supabase",
checkly: "Checkly",
qovery: "Qovery",
"terraform-cloud": "Terraform Cloud",
"teamcity": "TeamCity",
teamcity: "TeamCity",
"hashicorp-vault": "Vault",
"cloudflare-pages": "Cloudflare Pages",
"codefresh": "Codefresh",
codefresh: "Codefresh",
"digital-ocean-app-platform": "Digital Ocean App Platform",
"bitbucket": "BitBucket",
bitbucket: "BitBucket",
"cloud-66": "Cloud 66",
"northflank": "Northflank",
"windmill": "Windmill",
"gcp-secret-manager": "GCP Secret Manager"
}
northflank: "Northflank",
windmill: "Windmill",
"gcp-secret-manager": "GCP Secret Manager",
"hasura-cloud": "Hasura Cloud"
};
const envMapping: Mapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
Testing: "test"
};
const reverseEnvMapping: Mapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
test: "Testing"
};
const contextNetlifyMapping: Mapping = {
"dev": "Local development",
dev: "Local development",
"branch-deploy": "Branch deploys",
"deploy-preview": "Deploy Previews",
"production": "Production"
}
production: "Production"
};
const reverseContextNetlifyMapping: Mapping = {
"Local development": "dev",
"Branch deploys": "branch-deploy",
"Deploy Previews": "deploy-preview",
"Production": "production"
}
Production: "production"
};
const plansDev: Mapping = {
"starter": "prod_Mb4ATFT5QAHoPM",
"team": "prod_NEpD2WMXUS2eDn",
"professional": "prod_Mb4CetZ2jE7jdl",
"enterprise": "licence_key_required"
}
starter: "prod_Mb4ATFT5QAHoPM",
team: "prod_NEpD2WMXUS2eDn",
professional: "prod_Mb4CetZ2jE7jdl",
enterprise: "licence_key_required"
};
const plansProd: Mapping = {
"starter": "prod_Mb8oR5XNwyFTul",
"team": "prod_NEp7fAB3UJWK6A",
"professional": "prod_Mb8pUIpA0OUi5N",
"enterprise": "licence_key_required"
}
starter: "prod_Mb8oR5XNwyFTul",
team: "prod_NEp7fAB3UJWK6A",
professional: "prod_Mb8pUIpA0OUi5N",
enterprise: "licence_key_required"
};
const plans = plansProd || plansDev;
@@ -83,4 +84,5 @@ export {
integrationSlugNameMapping,
plans,
reverseContextNetlifyMapping,
reverseEnvMapping}
reverseEnvMapping
};
@@ -0,0 +1,11 @@
<svg width="81" height="84" viewBox="-20 -20 121 124" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5273_21928)">
<path d="M79.7186 28.6019C82.1218 21.073 80.6778 6.03601 76.0158 0.487861C75.4073 -0.238064 74.2624 -0.134361 73.757 0.664158L68.0121 9.72786C66.5887 11.5427 64.0308 11.9575 62.1124 10.6923C55.8827 6.59601 48.4359 4.21082 40.4322 4.21082C32.4285 4.21082 24.9817 6.59601 18.752 10.6923C16.8336 11.9575 14.2757 11.5323 12.8523 9.72786L7.10738 0.664158C6.60199 -0.134361 5.45712 -0.238064 4.84859 0.487861C0.186621 6.03601 -1.25735 21.073 1.14583 28.6019C1.94002 31.1012 2.16693 33.7456 1.69248 36.3279C1.22834 38.879 0.753897 41.9693 0.753897 44.1056C0.753897 66.1323 18.5251 84.0004 40.4322 84.0004C62.3497 84.0004 80.1105 66.1427 80.1105 44.1056C80.1105 41.959 79.6464 38.879 79.1719 36.3279C78.6975 33.7456 78.9244 31.1012 79.7186 28.6019ZM40.4322 75.0819C23.4965 75.0819 9.71684 61.2271 9.71684 44.199C9.71684 43.639 9.73747 43.0893 9.7581 42.5397C10.3769 30.9353 17.3802 21.0108 27.3024 16.2819C31.2836 14.3738 35.7393 13.316 40.4322 13.316C45.1251 13.316 49.5808 14.3842 53.5724 16.2923C63.4945 21.0212 70.4978 30.9456 71.1166 42.5397C71.1476 43.0893 71.1579 43.639 71.1579 44.199C71.1476 61.2271 57.3679 75.0819 40.4322 75.0819Z" fill="#1EB4D4"/>
<path d="M53.7371 56.083L45.8881 42.4044L39.153 30.997C38.9983 30.7274 38.7095 30.5615 38.3898 30.5615H31.9538C31.634 30.5615 31.3452 30.7378 31.1905 31.0074C31.0358 31.2874 31.0358 31.6296 31.2008 31.8993L37.6368 42.7881L28.9936 56.0415C28.8183 56.3111 28.7977 56.6637 28.9524 56.9541C29.1071 57.2444 29.4062 57.4207 29.7259 57.4207H36.2032C36.5023 57.4207 36.7808 57.2652 36.9458 57.0163L41.6181 49.6741L45.8056 56.9748C45.9603 57.2548 46.2594 57.4207 46.5688 57.4207H52.9533C53.273 57.4207 53.5618 57.2548 53.7165 56.9748C53.9022 56.6948 53.9022 56.363 53.7371 56.083Z" fill="#1EB4D4"/>
</g>
<defs>
<clipPath id="clip0_5273_21928">
<rect width="81" height="84" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
scripts/replace-standalone-build-variable.sh "$BAKED_NEXT_PUBLIC_POSTHOG_API_KEY" "$NEXT_PUBLIC_POSTHOG_API_KEY"
scripts/replace-standalone-build-variable.sh "$BAKED_NEXT_PUBLIC_INTERCOM_ID" "$NEXT_PUBLIC_INTERCOM_ID"
if [ "$TELEMETRY_ENABLED" != "false" ]; then
echo "Telemetry is enabled"
scripts/set-standalone-build-telemetry.sh true
else
echo "Client opted out of telemetry"
scripts/set-standalone-build-telemetry.sh false
fi
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
ORIGINAL=$1
REPLACEMENT=$2
if [ "${ORIGINAL}" = "${REPLACEMENT}" ]; then
echo "Environment variable replacement is the same, skipping.."
exit 0
fi
echo "Replacing pre-baked value.."
find public .next -type f -name "*.js" |
while read file; do
sed -i "s|$ORIGINAL|$REPLACEMENT|g" "$file"
done
View File
@@ -0,0 +1,8 @@
#!/bin/sh
VALUE=$1
find public .next -type f -name "*.js" |
while read file; do
sed -i "s|TELEMETRY_CAPTURING_ENABLED|$VALUE|g" "$file"
done
View File
+1 -1
View File
@@ -10,7 +10,7 @@ export const initPostHog = () => {
try {
if (typeof window !== "undefined") {
// @ts-ignore
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED) {
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED === "true") {
posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST
});
@@ -13,7 +13,7 @@ class Capturer {
}
capture(item: string) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
try {
this.api.capture(item);
} catch (error) {
@@ -23,7 +23,7 @@ class Capturer {
}
identify(id: string, email?: string) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
try {
this.api.identify(id, {
email: email
@@ -0,0 +1,61 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import type { Meta, StoryObj } from "@storybook/react";
import { Alert, AlertDescription } from "./Alert";
const meta: Meta<typeof Alert> = {
title: "Components/Alert",
component: Alert,
tags: ["v2"]
};
export default meta;
type Story = StoryObj<typeof Alert>;
const ExampleComponent = () => <AlertDescription>this is a description</AlertDescription>;
export const Default: Story = {
args: {
children: <ExampleComponent />
}
};
export const Warning: Story = {
args: {
children: <ExampleComponent />,
variant: "warning"
}
};
export const Danger: Story = {
args: {
children: <ExampleComponent />,
variant: "danger"
}
};
export const WithCustomIcon: Story = {
args: {
children: <ExampleComponent />,
variant: "warning",
icon: <FontAwesomeIcon icon={faPlus} />
}
};
export const WithOutIcon: Story = {
args: {
children: <ExampleComponent />,
variant: "warning",
icon: null
}
};
export const WithOutTitle: Story = {
args: {
children: <ExampleComponent />,
variant: "warning",
hideTitle: true
}
};
@@ -0,0 +1,85 @@
import { forwardRef } from "react";
import {
faExclamationCircle,
faExclamationTriangle,
faInfoCircle
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { type VariantProps, cva } from "cva";
import { twMerge } from "tailwind-merge";
const alertVariants = cva(
"w-full bg-mineshaft-800 rounded-lg border px-4 py-3 text-sm flex items-center gap-x-4",
{
variants: {
variant: {
default: "",
danger: "text-red border-red",
warning: "text-yellow border-yellow"
}
},
defaultVariants: {
variant: "default"
}
}
);
type AlertProps = {
title?: string;
hideTitle?: boolean;
icon?: React.ReactNode;
};
const variantTitleMap = {
default: "Info",
danger: "Danger",
warning: "Warning"
};
const variantIconMap = {
default: faInfoCircle,
danger: faExclamationCircle,
warning: faExclamationTriangle
};
const Alert = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> & AlertProps
>(({ className, variant, title, icon, hideTitle = false, children, ...props }, ref) => {
const defaultTitle = title ?? variantTitleMap[variant ?? "default"];
return (
<div
ref={ref}
role="alert"
className={twMerge(alertVariants({ variant }), className)}
{...props}
>
<div>
{typeof icon !== "undefined" ? (
<>{icon} </>
) : (
<FontAwesomeIcon className="text-lg" icon={variantIconMap[variant ?? "default"]} />
)}
</div>
<div className="flex flex-col gap-y-1">
{hideTitle ? null : (
<h5 className="font-medium leading-none tracking-tight" {...props}>
{defaultTitle}
</h5>
)}
{children}
</div>
</div>
);
});
Alert.displayName = "Alert";
const AlertDescription = forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={twMerge("text-sm [&_p]:leading-relaxed", className)} {...props} />
));
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertDescription };
@@ -0,0 +1 @@
export { Alert, AlertDescription } from "./Alert";
+1
View File
@@ -1,4 +1,5 @@
export * from "./Accordion";
export * from "./Alert";
export * from "./Button";
export * from "./Card";
export * from "./Checkbox";
+4
View File
@@ -0,0 +1,4 @@
export {
useCreateAPIKeyV2,
useDeleteAPIKeyV2,
useUpdateAPIKeyV2} from "./queries";
@@ -0,0 +1,62 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { userKeys } from "../users/queries";
import {
APIKeyDataV2,
CreateAPIKeyDataV2DTO,
CreateServiceTokenDataV3Res,
DeleteAPIKeyDataV2DTO,
UpdateAPIKeyDataV2DTO} from "./types";
export const useCreateAPIKeyV2 = () => {
const queryClient = useQueryClient();
return useMutation<CreateServiceTokenDataV3Res, {}, CreateAPIKeyDataV2DTO>({
mutationFn: async ({
name
}) => {
const { data } = await apiRequest.post("/api/v3/api-key", {
name
});
return data;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
}
});
};
export const useUpdateAPIKeyV2 = () => {
const queryClient = useQueryClient();
return useMutation<APIKeyDataV2, {}, UpdateAPIKeyDataV2DTO>({
mutationFn: async ({
apiKeyDataId,
name
}) => {
const { data: { apiKeyData } } = await apiRequest.patch(`/api/v3/api-key/${apiKeyDataId}`, {
name
});
return apiKeyData;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
}
});
};
export const useDeleteAPIKeyV2 = () => {
const queryClient = useQueryClient();
return useMutation<APIKeyDataV2, {}, DeleteAPIKeyDataV2DTO>({
mutationFn: async ({
apiKeyDataId
}) => {
const { data: { apiKeyData } } = await apiRequest.delete(`/api/v3/api-key/${apiKeyDataId}`);
return apiKeyData;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
}
});
};
+27
View File
@@ -0,0 +1,27 @@
export type APIKeyDataV2 = {
_id: string;
name: string;
user: string;
lastUsed?: string;
usageCount: number;
createdAt: string;
updatedAt: string;
};
export type CreateAPIKeyDataV2DTO = {
name: string;
}
export type CreateServiceTokenDataV3Res = {
apiKeyData: APIKeyDataV2;
apiKey: string;
}
export type UpdateAPIKeyDataV2DTO = {
apiKeyDataId: string;
name: string;
}
export type DeleteAPIKeyDataV2DTO = {
apiKeyDataId: string;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./apiKeys";
export * from "./auditLogs";
export * from "./auth";
export * from "./bots";
@@ -131,6 +131,7 @@ export const useUpdateSecretV3 = ({
mutationFn: async ({
secretPath = "/",
type,
secretId,
environment,
workspaceId,
secretName,
@@ -157,6 +158,7 @@ export const useUpdateSecretV3 = ({
environment,
type,
secretPath,
secretId,
...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment),
tags,
skipMultilineEncoding,
+1
View File
@@ -109,6 +109,7 @@ export type TUpdateSecretsV3DTO = {
skipMultilineEncoding?: boolean;
newSecretName?: string;
secretName: string;
secretId?: string;
secretValue: string;
secretComment?: string;
tags?: string[];
@@ -4,4 +4,5 @@ export {
useDeleteServiceToken,
useDeleteServiceTokenV3,
useGetUserWsServiceTokens,
useUpdateServiceTokenV3} from "./queries";
useUpdateServiceTokenV3
} from "./queries";
+1
View File
@@ -7,6 +7,7 @@ export {
useDeleteOrgMembership,
useDeleteUser,
useGetMyAPIKeys,
useGetMyAPIKeysV2,
useGetMyIp,
useGetMyOrganizationProjects,
useGetMySessions,
+17 -4
View File
@@ -7,6 +7,7 @@ import {
import { apiRequest } from "@app/config/request";
import { setAuthToken } from "@app/reactQuery";
import { APIKeyDataV2 } from "../apiKeys/types";
import { useUploadWsKey } from "../keys/queries";
import { workspaceKeys } from "../workspace/queries";
import {
@@ -24,12 +25,13 @@ import {
User
} from "./types";
const userKeys = {
export const userKeys = {
getUser: ["user"] as const,
userAction: ["user-action"] as const,
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
myIp: ["ip"] as const,
myAPIKeys: ["api-keys"] as const,
myAPIKeysV2: ["api-keys-v2"] as const,
mySessions: ["sessions"] as const,
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
};
@@ -270,7 +272,7 @@ export const useGetMyIp = () => {
});
};
export const useGetMyAPIKeys = () => {
export const useGetMyAPIKeys = () => { // TODO: deprecate (moving to API Key V2)
return useQuery({
queryKey: userKeys.myAPIKeys,
queryFn: async () => {
@@ -281,7 +283,18 @@ export const useGetMyAPIKeys = () => {
});
};
export const useCreateAPIKey = () => {
export const useGetMyAPIKeysV2 = () => {
return useQuery({
queryKey: userKeys.myAPIKeysV2,
queryFn: async () => {
const { data: { apiKeyData } } = await apiRequest.get<{ apiKeyData: APIKeyDataV2[] }>("/api/v3/users/me/api-keys");
return apiKeyData;
},
enabled: true
});
};
export const useCreateAPIKey = () => { // TODO: deprecate (moving to API Key V2)
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ name, expiresIn }: { name: string; expiresIn: number }) => {
@@ -298,7 +311,7 @@ export const useCreateAPIKey = () => {
});
};
export const useDeleteAPIKey = () => {
export const useDeleteAPIKey = () => { // TODO: deprecate (moving to API Key V2)
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (apiKeyDataId: string) => {
+1 -1
View File
@@ -726,7 +726,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
{infisicalPlatformVersion && (
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Platform Version: {infisicalPlatformVersion}
Version: {infisicalPlatformVersion}
</div>
)}
</div>
@@ -41,7 +41,6 @@ export default function FlyioAuthorizeIntegrationPage() {
const onFormSubmit = async ({
accessToken
}: FormData) => {
console.log("onFormSubmit accessToken: ", accessToken);
try {
setIsLoading(true);
@@ -0,0 +1,116 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
const schema = yup.object({
accessToken: yup.string().trim().required("Hasura Cloud Access Token is required")
});
type FormData = yup.InferType<typeof schema>;
const APP_NAME = "Hasura Cloud";
export default function HasuraCloudAuthorizeIntegrationPage() {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const { mutateAsync } = useSaveIntegrationAccessToken();
const { control, handleSubmit } = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: {
accessToken: ""
}
});
const onFormSubmit = async ({ accessToken }: FormData) => {
try {
setIsLoading(true);
const integrationAuth = await mutateAsync({
workspaceId: localStorage.getItem("projectData.id"),
integration: "hasura-cloud",
accessToken
});
setIsLoading(false);
router.push(`/integrations/hasura-cloud/create?integrationAuthId=${integrationAuth._id}`);
} catch (err) {
setIsLoading(false);
console.error(err);
}
};
return (
<div className="flex h-full w-full items-center justify-center">
<Head>
<title>Authorize {APP_NAME} Integration</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<Card className="mb-12 max-w-lg rounded-md border border-mineshaft-600">
<CardTitle
className="px-6 text-left text-xl"
subTitle="After adding your access token, you will be prompted to set up an integration for a particular Infisical project and environment."
>
<div className="flex flex-row items-center">
<div className="inline flex items-center pb-0.5">
<Image
src="/images/integrations/Hasura.svg"
height={30}
width={30}
alt={`${APP_NAME} logo`}
/>
</div>
<span className="ml-2.5">{APP_NAME} Integration </span>
<Link href="https://infisical.com/docs/integrations/cloud/hasura-cloud" passHref>
<a target="_blank" rel="noopener noreferrer">
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="ml-1.5 mb-[0.07rem] text-xxs"
/>
</div>
</a>
</Link>
</div>
</CardTitle>
<form onSubmit={handleSubmit(onFormSubmit)} className="px-6 pb-8 text-right">
<Controller
control={control}
name="accessToken"
render={({ field, fieldState: { error } }) => (
<FormControl
label={`${APP_NAME} Access Token`}
errorText={error?.message}
isError={Boolean(error)}
>
<Input {...field} placeholder="" />
</FormControl>
)}
/>
<Button
colorSchema="primary"
variant="outline_bg"
className="mt-2 w-min"
size="sm"
type="submit"
isLoading={isLoading}
>
Connect to {APP_NAME}
</Button>
</form>
</Card>
</div>
);
}
HasuraCloudAuthorizeIntegrationPage.requireAuth = true;
@@ -0,0 +1,228 @@
import { Controller, useForm } from "react-hook-form";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
import queryString from "query-string";
import * as yup from "yup";
import {
Button,
Card,
CardTitle,
FormControl,
Input,
Select,
SelectItem
} from "@app/components/v2";
import { useCreateIntegration } from "@app/hooks/api";
import {
useGetIntegrationAuthApps,
useGetIntegrationAuthById
} from "@app/hooks/api/integrationAuth";
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
const schema = yup.object({
secretPath: yup.string().trim().required("Secret path is required"),
sourceEnvironment: yup.string().trim().required("Project environment is required"),
appId: yup.string().trim().required("Hasura Cloud project is required")
});
type FormData = yup.InferType<typeof schema>;
const APP_NAME = "Hasura Cloud";
export default function HasuraCloudCreateIntegrationPage() {
const {
control,
handleSubmit,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema)
});
const router = useRouter();
const { mutateAsync } = useCreateIntegration();
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById(
(integrationAuthId as string) ?? ""
);
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } =
useGetIntegrationAuthApps({
integrationAuthId: (integrationAuthId as string) ?? ""
});
const onFormSubmit = async ({ secretPath, sourceEnvironment, appId }: FormData) => {
try {
if (!integrationAuth?._id) return;
const app = integrationAuthApps?.find((data) => data.appId === appId);
await mutateAsync({
integrationAuthId: integrationAuth?._id,
isActive: true,
sourceEnvironment,
secretPath,
appId,
app: app?.name
});
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
} catch (err) {
console.error(err);
}
};
return integrationAuth && workspace && integrationAuthApps ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<Head>
<title>Set Up {APP_NAME} Integration</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<Card className="max-w-lg rounded-md border border-mineshaft-600">
<CardTitle
className="px-6 text-left text-xl"
subTitle={`Choose which environment or folder in Infisical you want to sync to ${APP_NAME} environment variables.`}
>
<div className="flex flex-row items-center">
<div className="inline flex items-center pb-0.5">
<Image
src="/images/integrations/Hasura.svg"
height={30}
width={30}
alt={`${APP_NAME} logo`}
/>
</div>
<span className="ml-2.5">{APP_NAME} Integration </span>
<Link href="https://infisical.com/docs/integrations/cloud/flyio" passHref>
<a target="_blank" rel="noopener noreferrer">
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="ml-1.5 mb-[0.07rem] text-xxs"
/>
</div>
</a>
</Link>
</div>
</CardTitle>
<form onSubmit={handleSubmit(onFormSubmit)} className="flex w-full flex-col px-6">
<Controller
control={control}
name="sourceEnvironment"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Project Environment"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
className="w-full border border-mineshaft-500"
value={field.value}
onValueChange={(val) => {
field.onChange(val);
}}
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="secretPath"
render={({ field, fieldState: { error } }) => (
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="appId"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Hasura Cloud Project"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
className="w-full border border-mineshaft-500"
value={field.value}
isDisabled={integrationAuthApps?.length === 0}
onValueChange={(val) => {
field.onChange(val);
}}
>
{integrationAuthApps?.map((project) => (
<SelectItem value={project.appId ?? ""} key={`project-id-${project.appId}`}>
{project.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Button
type="submit"
color="mineshaft"
variant="outline_bg"
className="mb-6 mt-2 ml-auto"
isLoading={isSubmitting}
>
Create Integration
</Button>
</form>
</Card>
</div>
) : (
<div className="flex h-full w-full items-center justify-center">
<Head>
<title>Set Up {APP_NAME} Integration</title>
<link rel="icon" href="/infisical.ico" />
</Head>
{isIntegrationAuthLoading || isIntegrationAuthAppsLoading ? (
<img
src="/images/loading/loading.gif"
height={70}
width={120}
alt="infisical loading indicator"
/>
) : (
<div className="flex h-max max-w-md flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 text-center text-mineshaft-200">
<FontAwesomeIcon icon={faBugs} className="inlineli my-2 text-6xl" />
<p>
Something went wrong. Please contact{" "}
<a
className="inline cursor-pointer text-mineshaft-100 underline decoration-primary-500 underline-offset-4 opacity-80 duration-200 hover:opacity-100"
target="_blank"
rel="noopener noreferrer"
href="mailto:[email protected]"
>
support@infisical.com
</a>{" "}
if the issue persists.
</p>
</div>
)}
</div>
);
}
HasuraCloudCreateIntegrationPage.requireAuth = true;
@@ -1,6 +1,6 @@
import crypto from "crypto";
import { TCloudIntegration,UserWsKeyPair } from "@app/hooks/api/types";
import { TCloudIntegration, UserWsKeyPair } from "@app/hooks/api/types";
import {
decryptAssymmetric,
@@ -32,11 +32,10 @@ export const generateBotKey = (botPublicKey: string, latestKey: UserWsKeyPair) =
export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => {
try {
// generate CSRF token for OAuth2 code-token exchange integrations
const state = crypto.randomBytes(16).toString("hex");
localStorage.setItem("latestCSRFToken", state);
let link = "";
switch (integrationOption.slug) {
case "gcp-secret-manager":
@@ -123,6 +122,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
case "teamcity":
link = `${window.location.origin}/integrations/teamcity/authorize`;
break;
case "hasura-cloud":
link = `${window.location.origin}/integrations/hasura-cloud/authorize`;
break;
default:
break;
}
@@ -130,7 +132,6 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
if (link !== "") {
window.location.assign(link);
}
} catch (err) {
console.error(err);
}
@@ -202,6 +202,8 @@ export const IntegrationsPage = withProjectPermission(
integrations={integrations}
environments={environments}
onIntegrationDelete={({ _id: id }, cb) => handleIntegrationDelete(id, cb)}
isBotActive={bot?.isActive}
workspaceId={workspaceId}
/>
<CloudIntegrationSection
isLoading={isCloudIntegrationsLoading || isIntegrationAuthLoading}
@@ -1,9 +1,12 @@
import Link from "next/link";
import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { integrationSlugNameMapping } from "public/data/frequentConstants";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Alert,
AlertDescription,
DeleteActionModal,
EmptyState,
FormControl,
@@ -23,17 +26,22 @@ type Props = {
integrations?: TIntegration[];
isLoading?: boolean;
onIntegrationDelete: (integration: TIntegration, cb: () => void) => void;
isBotActive: boolean | undefined;
workspaceId: string;
};
export const IntegrationsSection = ({
integrations = [],
environments = [],
isLoading,
onIntegrationDelete
onIntegrationDelete,
isBotActive,
workspaceId
}: Props) => {
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteConfirmation"
] as const);
return (
<div className="mb-8">
<div className="mx-4 mb-4 mt-6 flex flex-col items-start justify-between px-2 text-xl">
@@ -45,7 +53,22 @@ export const IntegrationsSection = ({
<Skeleton className="h-28" />
</div>
)}
{!isLoading && !integrations.length && (
{!isBotActive && (
<div className="px-6 py-4">
<Alert hideTitle variant="warning">
<AlertDescription>
All the active integrations will be disabled. Disable End-to-End Encryption in{" "}
<Link href={`/project/${workspaceId}/settings`} passHref>
<a className="underline underline-offset-2">project settings </a>
</Link>
to re-enable it .
</AlertDescription>
</Alert>
</div>
)}
{!isLoading && !integrations.length && isBotActive && (
<div className="mx-6">
<EmptyState
className="rounded-md border border-mineshaft-700 pt-8 pb-4"
@@ -53,7 +76,7 @@ export const IntegrationsSection = ({
/>
</div>
)}
{!isLoading && (
{!isLoading && isBotActive && (
<div className="flex flex-col space-y-4 p-6 pt-0">
{integrations?.map((integration) => (
<div
@@ -153,6 +153,7 @@ export const SecretListView = ({
workspaceId,
secretPath,
secretName: key,
secretId,
secretValue: value || "",
type,
latestFileKey: decryptFileKey,
@@ -201,11 +202,14 @@ export const SecretListView = ({
try {
// personal secret change
if (overrideAction === "deleted") {
await handleSecretOperation("delete", "personal", oldKey);
await handleSecretOperation("delete", "personal", oldKey, {
secretId: orgSecret.idOverride
});
} else if (overrideAction && idOverride) {
await handleSecretOperation("update", "personal", oldKey, {
value: valueOverride,
newKey: hasKeyChanged ? key : undefined,
secretId: orgSecret.idOverride,
skipMultilineEncoding: modSecret.skipMultilineEncoding
});
} else if (overrideAction) {
@@ -218,6 +222,7 @@ export const SecretListView = ({
value,
tags: tagIds,
comment,
secretId: orgSecret._id,
newKey: hasKeyChanged ? key : undefined,
skipMultilineEncoding: modSecret.skipMultilineEncoding
});
@@ -308,7 +313,6 @@ export const SecretListView = ({
>
{namespace}
</div>
{filteredSecrets.map((secret) => (
<SecretItem
environment={environment}
@@ -146,12 +146,13 @@ export const SecretOverviewPage = () => {
}
};
const handleSecretUpdate = async (env: string, key: string, value: string) => {
const handleSecretUpdate = async (env: string, key: string, value: string, secretId?: string) => {
try {
await updateSecretV3({
environment: env,
workspaceId,
secretPath,
secretId,
secretName: key,
secretValue: value,
type: "shared",
@@ -242,7 +243,6 @@ export const SecretOverviewPage = () => {
);
const canViewOverviewPage = Boolean(userAvailableEnvs.length);
const filteredSecretNames = secKeys
?.filter((name) => name.toUpperCase().includes(searchFilter.toUpperCase()))
.sort((a, b) => (sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a)));
@@ -18,7 +18,7 @@ type Props = {
environment: string;
secretPath: string;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
};
@@ -42,7 +42,7 @@ export const SecretEditRow = ({
formState: { isDirty, isSubmitting }
} = useForm({
values: {
value: defaultValue
value: defaultValue || null
}
});
const [isDeleting, setIsDeleting] = useToggle();
@@ -70,7 +70,7 @@ export const SecretEditRow = ({
if (isCreatable) {
await onSecretCreate(environment, secretName, value);
} else {
await onSecretUpdate(environment, secretName, value);
await onSecretUpdate(environment, secretName, value, secretId);
}
}
reset({ value });
@@ -80,7 +80,7 @@ export const SecretEditRow = ({
setIsDeleting.on();
try {
await onSecretDelete(environment, secretName, secretId);
reset({ value: undefined });
reset({ value: null });
} finally {
setIsDeleting.off();
}
@@ -23,7 +23,7 @@ type Props = {
expandableColWidth: number;
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
};
@@ -143,8 +143,8 @@ export const SSOModal = ({
entityId: "Identifier (Entity ID)",
entryPoint: "Login URL",
entryPointPlaceholder: "https://login.microsoftonline.com/xxx/saml2",
issuer: "Azure AD Identifier",
issuerPlaceholder: "https://sts.windows.net/xxx/"
issuer: "Azure Application ID",
issuerPlaceholder: "abc-def-ghi-jkl-mno"
});
case AuthProvider.JUMPCLOUD_SAML:
return ({
@@ -0,0 +1,199 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
Button,
FormControl,
IconButton,
Input,
Modal,
ModalContent} from "@app/components/v2";
import { useToggle } from "@app/hooks";
import {
useCreateAPIKeyV2,
useUpdateAPIKeyV2
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = yup.object({
name: yup.string().required("API Key V2 name is required")
}).required();
export type FormData = yup.InferType<typeof schema>;
type Props = {
popUp: UsePopUpState<["apiKeyV2"]>;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["apiKeyV2"]>, state?: boolean) => void;
};
export const APIKeyV2Modal = ({
popUp,
handlePopUpToggle
}: Props) => {
const [newAPIKey, setNewAPIKey] = useState("");
const [isAPIKeyCopied, setIsAPIKeyCopied] = useToggle(false);
const { createNotification } = useNotificationContext();
const { mutateAsync: createMutateAsync } = useCreateAPIKeyV2();
const { mutateAsync: updateMutateAsync } = useUpdateAPIKeyV2();
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: {
name: ""
}
});
useEffect(() => {
let timer: NodeJS.Timeout;
if (isAPIKeyCopied) {
timer = setTimeout(() => setIsAPIKeyCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [setIsAPIKeyCopied]);
useEffect(() => {
const apiKeyData = popUp?.apiKeyV2?.data as {
apiKeyDataId: string;
name: string;
};
if (apiKeyData) {
reset({
name: apiKeyData.name
});
} else {
reset({
name: ""
});
}
}, [popUp?.apiKeyV2?.data]);
const copyTokenToClipboard = () => {
navigator.clipboard.writeText(newAPIKey);
setIsAPIKeyCopied.on();
};
const onFormSubmit = async ({
name
}: FormData) => {
try {
const apiKeyData = popUp?.apiKeyV2?.data as {
apiKeyDataId: string;
name: string;
};
if (apiKeyData) {
// update
await updateMutateAsync({
apiKeyDataId: apiKeyData.apiKeyDataId,
name
});
handlePopUpToggle("apiKeyV2", false);
} else {
// create
const { apiKey } = await createMutateAsync({
name
});
setNewAPIKey(apiKey);
}
createNotification({
text: `Successfully ${popUp?.apiKeyV2?.data ? "updated" : "created"} API Key`,
type: "success"
});
reset();
} catch (err) {
console.error(err);
createNotification({
text: `Failed to ${popUp?.apiKeyV2?.data ? "updated" : "created"} API Key`,
type: "error"
});
}
}
const hasAPIKey = Boolean(newAPIKey);
return (
<Modal
isOpen={popUp?.apiKeyV2?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("apiKeyV2", isOpen);
reset();
setNewAPIKey("");
}}
>
<ModalContent title={`${popUp?.apiKeyV2?.data ? "Update" : "Create"} API Key V2`}>
{!hasAPIKey ? (
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="My API Key"
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{popUp?.apiKeyV2?.data ? "Update" : "Create"}
</Button>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
) : (
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{newAPIKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={copyTokenToClipboard}
>
<FontAwesomeIcon icon={isAPIKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Click to copy
</span>
</IconButton>
</div>
)}
</ModalContent>
</Modal>
);
}
@@ -0,0 +1,81 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
Button,
DeleteActionModal
} from "@app/components/v2";
import { useDeleteAPIKeyV2 } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { APIKeyV2Modal } from "./APIKeyV2Modal";
import { APIKeyV2Table } from "./APIKeyV2Table";
export const APIKeyV2Section = () => {
const { createNotification } = useNotificationContext();
const { mutateAsync: deleteMutateAsync } = useDeleteAPIKeyV2();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"apiKeyV2",
"deleteAPIKeyV2"
] as const);
const onDeleteAPIKeyDataSubmit = async (apiKeyDataId: string) => {
try {
await deleteMutateAsync({
apiKeyDataId
});
createNotification({
text: "Successfully deleted API Key V2",
type: "success"
});
handlePopUpClose("deleteAPIKeyV2");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete API Key V2",
type: "error"
});
}
}
return (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="flex justify-between mb-8">
<p className="text-xl font-semibold text-mineshaft-100">
API Keys V2 (Beta)
</p>
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("apiKeyV2")}
>
Add API Key
</Button>
</div>
<APIKeyV2Table
handlePopUpOpen={handlePopUpOpen}
/>
<APIKeyV2Modal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
/>
<DeleteActionModal
isOpen={popUp.deleteAPIKeyV2.isOpen}
title={`Are you sure want to delete ${
(popUp?.deleteAPIKeyV2?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("deleteAPIKeyV2", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onDeleteAPIKeyDataSubmit(
(popUp?.deleteAPIKeyV2?.data as { apiKeyDataId: string })?.apiKeyDataId
)
}
/>
</div>
);
}
@@ -0,0 +1,107 @@
import { faKey, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import {
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import {
useGetMyAPIKeysV2
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteAPIKeyV2", "apiKeyV2"]>,
data?: {
apiKeyDataId?: string;
name?: string;
}
) => void;
};
export const APIKeyV2Table = ({
handlePopUpOpen
}: Props) => {
const { data, isLoading } = useGetMyAPIKeysV2();
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th className="">Name</Th>
<Th className="">Last Used</Th>
<Th className="">Created At</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="api-keys-v2" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(({
_id,
name,
lastUsed,
createdAt
}) => {
return (
<Tr className="h-10" key={`api-key-v2-${_id}`}>
<Td>{name}</Td>
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<IconButton
onClick={async () => {
handlePopUpOpen("apiKeyV2", {
apiKeyDataId: _id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
<IconButton
onClick={() => {
handlePopUpOpen("deleteAPIKeyV2", {
apiKeyDataId: _id
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState title="No API key v2 on file" icon={faKey} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
);
}
@@ -0,0 +1 @@
export { APIKeyV2Section } from "./APIKeyV2Section";
@@ -1,7 +1,11 @@
// import { APIKeyV2Section } from "../APIKeyV2Section";
import { APIKeySection } from "../APIKeySection";
export const PersonalAPIKeyTab = () => {
return (
<APIKeySection />
<>
{/* <APIKeyV2Section /> */}
<APIKeySection />
</>
);
}
@@ -3,7 +3,7 @@ import {
decryptAssymmetric,
encryptAssymmetric
} from "@app/components/utilities/cryptography/crypto";
import { Checkbox } from "@app/components/v2";
import { Alert, AlertDescription, Checkbox } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useGetUserWsKey, useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api";
@@ -76,30 +76,39 @@ export const E2EESection = () => {
};
return bot ? (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">End-to-End Encryption</p>
<p className="text-gray-400 mb-8">
<p className="mb-8 text-gray-400">
Disabling, end-to-end encryption (E2EE) unlocks capabilities like native integrations to
cloud providers as well as HTTP calls to get secrets back raw but enables the server to
read/decrypt your secret values.
</p>
<p className="text-gray-400 mb-8">
<p className="mb-8 text-gray-400">
Note that, even with E2EE disabled, your secrets are always encrypted at rest.
</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<div className="w-max">
<Checkbox
className="data-[state=checked]:bg-primary"
id="autoCapitalization"
isChecked={!bot.isActive}
isDisabled={!isAllowed}
onCheckedChange={async () => {
await toggleBotActivate();
}}
>
End-to-end encryption enabled
</Checkbox>
<div className="flex w-full flex-col gap-y-3">
<div className="w-max">
<Checkbox
className="data-[state=checked]:bg-primary"
id="autoCapitalization"
isChecked={!bot.isActive}
isDisabled={!isAllowed}
onCheckedChange={async () => {
await toggleBotActivate();
}}
>
End-to-end encryption enabled
</Checkbox>
</div>
<div>
<Alert variant="warning">
<AlertDescription>
Enabling End-to-end encryption disables all the integrations
</AlertDescription>
</Alert>
</div>
</div>
)}
</ProjectPermissionCan>