Fix merge conflicts

This commit is contained in:
Tuan Dang
2022-12-21 11:36:47 -05:00
134 changed files with 5155 additions and 1488 deletions
+31
View File
@@ -0,0 +1,31 @@
import SecurityClient from "~/utilities/SecurityClient";
interface Props {
workspaceId: string;
}
/**
* This function fetches the bot for a project
* @param {Object} obj
* @param {String} obj.workspaceId
* @returns
*/
const getBot = async ({ workspaceId }: Props) => {
return SecurityClient.fetchCall(
"/api/v1/bot/" + workspaceId,
{
method: "GET",
headers: {
"Content-Type": "application/json",
}
}
).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).bot;
} else {
console.log("Failed to get bot for project");
}
});
};
export default getBot;
@@ -0,0 +1,46 @@
import SecurityClient from "~/utilities/SecurityClient";
interface BotKey {
encryptedKey: string;
nonce: string;
}
interface Props {
botId: string;
isActive: boolean;
botKey: BotKey;
}
/**
* This function sets the active status of a bot and shares a copy of
* the project key (encrypted under the bot's public key) with the
* project's bot
* @param {Object} obj
* @param {String} obj.botId
* @param {String} obj.isActive
* @param {Object} obj.botKey
* @returns
*/
const setBotActiveStatus = async ({ botId, isActive, botKey }: Props) => {
return SecurityClient.fetchCall(
"/api/v1/bot/" + botId + "/active",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
isActive,
botKey
})
}
).then(async (res) => {
if (res && res.status == 200) {
return await res.json();
} else {
console.log("Failed to get bot for project");
}
});
};
export default setBotActiveStatus;
@@ -0,0 +1,21 @@
import SecurityClient from '~/utilities/SecurityClient';
const getIntegrationOptions = () => {
return SecurityClient.fetchCall(
'/api/v1/integration-auth/integration-options',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).integrationOptions;
} else {
console.log('Failed to get (cloud) integration options');
}
});
};
export default getIntegrationOptions;
@@ -1,18 +0,0 @@
import SecurityClient from '~/utilities/SecurityClient';
const getIntegrations = () => {
return SecurityClient.fetchCall('/api/v1/integration/integrations', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).integrations;
} else {
console.log('Failed to get project integrations');
}
});
};
export default getIntegrations;
@@ -0,0 +1,60 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route starts the integration after teh default one if gonna set up.
* Update integration with id [integrationId] to sync envars from the project's
* [environment] to the integration [app] with active state [isActive]
* @param {Object} obj
* @param {String} obj.integrationId - id of integration
* @param {String} obj.app - name of app
* @param {String} obj.environment - project environment to push secrets from
* @param {Boolean} obj.isActive - active state
* @param {String} obj.target - (optional) target (environment) for Vercel integration
* @param {String} obj.context - (optional) context (environment) for Netlify integration
* @param {String} obj.siteId - (optional) app (site_id) for Netlify integration
* @returns
*/
const updateIntegration = ({
integrationId,
app,
environment,
isActive,
target,
context,
siteId
}: {
integrationId: string,
app: string,
environment: string,
isActive: boolean,
target: string | null,
context: string | null,
siteId: string | null
}) => {
return SecurityClient.fetchCall(
"/api/v1/integration/" + integrationId,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
app,
environment,
isActive,
target,
context,
siteId
}),
}
).then(async (res) => {
if (res && res.status == 200) {
return res;
} else {
console.log("Failed to start an integration");
}
});
};
export default updateIntegration;
-23
View File
@@ -413,29 +413,6 @@ export default function Dashboard() {
setButtonReady(false);
pushKeys({ obj, workspaceId: router.query.id, env });
/**
* Check which integrations are active for this project and environment
* If there are any, update environment variables for those integrations
*/
let integrations = await getWorkspaceIntegrations({
workspaceId: router.query.id
});
integrations.map(async (integration) => {
if (
envMapping[env] == integration.environment &&
integration.isActive == true
) {
let objIntegration = Object.assign(
{},
...data.map((row) => ({ [row.key]: row.value }))
);
await pushKeysIntegration({
obj: objIntegration,
integrationId: integration._id
});
}
});
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
+4 -3
View File
@@ -16,16 +16,17 @@ export default function Heroku() {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state == localStorage.getItem("latestCSRFToken")) {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem("projectData.id"),
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "heroku",
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.log("Error - Not logged in yet");
console.error('Heroku integration error: ', error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+198 -377
View File
@@ -1,199 +1,179 @@
import React, { useEffect, useState } from 'react';
import Head from 'next/head';
import Image from 'next/image';
import { useRouter } from 'next/router';
import {
faArrowRight,
faCheck,
faRotate,
faX
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import Button from '~/components/basic/buttons/Button';
import ListBox from '~/components/basic/Listbox';
import NavHeader from '~/components/navigation/NavHeader';
import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject';
import pushKeysIntegration from '~/components/utilities/secrets/pushKeysIntegration';
import guidGenerator from '~/utilities/randomId';
import ActivateBotDialog from "~/components/basic/dialog/ActivateBotDialog";
import IntegrationAccessTokenDialog from "~/components/basic/dialog/IntegrationAccessTokenDialog";
import CloudIntegrationSection from "~/components/integrations/CloudIntegrationSection";
import FrameworkIntegrationSection from "~/components/integrations/FrameworkIntegrationSection";
import Integration from "~/components/integrations/Integration";
import IntegrationSection from "~/components/integrations/IntegrationSection";
import NavHeader from "~/components/navigation/NavHeader";
import {
envMapping,
frameworks,
reverseEnvMapping
} from '../../public/data/frequentConstants';
import deleteIntegration from '../api/integrations/DeleteIntegration';
import deleteIntegrationAuth from '../api/integrations/DeleteIntegrationAuth';
import getIntegrationApps from '../api/integrations/GetIntegrationApps';
import getIntegrations from '../api/integrations/GetIntegrations';
import getWorkspaceAuthorizations from '../api/integrations/getWorkspaceAuthorizations';
import getWorkspaceIntegrations from '../api/integrations/getWorkspaceIntegrations';
import startIntegration from '../api/integrations/StartIntegration';
const crypto = require('crypto');
const Integration = ({ projectIntegration }) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[projectIntegration.environment]
);
const [fileState, setFileState] = useState([]);
const [data, setData] = useState();
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const router = useRouter();
const [apps, setApps] = useState([]);
const [integrationApp, setIntegrationApp] = useState(
projectIntegration.app ? projectIntegration.app : apps[0]
);
useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({
integrationAuthId: projectIntegration.integrationAuth
});
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
setApps(tempHerokuAppNames);
setIntegrationApp(
projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0]
);
}, []);
return (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={
!projectIntegration.isActive && [
'Development',
'Staging',
'Production'
]
}
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration.charAt(0).toUpperCase() +
projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
integrationId: projectIntegration._id,
environment: envMapping[integrationEnvironment],
appName: integrationApp
});
if (result?.status == 200) {
let currentSecrets = await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id
});
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({
[row.key]: row.value
}))
);
await pushKeysIntegration({
obj,
integrationId: projectIntegration._id
});
router.reload();
}
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: projectIntegration._id
});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
/>
</div>
</div>
</div>
</div>
);
};
import frameworkIntegrationOptions from "../../public/json/frameworkIntegrations.json";
import getBot from "../api/bot/getBot";
import setBotActiveStatus from "../api/bot/setBotActiveStatus";
import getIntegrationOptions from "../api/integrations/GetIntegrationOptions";
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import getLatestFileKey from "../api/workspace/getLatestFileKey";
const {
decryptAssymmetric,
encryptAssymmetric
} = require('../../components/utilities/cryptography/crypto');
const crypto = require("crypto");
export default function Integrations() {
const [integrations, setIntegrations] = useState();
const [projectIntegrations, setProjectIntegrations] = useState();
const [authorizations, setAuthorizations] = useState();
const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]);
const [integrationAuths, setIntegrationAuths] = useState([]);
const [integrations, setIntegrations] = useState([]);
const [bot, setBot] = useState(null);
const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false);
// const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true);
const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null);
const router = useRouter();
const [csrfToken, setCsrfToken] = useState('');
useEffect(async () => {
const tempCSRFToken = crypto.randomBytes(16).toString('hex');
setCsrfToken(tempCSRFToken);
localStorage.setItem('latestCSRFToken', tempCSRFToken);
let projectAuthorizations = await getWorkspaceAuthorizations({
workspaceId: router.query.id
});
setAuthorizations(projectAuthorizations);
const projectIntegrations = await getWorkspaceIntegrations({
workspaceId: router.query.id
});
setProjectIntegrations(projectIntegrations);
try {
const integrationsData = await getIntegrations();
setIntegrations(integrationsData);
} catch (error) {
console.log('Error', error);
// get cloud integration options
setCloudIntegrationOptions(
await getIntegrationOptions()
);
// get project integration authorizations
setIntegrationAuths(
await getWorkspaceAuthorizations({
workspaceId: router.query.id,
})
);
// get project integrations
setIntegrations(
await getWorkspaceIntegrations({
workspaceId: router.query.id,
})
);
// get project bot
setBot(
await getBot({
workspaceId: router.query.id
}
));
} catch (err) {
console.log(err);
}
}, []);
return integrations ? (
/**
* Activate bot for project by performing the following steps:
* 1. Get the (encrypted) project key
* 2. Decrypt project key with user's private key
* 3. Encrypt project key with bot's public key
* 4. Send encrypted project key to backend and set bot status to active
*/
const handleBotActivate = async () => {
let botKey;
try {
if (bot) {
// case: there is a bot
const key = await getLatestFileKey({ workspaceId: router.query.id });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
const WORKSPACE_KEY = decryptAssymmetric({
ciphertext: key.latestKey.encryptedKey,
nonce: key.latestKey.nonce,
publicKey: key.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: WORKSPACE_KEY,
publicKey: bot.publicKey,
privateKey: PRIVATE_KEY
});
botKey = {
encryptedKey: ciphertext,
nonce
}
setBot((await setBotActiveStatus({
botId: bot._id,
isActive: bot.isActive ? false : true,
botKey
})).bot);
}
} catch (err) {
console.error(err);
}
}
/**
* Start integration for a given integration option [integrationOption]
* @param {Object} obj
* @param {Object} obj.integrationOption - an integration option
* @param {String} obj.name
* @param {String} obj.type
* @param {String} obj.docsLink
* @returns
*/
const handleIntegrationOption = async ({ integrationOption }) => {
try {
// generate CSRF token for OAuth2 code-token exchange integrations
const state = crypto.randomBytes(16).toString("hex");
localStorage.setItem('latestCSRFToken', state);
switch (integrationOption.name) {
case 'Heroku':
window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
break;
case 'Vercel':
window.location = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
break;
case 'Netlify':
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
break;
// case 'Fly.io':
// console.log('fly.io');
// setIntegrationAccessTokenDialogOpen(true);
// break;
}
} catch (err) {
console.log(err);
}
}
/**
* Open dialog to activate bot if bot is not active.
* Otherwise, start integration [integrationOption]
* @param {Object} integrationOption - an integration option
* @param {String} integrationOption.name
* @param {String} integrationOption.type
* @param {String} integrationOption.docsLink
* @returns
*/
const integrationOptionPress = (integrationOption) => {
try {
if (bot.isActive) {
// case: bot is active -> proceed with integration
handleIntegrationOption({ integrationOption });
return;
}
// case: bot is not active -> open modal to activate bot
setIsActivateBotDialogOpen(true);
} catch (err) {
console.error(err);
}
}
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Dashboard</title>
@@ -205,200 +185,41 @@ export default function Integrations() {
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<div className="flex flex-row">
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader pageName="Project Integrations" isProjectRelated={true} />
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Current Project Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Manage your integrations of Infisical with third-party services.
</p>
</div>
{projectIntegrations.length > 0 ? (
projectIntegrations.map((projectIntegration) => (
<Integration
key={guidGenerator()}
projectIntegration={projectIntegration}
/>
))
) : (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
<div className="mb-1">
You {"don't"} have any integrations set up yet. When you do,
they will appear here.
</div>
<div className="">
To start, click on any of the options below. It takes 5 clicks
to set up.
</div>
</div>
</div>
)}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
Platform & Cloud Integrations
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This will let your
environment variables flow automatically into selected third-party
services.
</p>
<p className="mr-4 text-xs text-gray-600 mt-1">
Note: during an integration with Heroku, for security reasons, it
is impossible to maintain end-to-end encryption. In theory, this
lets Infisical decrypt yor environment variables. In practice, we
can assure you that this will never be done, and it allows us to
protect your secrets from bad actors online. The core Infisical
service will always stay end-to-end encrypted. With any questions,
reach out support@infisical.com.
</p>
</div>
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map((integration) => (
<div
className={`relative ${
['Heroku'].includes(integrations[integration].name)
? ''
: 'opacity-50'
}`}
key={integrations[integration].name}
>
<a
href={`${
['Heroku'].includes(integrations[integration].name)
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
: '#'
}`}
rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
['Heroku'].includes(integrations[integration].name)
? 'hover:bg-white/10 duration-200 cursor-pointer'
: 'cursor-default grayscale'
}`}
>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={70}
width={70}
alt="integration logo"
></Image>
{integrations[integration].name.split(' ').length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs">
<div>{integrations[integration].name.split(' ')[0]}</div>
<div className="text-base">
{integrations[integration].name.split(' ')[1]}{' '}
{integrations[integration].name.split(' ')[2]}
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs">
{integrations[integration].name}
</div>
)}
</a>
{['Heroku'].includes(integrations[integration].name) &&
authorizations
.map((authorization) => authorization.integration)
.includes(integrations[integration].name.toLowerCase()) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div
onClick={() => {
deleteIntegrationAuth({
integrationAuthId: authorizations
.filter(
(authorization) =>
authorization.integration ==
integrations[integration].name.toLowerCase()
)
.map((authorization) => authorization._id)[0]
});
router.reload();
}}
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
>
<FontAwesomeIcon
icon={faX}
className="text-xs mr-2 py-px"
/>
Revoke
</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
<FontAwesomeIcon
icon={faCheck}
className="text-xs mr-2"
/>
Authorized
</div>
</div>
)}
{!['Heroku'].includes(integrations[integration].name) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
Coming Soon
</div>
</div>
)}
</div>
))}
</div>
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Framework Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on a framework to get the setup instructions.
</p>
</div>
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
{frameworks.map((framework) => (
<div key={framework.name}>
<a
href={framework.link}
rel="noopener"
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr hover:from-sky-400 hover:to-primary duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
>
<div
className={`font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
framework?.name?.split(' ').length > 1
? 'text-sm px-1'
: 'text-xl px-2'
} text-center w-full max-w-xs`}
>
{framework?.image && (
<Image
src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90}
alt="integration logo"
></Image>
)}
{framework?.name && framework?.image && (
<div className="h-2"></div>
)}
{framework?.name && framework.name}
</div>
</a>
</div>
))}
</div>
</div>
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader
pageName="Project Integrations"
isProjectRelated={true}
/>
<ActivateBotDialog
isOpen={isActivateBotDialogOpen}
closeModal={() => setIsActivateBotDialogOpen(false)}
selectedIntegrationOption={selectedIntegrationOption}
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/>
{/* <IntegrationAccessTokenDialog
isOpen={isIntegrationAccessTokenDialogOpen}
closeModal={() => setIntegrationAccessTokenDialogOpen(false)}
selectedIntegrationOption={selectedIntegrationOption}
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/> */}
<IntegrationSection integrations={integrations} />
{cloudIntegrationOptions.length > 0 ? (
<CloudIntegrationSection
cloudIntegrationOptions={cloudIntegrationOptions}
setSelectedIntegrationOption={setSelectedIntegrationOption}
integrationOptionPress={integrationOptionPress}
integrationAuths={integrationAuths}
/>
) : (
<div></div>
)}
<FrameworkIntegrationSection
frameworks={frameworkIntegrationOptions}
/>
</div>
</div>
) : (
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
}
+41
View File
@@ -0,0 +1,41 @@
import React, { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Netlify() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
// modify comment here
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "netlify"
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (err) {
console.error('Netlify integration error: ', err);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Netlify.requireAuth = true;
+40
View File
@@ -0,0 +1,40 @@
import React, { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Vercel() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "vercel"
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (err) {
console.error('Vercel integration error: ', err);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Vercel.requireAuth = true;