mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 10:28:22 +00:00
Merge pull request #988 from Infisical/integrations-uiux-updates
improved ui/ux for checkly, gcp, and vault integrations
This commit is contained in:
@@ -608,6 +608,12 @@ const getAppsLaravelForge = async ({
|
|||||||
* @returns {String} apps.name - name of Fly.io apps
|
* @returns {String} apps.name - name of Fly.io apps
|
||||||
*/
|
*/
|
||||||
const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||||
|
interface FlyioApp {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
hostname: string;
|
||||||
|
}
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query($role: String) {
|
query($role: String) {
|
||||||
apps(type: "container", first: 400, role: $role) {
|
apps(type: "container", first: 400, role: $role) {
|
||||||
@@ -620,7 +626,7 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const res = (
|
const res: FlyioApp[] = (
|
||||||
await standardRequest.post(
|
await standardRequest.post(
|
||||||
INTEGRATION_FLYIO_API_URL,
|
INTEGRATION_FLYIO_API_URL,
|
||||||
{
|
{
|
||||||
@@ -639,8 +645,9 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
)
|
)
|
||||||
).data.data.apps.nodes;
|
).data.data.apps.nodes;
|
||||||
|
|
||||||
const apps = res.map((a: any) => ({
|
const apps = res.map((a: FlyioApp) => ({
|
||||||
name: a.name,
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
|||||||
@@ -783,12 +783,12 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to AWS secret manager
|
* Sync/push [secrets] to AWS Secrets Manager
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {IIntegration} obj.integration - integration details
|
* @param {IIntegration} obj.integration - integration details
|
||||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
* @param {String} obj.accessId - access id for AWS secret manager integration
|
* @param {String} obj.accessId - access id for AWS Secrets Manager integration
|
||||||
* @param {String} obj.accessToken - access token for AWS secret manager integration
|
* @param {String} obj.accessToken - access token for AWS Secrets Manager integration
|
||||||
*/
|
*/
|
||||||
const syncSecretsAWSSecretManager = async ({
|
const syncSecretsAWSSecretManager = async ({
|
||||||
integration,
|
integration,
|
||||||
|
|||||||
@@ -187,7 +187,8 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
default: "/",
|
default: "/",
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
type: Schema.Types.Mixed
|
type: Schema.Types.Mixed,
|
||||||
|
default: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export const getIntegrationOptions = async () => {
|
|||||||
docsLink: "",
|
docsLink: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AWS Secret Manager",
|
name: "AWS Secrets Manager",
|
||||||
slug: "aws-secret-manager",
|
slug: "aws-secret-manager",
|
||||||
image: "Amazon Web Services.png",
|
image: "Amazon Web Services.png",
|
||||||
isAvailable: true,
|
isAvailable: true,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ The changelog below reflects new product developments and updates on a monthly b
|
|||||||
- Added secret backups support for the CLI; it now fetches and caches secrets locally to be used in the event of future failed fetch.
|
- Added secret backups support for the CLI; it now fetches and caches secrets locally to be used in the event of future failed fetch.
|
||||||
- Added support for comparing secret values across environments on each secret.
|
- Added support for comparing secret values across environments on each secret.
|
||||||
- Added native AWS Parameter Store integration.
|
- Added native AWS Parameter Store integration.
|
||||||
- Added native AWS Secret Manager integration.
|
- Added native AWS Secrets Manager integration.
|
||||||
- Added native GitLab integration.
|
- Added native GitLab integration.
|
||||||
- Added native CircleCI integration.
|
- Added native CircleCI integration.
|
||||||
- Added native Travis CI integration.
|
- Added native Travis CI integration.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "AWS Secret Manager"
|
title: "AWS Secrets Manager"
|
||||||
description: "How to sync secrets from Infisical to AWS Secret Manager"
|
description: "How to sync secrets from Infisical to AWS Secrets Manager"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
@@ -8,15 +8,15 @@ Prerequisites:
|
|||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
- Set up AWS and have/create an IAM user
|
- Set up AWS and have/create an IAM user
|
||||||
|
|
||||||
## Grant the IAM user permissions to access AWS Secret Manager
|
## Grant the IAM user permissions to access AWS Secrets Manager
|
||||||
|
|
||||||
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Secret Manager.
|
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Secrets Manager.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Secret Manager for the IAM user that you can use:
|
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Secrets Manager for the IAM user that you can use:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -40,7 +40,7 @@ For better security, here's a custom policy containing the minimum permissions r
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Authorize Infisical for AWS Secret Manager
|
## Authorize Infisical for AWS Secrets Manager
|
||||||
|
|
||||||
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ Obtain a AWS access key ID and secret access key for your IAM user in IAM > User
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
Press on the AWS Secret Manager tile and input your AWS access key ID and secret access key from the previous step.
|
Press on the AWS Secrets Manager tile and input your AWS access key ID and secret access key from the previous step.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -61,12 +61,12 @@ Press on the AWS Secret Manager tile and input your AWS access key ID and secret
|
|||||||
|
|
||||||
## Start integration
|
## Start integration
|
||||||
|
|
||||||
Select which Infisical environment secrets you want to sync to which AWS Secret Manager region and under which secret name. Then, press create integration to start syncing secrets to AWS Secret Manager.
|
Select which Infisical environment secrets you want to sync to which AWS Secrets Manager region and under which secret name. Then, press create integration to start syncing secrets to AWS Secrets Manager.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
Infisical currently syncs environment variables to AWS Secret Manager as
|
Infisical currently syncs environment variables to AWS Secrets Manager as
|
||||||
key-value pairs under one secret. We're actively exploring ways to help users
|
key-value pairs under one secret. We're actively exploring ways to help users
|
||||||
group environment variable key-pairs under multiple secrets for greater
|
group environment variable key-pairs under multiple secrets for greater
|
||||||
control.
|
control.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
|
|||||||
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
||||||
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
||||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||||
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||||
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
||||||
| [GCP Secret Manager](/integrations/cloud/gcp-secret-manager) | Cloud | Available |
|
| [GCP Secret Manager](/integrations/cloud/gcp-secret-manager) | Cloud | Available |
|
||||||
| [Windmill](/integrations/cloud/windmill) | Cloud | Available |
|
| [Windmill](/integrations/cloud/windmill) | Cloud | Available |
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ interface Mapping {
|
|||||||
const integrationSlugNameMapping: Mapping = {
|
const integrationSlugNameMapping: Mapping = {
|
||||||
"azure-key-vault": "Azure Key Vault",
|
"azure-key-vault": "Azure Key Vault",
|
||||||
"aws-parameter-store": "AWS Parameter Store",
|
"aws-parameter-store": "AWS Parameter Store",
|
||||||
"aws-secret-manager": "AWS Secret Manager",
|
"aws-secret-manager": "AWS Secrets Manager",
|
||||||
"heroku": "Heroku",
|
"heroku": "Heroku",
|
||||||
"vercel": "Vercel",
|
"vercel": "Vercel",
|
||||||
"netlify": "Netlify",
|
"netlify": "Netlify",
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isSelected={router.asPath.includes(
|
isSelected={router.asPath.includes(
|
||||||
`/project/${currentWorkspace?._id}/secrets/overview`
|
`/project/${currentWorkspace?._id}/secrets`
|
||||||
)}
|
)}
|
||||||
icon="system-outline-90-lock-closed"
|
icon="system-outline-90-lock-closed"
|
||||||
>
|
>
|
||||||
@@ -468,7 +468,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<Link href={`/integrations/${currentWorkspace?._id}`} passHref>
|
<Link href={`/integrations/${currentWorkspace?._id}`} passHref>
|
||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isSelected={router.asPath === `/integrations/${currentWorkspace?._id}`}
|
isSelected={router.asPath.includes("/integrations")}
|
||||||
icon="system-outline-82-extension"
|
icon="system-outline-82-extension"
|
||||||
>
|
>
|
||||||
{t("nav.menu.integrations")}
|
{t("nav.menu.integrations")}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSaveIntegrationAccessToken
|
useSaveIntegrationAccessToken
|
||||||
@@ -56,12 +61,41 @@ export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="mb-4 text-center">AWS Parameter Store Integration</CardTitle>
|
<title>Authorize AWS Parameter Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding the details below, 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">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Amazon Web Services.png"
|
||||||
|
height={35}
|
||||||
|
width={35}
|
||||||
|
alt="AWS logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">AWS Parameter Store Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/aws-parameter-store" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Access Key ID"
|
label="Access Key ID"
|
||||||
errorText={accessKeyErrorText}
|
errorText={accessKeyErrorText}
|
||||||
isError={accessKeyErrorText !== "" ?? false}
|
isError={accessKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder=""
|
placeholder=""
|
||||||
@@ -73,6 +107,7 @@ export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
|||||||
label="Secret Access Key"
|
label="Secret Access Key"
|
||||||
errorText={accessSecretKeyErrorText}
|
errorText={accessSecretKeyErrorText}
|
||||||
isError={accessSecretKeyErrorText !== "" ?? false}
|
isError={accessSecretKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder=""
|
placeholder=""
|
||||||
@@ -82,8 +117,9 @@ export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to AWS Parameter Store
|
Connect to AWS Parameter Store
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +62,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||||
const [secretPath, setSecretPath] = useState("/");
|
const [secretPath, setSecretPath] = useState("/");
|
||||||
@@ -113,10 +118,38 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">AWS Parameter Store Integration</CardTitle>
|
<title>Set Up AWS Parameter Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment in Infisical you want to sync to secerts in AWS Parameter Store."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Amazon Web Services.png"
|
||||||
|
height={35}
|
||||||
|
width={35}
|
||||||
|
alt="AWS logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">AWS Parameter Store Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/aws-parameter-store" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -132,14 +165,14 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="AWS Region">
|
<FormControl label="AWS Region" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedAWSRegion}
|
value={selectedAWSRegion}
|
||||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
onValueChange={(val) => setSelectedAWSRegion(val)}
|
||||||
@@ -152,7 +185,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Path" errorText={pathErrorText} isError={pathErrorText !== "" ?? false}>
|
<FormControl label="Path" errorText={pathErrorText} isError={pathErrorText !== "" ?? false} className="px-6">
|
||||||
<Input
|
<Input
|
||||||
placeholder={`/${workspace.name
|
placeholder={`/${workspace.name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -164,15 +197,39 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in AWS Parameter Store with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up AWS Parameter Store Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isintegrationAuthLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
||||||
|
|
||||||
@@ -54,12 +59,41 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="mb-4 text-center">AWS Secret Manager Integration</CardTitle>
|
<title>Authorize AWS Secrets Manager Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding the details below, 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">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Amazon Web Services.png"
|
||||||
|
height={35}
|
||||||
|
width={35}
|
||||||
|
alt="AWS logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">AWS Secrets Manager Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/aws-secret-manager" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Access Key ID"
|
label="Access Key ID"
|
||||||
errorText={accessKeyErrorText}
|
errorText={accessKeyErrorText}
|
||||||
isError={accessKeyErrorText !== "" ?? false}
|
isError={accessKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input placeholder="" value={accessKey} onChange={(e) => setAccessKey(e.target.value)} />
|
<Input placeholder="" value={accessKey} onChange={(e) => setAccessKey(e.target.value)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -67,6 +101,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
label="Secret Access Key"
|
label="Secret Access Key"
|
||||||
errorText={accessSecretKeyErrorText}
|
errorText={accessSecretKeyErrorText}
|
||||||
isError={accessSecretKeyErrorText !== "" ?? false}
|
isError={accessSecretKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder=""
|
placeholder=""
|
||||||
@@ -76,11 +111,12 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to AWS Secret Manager
|
Connect to AWS Secrets Manager
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +62,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||||
const [secretPath, setSecretPath] = useState("/");
|
const [secretPath, setSecretPath] = useState("/");
|
||||||
@@ -112,10 +117,38 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">AWS Secret Manager Integration</CardTitle>
|
<title>Set Up AWS Secrets Manager Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment in Infisical you want to sync to secerts in AWS Secrets Manager."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Amazon Web Services.png"
|
||||||
|
height={35}
|
||||||
|
width={35}
|
||||||
|
alt="AWS logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">AWS Secrets Manager Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/aws-secret-manager" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -131,14 +164,14 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="AWS Region">
|
<FormControl label="AWS Region" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedAWSRegion}
|
value={selectedAWSRegion}
|
||||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
onValueChange={(val) => setSelectedAWSRegion(val)}
|
||||||
@@ -155,6 +188,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
label="AWS SM Secret Name"
|
label="AWS SM Secret Name"
|
||||||
errorText={targetSecretNameErrorText}
|
errorText={targetSecretNameErrorText}
|
||||||
isError={targetSecretNameErrorText !== "" ?? false}
|
isError={targetSecretNameErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder={`${workspace.name
|
placeholder={`${workspace.name
|
||||||
@@ -167,15 +201,39 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in AWS Secrets Manager with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up AWS Secrets Manager Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isintegrationAuthLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSaveIntegrationAccessToken
|
useSaveIntegrationAccessToken
|
||||||
@@ -41,8 +46,36 @@ export default function ChecklyCreateIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<Head>
|
||||||
|
<title>Authorize Checkly Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
<CardTitle className="text-left px-6" subTitle="After adding your API-key, you will be prompted to set up an integration for a particular Infisical project and environment.">Checkly Integration</CardTitle>
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding your API key, 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/Checkly.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Checkly logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">Checkly Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/checkly" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Checkly API key"
|
label="Checkly API key"
|
||||||
errorText={accessTokenErrorText}
|
errorText={accessTokenErrorText}
|
||||||
@@ -57,7 +90,7 @@ export default function ChecklyCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isFullWidth={false}
|
isFullWidth={false}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +15,11 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
SelectItem
|
SelectItem,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
Tabs
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
useCreateIntegration
|
useCreateIntegration
|
||||||
@@ -21,6 +31,11 @@ import {
|
|||||||
} from "../../../hooks/api/integrationAuth";
|
} from "../../../hooks/api/integrationAuth";
|
||||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||||
|
|
||||||
|
enum TabSections {
|
||||||
|
Connection = "connection",
|
||||||
|
Options = "options"
|
||||||
|
}
|
||||||
|
|
||||||
export default function ChecklyCreateIntegrationPage() {
|
export default function ChecklyCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutateAsync } = useCreateIntegration();
|
const { mutateAsync } = useCreateIntegration();
|
||||||
@@ -29,7 +44,7 @@ export default function ChecklyCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,81 +106,156 @@ export default function ChecklyCreateIntegrationPage() {
|
|||||||
selectedSourceEnvironment &&
|
selectedSourceEnvironment &&
|
||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetApp ? (
|
targetApp ? (
|
||||||
<div className="flex h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
|
<div className="flex flex-col h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up Checkly Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||||
<CardTitle
|
<CardTitle
|
||||||
className="px-6 text-left"
|
className="text-left px-6 text-xl"
|
||||||
subTitle="Choose which environment in Infisical you want to sync with your Checkly account."
|
subTitle="Choose which environment in Infisical you want to sync to Checkly environment variables."
|
||||||
>
|
>
|
||||||
Checkly Integration
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Checkly.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Checkly logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">Checkly Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/checkly" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<FormControl label="Infisical Project Environment" className="mt-4 px-6">
|
<Tabs defaultValue={TabSections.Connection} className="px-6">
|
||||||
<Select
|
<TabList>
|
||||||
value={selectedSourceEnvironment}
|
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||||
className="w-full border border-mineshaft-500"
|
<Tab value={TabSections.Options}>Options</Tab>
|
||||||
>
|
</div>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
</TabList>
|
||||||
<SelectItem
|
<TabPanel value={TabSections.Connection}>
|
||||||
value={sourceEnvironment.slug}
|
<motion.div
|
||||||
key={`source-environment-${sourceEnvironment.slug}`}
|
key="panel-1"
|
||||||
>
|
transition={{ duration: 0.15 }}
|
||||||
{sourceEnvironment.name}
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
</SelectItem>
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
))}
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
</Select>
|
>
|
||||||
</FormControl>
|
<FormControl label="Infisical Project Environment">
|
||||||
<FormControl label="Secrets Path" className="mt-4 px-6">
|
<Select
|
||||||
<Input
|
value={selectedSourceEnvironment}
|
||||||
value={secretPath}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
className="w-full border border-mineshaft-500"
|
||||||
placeholder="Provide a path, default is /"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl label="Checkly Account" className="mt-4 px-6">
|
|
||||||
<Select
|
|
||||||
value={targetApp}
|
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
|
||||||
className="w-full border border-mineshaft-500"
|
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
|
||||||
>
|
|
||||||
{integrationAuthApps.length > 0 ? (
|
|
||||||
integrationAuthApps.map((integrationAuthApp) => (
|
|
||||||
<SelectItem
|
|
||||||
value={integrationAuthApp.name}
|
|
||||||
key={`target-app-${integrationAuthApp.name}`}
|
|
||||||
>
|
>
|
||||||
{integrationAuthApp.name}
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
</SelectItem>
|
<SelectItem
|
||||||
))
|
value={sourceEnvironment.slug}
|
||||||
) : (
|
key={`source-environment-${sourceEnvironment.slug}`}
|
||||||
<SelectItem value="none" key="target-app-none">
|
>
|
||||||
No apps found
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Append the Secret Name with..." className="mt-4 px-6">
|
<FormControl label="Secrets Path">
|
||||||
<Input
|
<Input
|
||||||
value={secretSuffix}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretSuffix(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a suffix for secret names, default is no suffix"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormControl label="Checkly Account">
|
||||||
|
<Select
|
||||||
|
value={targetApp}
|
||||||
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
|
>
|
||||||
|
{integrationAuthApps.length > 0 ? (
|
||||||
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
|
<SelectItem
|
||||||
|
value={integrationAuthApp.name}
|
||||||
|
key={`target-app-${integrationAuthApp.name}`}
|
||||||
|
>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No apps found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Options}>
|
||||||
|
<motion.div
|
||||||
|
key="panel-1"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: -30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<FormControl label="Append Secret Names with..." className="pb-[9.75rem]">
|
||||||
|
<Input
|
||||||
|
value={secretSuffix}
|
||||||
|
onChange={(evt) => setSecretSuffix(evt.target.value)}
|
||||||
|
placeholder="Provide a suffix for secret names, default is no suffix"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
className="mt-2 mb-6 ml-auto mr-6"
|
className="mb-6 ml-auto mr-6"
|
||||||
isFullWidth={false}
|
isFullWidth={false}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Checkly with secrets from Infisical.</span>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">If you have multiple Checkly integrations and are using suffixes for at least one of them, you will have to add suffixes for all the active Checkly integrations – otherwise you might run into rare unexpected behavior.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up Checkly Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSaveIntegrationAccessToken
|
useSaveIntegrationAccessToken
|
||||||
@@ -41,19 +46,49 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">CircleCI Integration</CardTitle>
|
<title>Authorize CircleCI Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding your API 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/Circle CI.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="CircleCI logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">CircleCI Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cicd/circleci" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="CircleCI API Token"
|
label="CircleCI API Token"
|
||||||
errorText={apiKeyErrorText}
|
errorText={apiKeyErrorText}
|
||||||
isError={apiKeyErrorText !== "" ?? false}
|
isError={apiKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to CircleCI
|
Connect to CircleCI
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -28,8 +33,8 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,10 +89,38 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
selectedSourceEnvironment &&
|
selectedSourceEnvironment &&
|
||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetApp ? (
|
targetApp ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">CircleCI Integration</CardTitle>
|
<title>Set Up CircleCI Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment or folder in Infisical you want to sync to CircleCI environment variables."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Circle CI.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="CircleCI logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">CircleCI Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cicd/circleci" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -103,14 +136,14 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="CircleCI Project" className="mt-4">
|
<FormControl label="CircleCI Project" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
@@ -135,17 +168,41 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in CircleCI with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up CircleCI Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isIntegrationAuthAppsLoading || isintegrationAuthLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { useState } from "react";
|
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 { 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 {
|
import {
|
||||||
useSaveIntegrationAccessToken
|
useSaveIntegrationAccessToken
|
||||||
@@ -7,64 +15,116 @@ import {
|
|||||||
|
|
||||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||||
|
|
||||||
export default function FlyioCreateIntegrationPage() {
|
const schema = yup.object({
|
||||||
|
accessToken: yup.string().trim().required("Fly.io Access Token is required")
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
|
export default function FlyioAuthorizeIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
accessToken: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||||
|
|
||||||
const [accessToken, setAccessToken] = useState("");
|
|
||||||
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const onFormSubmit = async ({
|
||||||
|
accessToken
|
||||||
|
}: FormData) => {
|
||||||
|
console.log("onFormSubmit accessToken: ", accessToken);
|
||||||
try {
|
try {
|
||||||
setAccessTokenErrorText("");
|
|
||||||
if (accessToken.length === 0) {
|
|
||||||
setAccessTokenErrorText("Access token cannot be blank");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "flyio",
|
integration: "flyio",
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
router.push(`/integrations/flyio/create?integrationAuthId=${integrationAuth._id}`);
|
router.push(`/integrations/flyio/create?integrationAuthId=${integrationAuth._id}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
setIsLoading(false);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Fly.io Integration</CardTitle>
|
<title>Authorize Fly.io Integration</title>
|
||||||
<FormControl
|
<link rel='icon' href='/infisical.ico' />
|
||||||
label="Fly.io Access Token"
|
</Head>
|
||||||
errorText={accessTokenErrorText}
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
isError={accessTokenErrorText !== "" ?? false}
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding your access token, you will be prompted to set up an integration for a particular Infisical project and environment."
|
||||||
>
|
>
|
||||||
<Input
|
<div className="flex flex-row items-center">
|
||||||
placeholder=""
|
<div className="inline flex items-center pb-0.5">
|
||||||
value={accessToken}
|
<Image
|
||||||
onChange={(e) => setAccessToken(e.target.value)}
|
src="/images/integrations/Flyio.svg"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Fly.io logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">Fly.io Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/flyio" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
className="px-6 text-right pb-8"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="accessToken"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Fly.io Access Token"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
<Button
|
||||||
<Button
|
colorSchema="primary"
|
||||||
onClick={handleButtonClick}
|
variant="outline_bg"
|
||||||
color="mineshaft"
|
className="mt-2 w-min"
|
||||||
className="mt-4"
|
size="sm"
|
||||||
isLoading={isLoading}
|
type="submit"
|
||||||
>
|
isLoading={isLoading}
|
||||||
Connect to Fly.io
|
>
|
||||||
</Button>
|
Connect to Fly.io
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlyioCreateIntegrationPage.requireAuth = true;
|
FlyioAuthorizeIntegrationPage.requireAuth = true;
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -23,13 +28,14 @@ import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
|||||||
|
|
||||||
export default function FlyioCreateIntegrationPage() {
|
export default function FlyioCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync } = useCreateIntegration();
|
const { mutateAsync } = useCreateIntegration();
|
||||||
|
|
||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,10 +90,38 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
selectedSourceEnvironment &&
|
selectedSourceEnvironment &&
|
||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetApp ? (
|
targetApp ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Fly.io Integration</CardTitle>
|
<title>Set Up Fly.io Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment or folder in Infisical you want to sync to Fly.io environment variables."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Flyio.svg"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Fly.io logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">Fly.io Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/flyio" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -103,14 +137,14 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Fly.io App" className="mt-4">
|
<FormControl label="Fly.io App" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
@@ -136,16 +170,40 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Fly.io with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up Fly.io 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="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
import { useState } from "react";
|
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 { useRouter } from "next/router";
|
||||||
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useGetCloudIntegrations,
|
useGetCloudIntegrations,
|
||||||
@@ -11,39 +18,27 @@ import {
|
|||||||
|
|
||||||
import { Button, Card, CardTitle, FormControl, TextArea } from "../../../components/v2";
|
import { Button, Card, CardTitle, FormControl, TextArea } from "../../../components/v2";
|
||||||
|
|
||||||
|
const schema = yup.object({
|
||||||
|
accessToken: yup.string().required("Service Account JSON cannot be empty")
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: yupResolver(schema)
|
||||||
|
});
|
||||||
|
|
||||||
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
||||||
|
|
||||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||||
|
|
||||||
const [accessToken, setAccessToken] = useState("");
|
|
||||||
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleIntegrateWithPAT = async () => {
|
|
||||||
try {
|
|
||||||
setAccessTokenErrorText("");
|
|
||||||
if (accessToken.length === 0) {
|
|
||||||
setAccessTokenErrorText("Service account JSON cannot be blank");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const integrationAuth = await mutateAsync({
|
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
|
||||||
integration: "gcp-secret-manager",
|
|
||||||
refreshToken: accessToken
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
|
|
||||||
router.push(`/integrations/gcp-secret-manager/create?integrationAuthId=${integrationAuth._id}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIntegrateWithOAuth = () => {
|
const handleIntegrateWithOAuth = () => {
|
||||||
if (!cloudIntegrations) return;
|
if (!cloudIntegrations) return;
|
||||||
@@ -57,46 +52,106 @@ export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
|||||||
const link = `https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/cloud-platform&response_type=code&access_type=offline&state=${state}&redirect_uri=${window.location.origin}/integrations/gcp-secret-manager/oauth2/callback&client_id=${integrationOption.clientId}`;
|
const link = `https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/cloud-platform&response_type=code&access_type=offline&state=${state}&redirect_uri=${window.location.origin}/integrations/gcp-secret-manager/oauth2/callback&client_id=${integrationOption.clientId}`;
|
||||||
window.location.assign(link);
|
window.location.assign(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
accessToken
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const integrationAuth = await mutateAsync({
|
||||||
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
|
integration: "gcp-secret-manager",
|
||||||
|
refreshToken: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
router.push(`/integrations/gcp-secret-manager/create?integrationAuthId=${integrationAuth._id}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
|
<title>Authorize GCP Secret Manager Integration</title>
|
||||||
<Button
|
<link rel='icon' href='/infisical.ico' />
|
||||||
colorSchema="primary"
|
</Head>
|
||||||
variant="outline_bg"
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
onClick={handleIntegrateWithOAuth}
|
<CardTitle
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
className="text-left px-6 text-xl"
|
||||||
className="h-11 w-full mx-0 mt-4"
|
subTitle="Connect Infisical to GCP Secret Manager to sync secrets."
|
||||||
>
|
>
|
||||||
Continue with OAuth
|
<div className="flex flex-row items-center">
|
||||||
</Button>
|
<div className="inline flex items-center pb-0.5">
|
||||||
<div className='w-full flex flex-row items-center my-4 py-2'>
|
<Image
|
||||||
<div className='w-full border-t border-mineshaft-400/60' />
|
src="/images/integrations/Google Cloud Platform.png"
|
||||||
<span className="mx-2 text-mineshaft-200 text-xs">or</span>
|
height={30}
|
||||||
<div className='w-full border-t border-mineshaft-400/60' />
|
width={30}
|
||||||
|
alt="GCP logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">GCP Secret Manager Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/gcp-secret-manager" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<div className="px-6">
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={handleIntegrateWithOAuth}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
|
className="h-11 w-full mx-0 mt-4"
|
||||||
|
>
|
||||||
|
Continue with OAuth
|
||||||
|
</Button>
|
||||||
|
<div className='w-full flex flex-row items-center my-4 py-2'>
|
||||||
|
<div className='w-full border-t border-mineshaft-400/40' />
|
||||||
|
<span className="mx-2 text-mineshaft-400 text-xs">or</span>
|
||||||
|
<div className='w-full border-t border-mineshaft-400/40' />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FormControl
|
<form
|
||||||
label="GCP Service Account JSON"
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
errorText={accessTokenErrorText}
|
className="px-6 text-right pb-8"
|
||||||
isError={accessTokenErrorText !== "" ?? false}
|
|
||||||
>
|
>
|
||||||
<TextArea
|
<Controller
|
||||||
value={accessToken}
|
control={control}
|
||||||
onChange={(e) => setAccessToken(e.target.value)}
|
name="accessToken"
|
||||||
placeholder=""
|
render={({ field, fieldState: { error } }) => (
|
||||||
className="h-48"
|
<FormControl
|
||||||
/>
|
label="GCP Service Account JSON"
|
||||||
</FormControl>
|
errorText={error?.message}
|
||||||
<Button
|
isError={Boolean(error)}
|
||||||
onClick={handleIntegrateWithPAT}
|
>
|
||||||
color="mineshaft"
|
<TextArea
|
||||||
className="mt-4"
|
{...field}
|
||||||
isLoading={isLoading}
|
className="h-48 border border-mineshaft-600 bg-bunker-900/80"
|
||||||
>
|
/>
|
||||||
Connect to GCP Secret Manager
|
</FormControl>
|
||||||
</Button>
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
className="mt-2 w-min"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Connect to GCP Secret Manager
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
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 { 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 { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useCreateIntegration
|
useCreateIntegration
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
@@ -16,14 +22,15 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
Switch,
|
Switch,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
Tabs
|
Tabs} from "../../../components/v2";
|
||||||
} from "../../../components/v2";
|
|
||||||
import {
|
import {
|
||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
useGetIntegrationAuthById
|
useGetIntegrationAuthById
|
||||||
@@ -39,30 +46,36 @@ const schema = yup.object({
|
|||||||
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
||||||
secretPath: yup.string().required("Secret path is required"),
|
secretPath: yup.string().required("Secret path is required"),
|
||||||
targetAppId: yup.string().required("GCP project is required"),
|
targetAppId: yup.string().required("GCP project is required"),
|
||||||
secretPrefix: yup.string(),
|
secretPrefix: yup.string().trim(),
|
||||||
secretSuffix: yup.string(),
|
secretSuffix: yup.string().trim(),
|
||||||
shouldLabel: yup.boolean(),
|
shouldLabel: yup.boolean(),
|
||||||
labelName: yup.string(),
|
labelName: yup.string().trim(),
|
||||||
labelValue: yup.string()
|
labelValue: yup.string().trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = yup.InferType<typeof schema>;
|
type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
export default function GCPSecretManagerCreateIntegrationPage() {
|
export default function GCPSecretManagerCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
"confirmIntegration"
|
||||||
|
] as const);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
watch
|
watch
|
||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
secretPath: "/",
|
secretPath: "/",
|
||||||
shouldLabel: false,
|
secretPrefix: "",
|
||||||
labelName: "managed-by",
|
secretSuffix: "",
|
||||||
labelValue: "infisical"
|
shouldLabel: false,
|
||||||
}
|
labelName: "managed-by",
|
||||||
|
labelValue: "infisical"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const shouldLabel = watch("shouldLabel");
|
const shouldLabel = watch("shouldLabel");
|
||||||
@@ -74,7 +87,7 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,8 +100,8 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue("labelName", undefined);
|
setValue("labelName", "");
|
||||||
setValue("labelValue", undefined);
|
setValue("labelValue", "");
|
||||||
}, [shouldLabel]);
|
}, [shouldLabel]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -130,8 +143,8 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
|||||||
sourceEnvironment: sce,
|
sourceEnvironment: sce,
|
||||||
secretPath,
|
secretPath,
|
||||||
metadata: {
|
metadata: {
|
||||||
secretPrefix,
|
...(secretPrefix ? { secretPrefix } : {}),
|
||||||
secretSuffix,
|
...(secretSuffix ? { secretSuffix } : {}),
|
||||||
...(sl ? {
|
...(sl ? {
|
||||||
secretGCPLabel: {
|
secretGCPLabel: {
|
||||||
labelName,
|
labelName,
|
||||||
@@ -154,15 +167,52 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
|||||||
selectedSourceEnvironment &&
|
selectedSourceEnvironment &&
|
||||||
integrationAuthApps ? (
|
integrationAuthApps ? (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit((data: FormData) => {
|
||||||
className="flex h-full w-full items-center justify-center"
|
if (!data.secretPrefix && !data.secretSuffix && !data.shouldLabel) {
|
||||||
|
handlePopUpOpen("confirmIntegration", data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit(data);
|
||||||
|
})}
|
||||||
|
className="flex flex-col h-full w-full items-center justify-center"
|
||||||
>
|
>
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
|
<Head>
|
||||||
<Tabs defaultValue={TabSections.Connection}>
|
<title>Set Up GCP Secret Manager Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl mb-2"
|
||||||
|
subTitle="Select which environment or folder in Infisical you want to sync to GCP Secret Manager."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Google Cloud Platform.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="GCP logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">GCP Secret Manager Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/gcp-secret-manager" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<Tabs defaultValue={TabSections.Connection} className="px-6">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||||
<Tab value={TabSections.Options}>Options</Tab>
|
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||||
|
<Tab value={TabSections.Options}>Options</Tab>
|
||||||
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={TabSections.Connection}>
|
<TabPanel value={TabSections.Connection}>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -172,190 +222,258 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
|||||||
animate={{ opacity: 1, translateX: 0 }}
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
exit={{ opacity: 0, translateX: 30 }}
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
>
|
>
|
||||||
<div>
|
<Controller
|
||||||
<Controller
|
control={control}
|
||||||
|
name="selectedSourceEnvironment"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Project Environment"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
|
<SelectItem
|
||||||
|
value={sourceEnvironment.slug}
|
||||||
|
key={`source-environment-${sourceEnvironment.slug}`}
|
||||||
|
>
|
||||||
|
{sourceEnvironment.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="selectedSourceEnvironment"
|
defaultValue=""
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
name="secretPath"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secrets Path"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="/"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="targetAppId"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Project Environment"
|
label="GCP Project"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => {
|
||||||
|
if (e === "") return;
|
||||||
|
onChange(e)
|
||||||
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
value={sourceEnvironment.slug}
|
<SelectItem
|
||||||
key={`source-environment-${sourceEnvironment.slug}`}
|
value={String(integrationAuthApp.appId as string)}
|
||||||
>
|
key={`target-app-${String(integrationAuthApp.appId)}`}
|
||||||
{sourceEnvironment.name}
|
>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Options}>
|
||||||
|
<motion.div
|
||||||
|
key="panel-1"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: -30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="secretPrefix"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret Prefix"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="INFISICAL_"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="secretSuffix"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret Suffix"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="_INFISICAL"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 mb-[2.36rem]">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="shouldLabel"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Switch
|
||||||
|
id="label-gcp"
|
||||||
|
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||||
|
isChecked={value}
|
||||||
|
>
|
||||||
|
Label in GCP Secret Manager
|
||||||
|
</Switch>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
</div>
|
||||||
|
{shouldLabel && (
|
||||||
|
<div className="mt-8">
|
||||||
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue=""
|
name="labelName"
|
||||||
name="secretPath"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Secrets Path"
|
label="Label Name"
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
placeholder="/"
|
placeholder="managed-by"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="targetAppId"
|
name="labelValue"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => (
|
||||||
return (
|
<FormControl
|
||||||
<FormControl
|
label="Label Name"
|
||||||
label="GCP Project"
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => {
|
|
||||||
if (e === "") return;
|
|
||||||
onChange(e)
|
|
||||||
}}
|
|
||||||
className="w-full"
|
|
||||||
>
|
>
|
||||||
{integrationAuthApps.length > 0 ? (
|
<Input
|
||||||
integrationAuthApps.map((integrationAuthApp) => (
|
{...field}
|
||||||
<SelectItem
|
placeholder="infisical"
|
||||||
value={String(integrationAuthApp.appId as string)}
|
/>
|
||||||
key={`target-app-${String(integrationAuthApp.appId)}`}
|
</FormControl>
|
||||||
>
|
)}
|
||||||
{integrationAuthApp.name}
|
/>
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<SelectItem value="none" key="target-app-none">
|
|
||||||
No projects found
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className="mt-4"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
|
||||||
Create Integration
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={TabSections.Options}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="secretPrefix"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Secret Prefix"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="INFISICAL_"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="secretSuffix"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Secret Suffix"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="_INFISICAL"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="mt-8">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="shouldLabel"
|
|
||||||
render={({ field: { onChange, value } }) => (
|
|
||||||
<Switch
|
|
||||||
id="label-gcp"
|
|
||||||
onCheckedChange={(isChecked) => onChange(isChecked)}
|
|
||||||
isChecked={value}
|
|
||||||
>
|
|
||||||
Label in GCP Secret Manager
|
|
||||||
</Switch>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{shouldLabel && (
|
|
||||||
<div className="mt-8">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="labelName"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Label Name"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="managed-by"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="labelValue"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Label Name"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="infisical"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TabPanel>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
className="mb-8 ml-auto mr-6 w-min"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
|
>
|
||||||
|
Create Integration
|
||||||
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
{/* <div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/>
|
||||||
|
<span className="ml-3 text-md text-mineshaft-100">Pro Tip</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">
|
||||||
|
After creating an integration, your secrets will start syncing immediately.
|
||||||
|
|
||||||
|
To avoid overwriting existing secrets in GCP Secret Manager, you may consider adding a secret prefix/suffix and/or enabling labeling in the options tab.
|
||||||
|
</span>
|
||||||
|
</div> */}
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp.confirmIntegration?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("confirmIntegration", isOpen)}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title="Heads Up"
|
||||||
|
footerContent={
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button onClick={() => onFormSubmit(popUp.confirmIntegration?.data as FormData)}>
|
||||||
|
Continue Anyway
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePopUpClose("confirmIntegration")}
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You're about to overwrite any existing secrets in GCP Secret Manager.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
To avoid this behavior, you may consider adding a secret prefix/suffix or enabling labeling in the options tab.
|
||||||
|
</p>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up GCP Secret Manager Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
GCPSecretManagerCreateIntegrationPage.requireAuth = true;
|
GCPSecretManagerCreateIntegrationPage.requireAuth = true;
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +34,7 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,10 +93,38 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
selectedSourceEnvironment &&
|
selectedSourceEnvironment &&
|
||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetAppId ? (
|
targetAppId ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">GitHub Integration</CardTitle>
|
<title>Set Up GitHub Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment in Infisical you want to sync to environment variables in GitHub."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center bg-mineshaft-200 rounded-full">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/GitHub.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="GitHub logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">GitHub Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cicd/githubactions" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -107,14 +140,14 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="GitHub Repo" className="mt-4">
|
<FormControl label="GitHub Repo" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetAppId}
|
value={targetAppId}
|
||||||
onValueChange={(val) => setTargetAppId(val)}
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
@@ -140,16 +173,40 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mt-2 mb-6 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in GitHub with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up GitHub Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,118 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useGetCloudIntegrations } from "@app/hooks/api";
|
import { useGetCloudIntegrations } from "@app/hooks/api";
|
||||||
|
|
||||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||||
|
|
||||||
|
const schema = yup.object({
|
||||||
|
gitLabURL: yup.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
export default function GitLabAuthorizeIntegrationPage() {
|
export default function GitLabAuthorizeIntegrationPage() {
|
||||||
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
const {
|
||||||
|
control,
|
||||||
const [gitLabURL, setGitLabURL] = useState("");
|
handleSubmit
|
||||||
|
} = useForm<FormData>({
|
||||||
const handleIntegrateWithOAuth = () => {
|
resolver: yupResolver(schema),
|
||||||
if (!cloudIntegrations) return;
|
defaultValues: {
|
||||||
const integrationOption = cloudIntegrations.find((integration) => integration.slug === "gitlab");
|
gitLabURL: ""
|
||||||
|
|
||||||
if (!integrationOption) return;
|
|
||||||
|
|
||||||
const baseURL = gitLabURL.trim() === "" ? "https://gitlab.com" : gitLabURL.trim();
|
|
||||||
|
|
||||||
const csrfToken = crypto.randomBytes(16).toString("hex");
|
|
||||||
localStorage.setItem("latestCSRFToken", csrfToken);
|
|
||||||
|
|
||||||
const state = `${csrfToken}|${gitLabURL.trim() === "" ? "" : gitLabURL.trim()}`;
|
|
||||||
const link = `${baseURL}/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
|
||||||
|
|
||||||
window.location.assign(link);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
||||||
|
|
||||||
|
const onFormSubmit = ({
|
||||||
|
gitLabURL
|
||||||
|
}: FormData) => {
|
||||||
|
if (!cloudIntegrations) return;
|
||||||
|
const integrationOption = cloudIntegrations.find((integration) => integration.slug === "gitlab");
|
||||||
|
|
||||||
|
if (!integrationOption) return;
|
||||||
|
|
||||||
|
const baseURL = (gitLabURL as string).trim() === "" ? "https://gitlab.com" : (gitLabURL as string).trim();
|
||||||
|
|
||||||
|
const csrfToken = crypto.randomBytes(16).toString("hex");
|
||||||
|
localStorage.setItem("latestCSRFToken", csrfToken);
|
||||||
|
|
||||||
|
const state = `${csrfToken}|${(gitLabURL as string).trim() === "" ? "" : (gitLabURL as string).trim()}`;
|
||||||
|
const link = `${baseURL}/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
||||||
|
|
||||||
|
window.location.assign(link);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">GitLab Integration</CardTitle>
|
<title>Authorize GitLab Integration</title>
|
||||||
<FormControl label="Self-hosted URL (optional)">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
<Input
|
</Head>
|
||||||
placeholder="https://self-hosted-gitlab.com"
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
value={gitLabURL} onChange={(e) => setGitLabURL(e.target.value)}
|
<CardTitle
|
||||||
/>
|
className="text-left px-6 text-xl"
|
||||||
</FormControl>
|
subTitle="Authorize this integration to sync secrets from Infisical to GitLab. If no self-hosted GitLab URL is specified, then Infisical will connect you to GitLab Cloud."
|
||||||
<Button
|
>
|
||||||
onClick={handleIntegrateWithOAuth}
|
<div className="flex flex-row items-center">
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
<div className="inline flex items-center pb-0.5">
|
||||||
className="h-11 w-full mx-0 mt-4"
|
<Image
|
||||||
>
|
src="/images/integrations/Gitlab.png"
|
||||||
|
height={28}
|
||||||
|
width={28}
|
||||||
|
alt="Gitlab logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">GitLab Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cicd/gitlab" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
className="px-6 text-right pb-8"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="gitLabURL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Self-hosted URL (optional)"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://self-hosted-gitlab.com"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
className="mt-2 w-min"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
Continue with OAuth
|
Continue with OAuth
|
||||||
</Button>
|
</Button>
|
||||||
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
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 { 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 { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useCreateIntegration
|
useCreateIntegration
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
@@ -16,6 +22,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
Tab,
|
Tab,
|
||||||
@@ -55,6 +63,9 @@ type FormData = yup.InferType<typeof schema>;
|
|||||||
|
|
||||||
export default function GitLabCreateIntegrationPage() {
|
export default function GitLabCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
"confirmIntegration"
|
||||||
|
] as const);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -65,7 +76,9 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
targetEntity: "individual",
|
targetEntity: "individual",
|
||||||
secretPath: "/"
|
secretPath: "/",
|
||||||
|
secretPrefix: "",
|
||||||
|
secretSuffix: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||||
@@ -79,11 +92,12 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
|
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
|
|
||||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||||
...(targetTeamId ? { teamId: targetTeamId } : {})
|
...(targetTeamId ? { teamId: targetTeamId } : {})
|
||||||
});
|
});
|
||||||
const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(
|
const { data: integrationAuthTeams, isLoading: isintegrationAuthTeamsLoading } = useGetIntegrationAuthTeams(
|
||||||
(integrationAuthId as string) ?? ""
|
(integrationAuthId as string) ?? ""
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -161,195 +175,229 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
integrationAuthTeams ? (
|
integrationAuthTeams ? (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit((data: FormData) => {
|
||||||
className="flex h-full w-full items-center justify-center"
|
if (!data.secretPrefix && !data.secretSuffix) {
|
||||||
|
handlePopUpOpen("confirmIntegration", data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit(data);
|
||||||
|
})}
|
||||||
|
className="flex flex-col h-full w-full items-center justify-center"
|
||||||
>
|
>
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">GitLab Integration</CardTitle>
|
<title>Set Up GitLab Integration</title>
|
||||||
<Tabs defaultValue={TabSections.Connection}>
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Select which environment or folder in Infisical you want to sync to GitLab's environment variables."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Gitlab.png"
|
||||||
|
height={28}
|
||||||
|
width={28}
|
||||||
|
alt="Gitlab logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">GitLab Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cicd/gitlab" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<Tabs defaultValue={TabSections.Connection} className="px-6">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||||
<Tab value={TabSections.Options}>Options</Tab>
|
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||||
|
<Tab value={TabSections.Options}>Options</Tab>
|
||||||
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={TabSections.Connection}>
|
<TabPanel value={TabSections.Connection}>
|
||||||
<motion.div
|
<motion.div
|
||||||
key="panel-1"
|
key="panel-1"
|
||||||
transition={{ duration: 0.15 }}
|
transition={{ duration: 0.15 }}
|
||||||
initial={{ opacity: 0, translateX: 30 }}
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
animate={{ opacity: 1, translateX: 0 }}
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
exit={{ opacity: 0, translateX: 30 }}
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
>
|
>
|
||||||
<div>
|
<Controller
|
||||||
<Controller
|
control={control}
|
||||||
control={control}
|
name="selectedSourceEnvironment"
|
||||||
name="selectedSourceEnvironment"
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
<FormControl
|
||||||
<FormControl
|
label="Project Environment"
|
||||||
label="Project Environment"
|
errorText={error?.message}
|
||||||
errorText={error?.message}
|
isError={Boolean(error)}
|
||||||
isError={Boolean(error)}
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Select
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
defaultValue={field.value}
|
<SelectItem
|
||||||
{...field}
|
value={sourceEnvironment.slug}
|
||||||
onValueChange={(e) => onChange(e)}
|
key={`source-environment-${sourceEnvironment.slug}`}
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
|
||||||
<SelectItem
|
|
||||||
value={sourceEnvironment.slug}
|
|
||||||
key={`source-environment-${sourceEnvironment.slug}`}
|
|
||||||
>
|
|
||||||
{sourceEnvironment.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
defaultValue=""
|
|
||||||
name="secretPath"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Secrets Path"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
>
|
||||||
<Input
|
{sourceEnvironment.name}
|
||||||
{...field}
|
</SelectItem>
|
||||||
placeholder="/"
|
))}
|
||||||
/>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="targetEntity"
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="GitLab Integration Type"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{gitLabEntities.map((entity) => {
|
|
||||||
return (
|
|
||||||
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
|
|
||||||
{entity.name}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{targetEntity === "group" && targetTeamId && (
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="targetTeamId"
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="GitLab Group"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{integrationAuthTeams.length > 0 ? (
|
|
||||||
integrationAuthTeams.map((integrationAuthTeam) =>
|
|
||||||
(
|
|
||||||
<SelectItem
|
|
||||||
value={String(integrationAuthTeam.teamId as string)}
|
|
||||||
key={`target-team-${String(integrationAuthTeam.teamId)}`}
|
|
||||||
>
|
|
||||||
{integrationAuthTeam.name}
|
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<SelectItem value="none" key="target-team-none">
|
|
||||||
No groups found
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="targetAppId"
|
control={control}
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
defaultValue=""
|
||||||
return (
|
name="secretPath"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="GitLab Project"
|
label="Secrets Path"
|
||||||
errorText={error?.message}
|
isError={Boolean(error)}
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<Select
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => {
|
placeholder="/"
|
||||||
if (e === "") return;
|
/>
|
||||||
onChange(e)
|
|
||||||
}}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{integrationAuthApps.length > 0 ? (
|
|
||||||
integrationAuthApps.map((integrationAuthApp) => (
|
|
||||||
<SelectItem
|
|
||||||
value={String(integrationAuthApp.appId as string)}
|
|
||||||
key={`target-app-${String(integrationAuthApp.appId)}`}
|
|
||||||
>
|
|
||||||
{integrationAuthApp.name}
|
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<SelectItem value="none" key="target-app-none">
|
|
||||||
No projects found
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue=""
|
name="targetEntity"
|
||||||
name="targetEnvironment"
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
render={({ field, fieldState: { error } }) => (
|
<FormControl
|
||||||
<FormControl
|
label="GitLab Integration Type"
|
||||||
label="GitLab Environment Scope (Optional)"
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
>
|
||||||
>
|
<Select
|
||||||
<Input
|
{...field}
|
||||||
{...field}
|
onValueChange={(e) => onChange(e)}
|
||||||
placeholder="*"
|
className="w-full"
|
||||||
/>
|
>
|
||||||
</FormControl>
|
{gitLabEntities.map((entity) => {
|
||||||
)}
|
return (
|
||||||
|
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
|
||||||
|
{entity.name}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{targetEntity === "group" && targetTeamId && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="targetTeamId"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="GitLab Group"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{integrationAuthTeams.length > 0 ? (
|
||||||
|
integrationAuthTeams.map((integrationAuthTeam) =>
|
||||||
|
(
|
||||||
|
<SelectItem
|
||||||
|
value={String(integrationAuthTeam.teamId as string)}
|
||||||
|
key={`target-team-${String(integrationAuthTeam.teamId)}`}
|
||||||
|
>
|
||||||
|
{integrationAuthTeam.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-team-none">
|
||||||
|
No groups found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
<Button
|
<Controller
|
||||||
className="mt-4"
|
control={control}
|
||||||
size="sm"
|
name="targetAppId"
|
||||||
type="submit"
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||||
isLoading={isLoading}
|
return (
|
||||||
>
|
<FormControl
|
||||||
Create Integration
|
label="GitLab Project"
|
||||||
</Button>
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => {
|
||||||
|
if (e === "") return;
|
||||||
|
onChange(e)
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{integrationAuthApps.length > 0 ? (
|
||||||
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
|
<SelectItem
|
||||||
|
value={String(integrationAuthApp.appId as string)}
|
||||||
|
key={`target-app-${String(integrationAuthApp.appId)}`}
|
||||||
|
>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="targetEnvironment"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="GitLab Environment Scope (Optional)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="*"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={TabSections.Options}>
|
<TabPanel value={TabSections.Options}>
|
||||||
<div>
|
<motion.div
|
||||||
|
key="panel-1"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: -30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
className="pb-[14.25rem]"
|
||||||
|
>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="secretPrefix"
|
name="secretPrefix"
|
||||||
@@ -382,13 +430,75 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</motion.div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card>
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
className="mb-8 ml-auto mr-6 w-min"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Create Integration
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
{/* <div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in GitLab with secrets from Infisical.</span>
|
||||||
|
</div> */}
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp.confirmIntegration?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("confirmIntegration", isOpen)}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title="Heads Up"
|
||||||
|
footerContent={
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button onClick={() => onFormSubmit(popUp.confirmIntegration?.data as FormData)}>
|
||||||
|
Continue Anyway
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePopUpClose("confirmIntegration")}
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You're about to overwrite any existing secrets in GitLab.
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
To avoid this behavior, you may consider adding a secret prefix/suffix in the options tab.
|
||||||
|
</p>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up GitLab Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isIntegrationAuthAppsLoading || isintegrationAuthTeamsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSaveIntegrationAccessToken
|
useSaveIntegrationAccessToken
|
||||||
@@ -80,12 +85,41 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Vault Integration</CardTitle>
|
<title>Authorize Vault Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After connecting to Vault, 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">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Vault.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="HCP Vault logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">HashiCorp Vault Integration</span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/hashicorp-vault" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Vault Cluster URL"
|
label="Vault Cluster URL"
|
||||||
errorText={vaultURLErrorText}
|
errorText={vaultURLErrorText}
|
||||||
isError={vaultURLErrorText !== "" ?? false}
|
isError={vaultURLErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input placeholder="" value={vaultURL} onChange={(e) => setVaultURL(e.target.value)} />
|
<Input placeholder="" value={vaultURL} onChange={(e) => setVaultURL(e.target.value)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -93,6 +127,7 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
|||||||
label="Vault Namespace"
|
label="Vault Namespace"
|
||||||
errorText={vaultNamespaceErrorText}
|
errorText={vaultNamespaceErrorText}
|
||||||
isError={vaultNamespaceErrorText !== "" ?? false}
|
isError={vaultNamespaceErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="admin/education"
|
placeholder="admin/education"
|
||||||
@@ -104,6 +139,7 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
|||||||
label="Vault RoleID"
|
label="Vault RoleID"
|
||||||
errorText={vaultRoleIDErrorText}
|
errorText={vaultRoleIDErrorText}
|
||||||
isError={vaultRoleIDErrorText !== "" ?? false}
|
isError={vaultRoleIDErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input placeholder="" value={vaultRoleID} onChange={(e) => setVaultRoleID(e.target.value)} />
|
<Input placeholder="" value={vaultRoleID} onChange={(e) => setVaultRoleID(e.target.value)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -111,6 +147,7 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
|||||||
label="Vault SecretID"
|
label="Vault SecretID"
|
||||||
errorText={vaultSecretIDErrorText}
|
errorText={vaultSecretIDErrorText}
|
||||||
isError={vaultSecretIDErrorText !== "" ?? false}
|
isError={vaultSecretIDErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder=""
|
placeholder=""
|
||||||
@@ -120,8 +157,9 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to Vault
|
Connect to Vault
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +30,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
|
|
||||||
const [vaultEnginePath, setVaultEnginePath] = useState("");
|
const [vaultEnginePath, setVaultEnginePath] = useState("");
|
||||||
const [vaultEnginePathErrorText, setVaultEnginePathErrorText] = useState("");
|
const [vaultEnginePathErrorText, setVaultEnginePathErrorText] = useState("");
|
||||||
@@ -79,10 +84,38 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return integrationAuth && workspace ? (
|
return integrationAuth && workspace ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Vault Integration</CardTitle>
|
<title>Set Up Vault Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Select which environment or folder in Infisical you want to sync to which path in HashiCorp Vault."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Vault.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="HCP Vault logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">HashiCorp Vault Integration</span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/hashicorp-vault" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -98,7 +131,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
@@ -109,6 +142,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
|||||||
label="Vault KV Secrets Engine Path"
|
label="Vault KV Secrets Engine Path"
|
||||||
errorText={vaultEnginePathErrorText}
|
errorText={vaultEnginePathErrorText}
|
||||||
isError={vaultEnginePathErrorText !== "" ?? false}
|
isError={vaultEnginePathErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="kv"
|
placeholder="kv"
|
||||||
@@ -120,6 +154,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
|||||||
label="Vault Secret(s) Path"
|
label="Vault Secret(s) Path"
|
||||||
errorText={vaultSecretPathErrorText}
|
errorText={vaultSecretPathErrorText}
|
||||||
isError={vaultSecretPathErrorText !== "" ?? false}
|
isError={vaultSecretPathErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="machine/dev"
|
placeholder="machine/dev"
|
||||||
@@ -129,17 +164,41 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={!(isValidVaultPath(vaultEnginePath) && isValidVaultPath(vaultSecretPath))}
|
isDisabled={!(isValidVaultPath(vaultEnginePath) && isValidVaultPath(vaultSecretPath))}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Vault with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up Vault Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isintegrationAuthLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { useSaveIntegrationAccessToken} from "@app/hooks/api";
|
import { useSaveIntegrationAccessToken} from "@app/hooks/api";
|
||||||
|
|
||||||
@@ -39,19 +44,49 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Render Integration</CardTitle>
|
<title>Authorize Render Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding your API key, 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/Render.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Render logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">Render Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/render" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Render API Key"
|
label="Render API Key"
|
||||||
errorText={apiKeyErrorText}
|
errorText={apiKeyErrorText}
|
||||||
isError={apiKeyErrorText !== "" ?? false}
|
isError={apiKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input placeholder="rnd_xxx" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
<Input placeholder="rnd_xxx" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to Render
|
Connect to Render
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -28,8 +33,8 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,10 +87,38 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
selectedSourceEnvironment &&
|
selectedSourceEnvironment &&
|
||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetApp ? (
|
targetApp ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Render Integration</CardTitle>
|
<title>Set Up Render Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment or folder in Infisical you want to sync to Render environment variables."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Render.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Render logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2.5">Render Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/render" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -101,14 +134,14 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Render Service" className="mt-4">
|
<FormControl label="Render Service" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
@@ -134,16 +167,40 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Render with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up Render 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="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSaveIntegrationAccessToken
|
useSaveIntegrationAccessToken
|
||||||
@@ -51,12 +56,41 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">TeamCity Integration</CardTitle>
|
<title>Authorize TeamCity Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="After adding the details below, 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/TeamCity.png"
|
||||||
|
height={28}
|
||||||
|
width={28}
|
||||||
|
alt="TeamCity logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2">TeamCity Integration</span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/teamcity" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="TeamCity Access Token"
|
label="TeamCity Access Token"
|
||||||
errorText={apiKeyErrorText}
|
errorText={apiKeyErrorText}
|
||||||
isError={apiKeyErrorText !== "" ?? false}
|
isError={apiKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Access Token"
|
placeholder="Access Token"
|
||||||
@@ -68,6 +102,7 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
label="TeamCity Server URL"
|
label="TeamCity Server URL"
|
||||||
errorText={serverUrlErrorText}
|
errorText={serverUrlErrorText}
|
||||||
isError={serverUrlErrorText !== "" ?? false}
|
isError={serverUrlErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://example.teamcity.com"
|
placeholder="https://example.teamcity.com"
|
||||||
@@ -77,8 +112,9 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to TeamCity
|
Connect to TeamCity
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -35,8 +40,8 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,9 +107,37 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
filteredBuildConfigs &&
|
filteredBuildConfigs &&
|
||||||
targetAppId ? (
|
targetAppId ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">TeamCity Integration</CardTitle>
|
<title>Set Up TeamCity Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Choose which environment or folders in Infisical you want to sync to which project in TeamCity."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center pb-0.5">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/TeamCity.png"
|
||||||
|
height={28}
|
||||||
|
width={28}
|
||||||
|
alt="TeamCity logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2">TeamCity Integration</span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/teamcity" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -120,14 +153,14 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="TeamCity Project" className="mt-4">
|
<FormControl label="TeamCity Project" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetAppId}
|
value={targetAppId}
|
||||||
onValueChange={(val) => setTargetAppId(val)}
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
@@ -150,7 +183,7 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Team City Build Config (Optional)" className="mt-4">
|
<FormControl label="Team City Build Config (Optional)" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetBuildConfigId}
|
value={targetBuildConfigId}
|
||||||
onValueChange={(val) => setTargetBuildConfigId(val)}
|
onValueChange={(val) => setTargetBuildConfigId(val)}
|
||||||
@@ -169,7 +202,8 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
@@ -178,7 +212,25 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up TeamCity 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="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -42,7 +47,7 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,10 +114,38 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetAppId &&
|
targetAppId &&
|
||||||
targetEnvironment ? (
|
targetEnvironment ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Vercel Integration</CardTitle>
|
<title>Set Up Vercel Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||||
|
<CardTitle
|
||||||
|
className="text-left px-6 text-xl"
|
||||||
|
subTitle="Select which environment or folder in Infisical you want to sync to Vercel's environment variables."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Vercel.png"
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
alt="Vercel logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2">Vercel Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/vercel" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Infisical Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -128,14 +161,14 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Vercel App">
|
<FormControl label="Vercel App" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetAppId}
|
value={targetAppId}
|
||||||
onValueChange={(val) => setTargetAppId(val)}
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
@@ -158,7 +191,7 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Vercel App Environment">
|
<FormControl label="Vercel App Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetEnvironment}
|
value={targetEnvironment}
|
||||||
onValueChange={(val) => setTargetEnvironment(val)}
|
onValueChange={(val) => setTargetEnvironment(val)}
|
||||||
@@ -175,7 +208,7 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{targetEnvironment === "preview" && filteredBranches && (
|
{targetEnvironment === "preview" && filteredBranches && (
|
||||||
<FormControl label="Vercel Preview Branch (Optional)">
|
<FormControl label="Vercel Preview Branch (Optional)" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetBranch}
|
value={targetBranch}
|
||||||
onValueChange={(val) => setTargetBranch(val)}
|
onValueChange={(val) => setTargetBranch(val)}
|
||||||
@@ -192,16 +225,40 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mt-2 mb-6 ml-auto mr-6"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||||
|
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||||
|
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||||
|
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Vercel with secrets from Infisical.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Head>
|
||||||
|
<title>Set Up Vercel Integration</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||||
|
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||||
|
<p>
|
||||||
|
Something went wrong. Please contact <a
|
||||||
|
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
>
|
||||||
|
support@infisical.com
|
||||||
|
</a> if the issue persists.
|
||||||
|
</p>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user