mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 12:38:36 +00:00
chore(frontend): changed source code to src folder from root
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import deleteIntegrationAuth from '../../pages/api/integrations/DeleteIntegrationAuth';
|
||||
|
||||
interface CloudIntegrationOption {
|
||||
isAvailable: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
clientId: string;
|
||||
docsLink: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface IntegrationAuth {
|
||||
_id: string;
|
||||
integration: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
cloudIntegrationOption: CloudIntegrationOption;
|
||||
setSelectedIntegrationOption: (
|
||||
cloudIntegration: CloudIntegrationOption
|
||||
) => void;
|
||||
integrationOptionPress: (
|
||||
cloudIntegrationOption: CloudIntegrationOption
|
||||
) => void;
|
||||
integrationAuths: IntegrationAuth[];
|
||||
}
|
||||
|
||||
const CloudIntegration = ({
|
||||
cloudIntegrationOption,
|
||||
setSelectedIntegrationOption,
|
||||
integrationOptionPress,
|
||||
integrationAuths,
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
return integrationAuths ? (
|
||||
<div
|
||||
className={`relative ${
|
||||
cloudIntegrationOption.isAvailable
|
||||
? 'hover:bg-white/10 duration-200 cursor-pointer'
|
||||
: 'opacity-50'
|
||||
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
|
||||
onClick={() => {
|
||||
if (!cloudIntegrationOption.isAvailable) return;
|
||||
setSelectedIntegrationOption(cloudIntegrationOption);
|
||||
integrationOptionPress(cloudIntegrationOption);
|
||||
}}
|
||||
key={cloudIntegrationOption.name}
|
||||
>
|
||||
<Image
|
||||
src={`/images/integrations/${cloudIntegrationOption.name}.png`}
|
||||
height={70}
|
||||
width={70}
|
||||
alt='integration logo'
|
||||
/>
|
||||
{cloudIntegrationOption.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>{cloudIntegrationOption.name.split(' ')[0]}</div>
|
||||
<div className='text-base'>
|
||||
{cloudIntegrationOption.name.split(' ')[1]}{' '}
|
||||
{cloudIntegrationOption.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'>
|
||||
{cloudIntegrationOption.name}
|
||||
</div>
|
||||
)}
|
||||
{cloudIntegrationOption.isAvailable &&
|
||||
integrationAuths
|
||||
.map((authorization) => authorization.integration)
|
||||
.includes(cloudIntegrationOption.name.toLowerCase()) && (
|
||||
<div className='absolute group z-40 top-0 right-0 flex flex-row'>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
deleteIntegrationAuth({
|
||||
integrationAuthId: integrationAuths
|
||||
.filter(
|
||||
(authorization) =>
|
||||
authorization.integration ==
|
||||
cloudIntegrationOption.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>
|
||||
)}
|
||||
{!cloudIntegrationOption.isAvailable && (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudIntegration;
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import CloudIntegration from './CloudIntegration';
|
||||
|
||||
interface CloudIntegrationOption {
|
||||
isAvailable: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
clientId: string;
|
||||
docsLink: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
cloudIntegrationOptions: CloudIntegrationOption[];
|
||||
setSelectedIntegrationOption: () => void;
|
||||
integrationOptionPress: () => void;
|
||||
integrationAuths: any;
|
||||
}
|
||||
|
||||
const CloudIntegrationSection = ({
|
||||
cloudIntegrationOptions,
|
||||
setSelectedIntegrationOption,
|
||||
integrationOptionPress,
|
||||
integrationAuths,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}
|
||||
>
|
||||
<h1 className='font-semibold text-3xl'>
|
||||
{t('integrations:cloud-integrations')}
|
||||
</h1>
|
||||
<p className='text-base text-gray-400'>
|
||||
{t('integrations:click-to-start')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl'>
|
||||
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
|
||||
<CloudIntegration
|
||||
cloudIntegrationOption={cloudIntegrationOption}
|
||||
setSelectedIntegrationOption={setSelectedIntegrationOption}
|
||||
integrationOptionPress={integrationOptionPress}
|
||||
integrationAuths={integrationAuths}
|
||||
key={`cloud-integration-${cloudIntegrationOption.slug}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudIntegrationSection;
|
||||
@@ -0,0 +1,39 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Framework {
|
||||
name: string;
|
||||
slug: string;
|
||||
image: string;
|
||||
docsLink: string;
|
||||
}
|
||||
|
||||
const FrameworkIntegration = ({ framework }: { framework: Framework }) => {
|
||||
return (
|
||||
<a
|
||||
href={framework.docsLink}
|
||||
rel='noopener'
|
||||
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
|
||||
>
|
||||
<div
|
||||
className={`hover:bg-white/10 duration-200 cursor-pointer 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>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrameworkIntegration;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import FrameworkIntegration from './FrameworkIntegration';
|
||||
|
||||
interface Framework {
|
||||
name: string;
|
||||
image: string;
|
||||
link: string;
|
||||
slug: string;
|
||||
docsLink: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
frameworks: [Framework];
|
||||
}
|
||||
|
||||
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2'>
|
||||
<h1 className='font-semibold text-3xl'>
|
||||
{t('integrations:framework-integrations')}
|
||||
</h1>
|
||||
<p className='text-base text-gray-400'>
|
||||
{t('integrations:click-to-setup')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl'>
|
||||
{frameworks.map((framework) => (
|
||||
<FrameworkIntegration
|
||||
framework={framework}
|
||||
key={`framework-integration-${framework.slug}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrameworkIntegrationSection;
|
||||
@@ -0,0 +1,254 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faArrowRight,
|
||||
faRotate,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
// TODO: This needs to be moved from public folder
|
||||
import {
|
||||
contextNetlifyMapping,
|
||||
reverseContextNetlifyMapping,
|
||||
} from "public/data/frequentConstants";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import ListBox from "~/components/basic/Listbox";
|
||||
|
||||
import deleteIntegration from "../../pages/api/integrations/DeleteIntegration"
|
||||
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
|
||||
import updateIntegration from "../../pages/api/integrations/updateIntegration"
|
||||
|
||||
interface Integration {
|
||||
_id: string;
|
||||
app?: string;
|
||||
target?: string;
|
||||
environment: string;
|
||||
integration: string;
|
||||
integrationAuth: string;
|
||||
isActive: boolean;
|
||||
context: string;
|
||||
}
|
||||
|
||||
interface IntegrationApp {
|
||||
name: string;
|
||||
siteId: string;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
integration: Integration;
|
||||
environments: Array<{ name: string; slug: string }>;
|
||||
};
|
||||
|
||||
const Integration = ({
|
||||
integration,
|
||||
environments = []
|
||||
}:Props ) => {
|
||||
// set initial environment. This find will only execute when component is mounting
|
||||
const [integrationEnvironment, setIntegrationEnvironment] = useState<
|
||||
Props['environments'][0]
|
||||
>(
|
||||
environments.find(({ slug }) => slug === integration.environment) || {
|
||||
name: '',
|
||||
slug: '',
|
||||
}
|
||||
);
|
||||
const [fileState, setFileState] = useState([]);
|
||||
const router = useRouter();
|
||||
const [apps, setApps] = useState<IntegrationApp[]>([]); // integration app objects
|
||||
const [integrationApp, setIntegrationApp] = useState(""); // integration app name
|
||||
const [integrationTarget, setIntegrationTarget] = useState(""); // vercel-specific integration param
|
||||
const [integrationContext, setIntegrationContext] = useState(""); // netlify-specific integration param
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const loadIntegration = async () => {
|
||||
interface App {
|
||||
name: string;
|
||||
siteId?: string;
|
||||
}
|
||||
|
||||
const tempApps: [IntegrationApp] = await getIntegrationApps({
|
||||
integrationAuthId: integration.integrationAuth,
|
||||
});
|
||||
|
||||
setApps(tempApps);
|
||||
setIntegrationApp(
|
||||
integration.app ? integration.app : tempApps[0].name
|
||||
);
|
||||
|
||||
switch (integration.integration) {
|
||||
case "vercel":
|
||||
setIntegrationTarget(
|
||||
integration?.target
|
||||
? integration.target.charAt(0).toUpperCase() + integration.target.substring(1)
|
||||
: "Development"
|
||||
);
|
||||
break;
|
||||
case "netlify":
|
||||
setIntegrationContext(integration?.context ? contextNetlifyMapping[integration.context] : "Local development");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
loadIntegration();
|
||||
}, []);
|
||||
|
||||
const renderIntegrationSpecificParams = (integration: Integration) => {
|
||||
try {
|
||||
switch (integration.integration) {
|
||||
case "vercel":
|
||||
return (
|
||||
<div>
|
||||
<div className="text-gray-400 text-xs font-semibold mb-2 w-60">
|
||||
ENVIRONMENT
|
||||
</div>
|
||||
<ListBox
|
||||
data={!integration.isActive ? [
|
||||
"Development",
|
||||
"Preview",
|
||||
"Production"
|
||||
] : null}
|
||||
selected={integrationTarget}
|
||||
onChange={setIntegrationTarget}
|
||||
isFull={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "netlify":
|
||||
return (
|
||||
<div>
|
||||
<div className="text-gray-400 text-xs font-semibold mb-2">
|
||||
CONTEXT
|
||||
</div>
|
||||
<ListBox
|
||||
data={!integration.isActive ? [
|
||||
"Production",
|
||||
"Deploy previews",
|
||||
"Branch deploys",
|
||||
"Local development"
|
||||
] : null}
|
||||
selected={integrationContext}
|
||||
onChange={setIntegrationContext}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <div></div>;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!integrationApp || apps.length === 0) return <div></div>
|
||||
|
||||
return (
|
||||
<div className='max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between'>
|
||||
<div className='flex'>
|
||||
<div>
|
||||
<p className='text-gray-400 text-xs font-semibold mb-2'>
|
||||
ENVIRONMENT
|
||||
</p>
|
||||
<ListBox
|
||||
data={
|
||||
!integration.isActive
|
||||
? environments.map(({ name }) => name)
|
||||
: null
|
||||
}
|
||||
selected={integrationEnvironment.name}
|
||||
onChange={(envName) =>
|
||||
setIntegrationEnvironment(
|
||||
environments.find(({ name }) => envName === name) || {
|
||||
name: 'unknown',
|
||||
slug: 'unknown',
|
||||
}
|
||||
)
|
||||
}
|
||||
isFull={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='pt-2'>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className='mx-4 text-gray-400 mt-8'
|
||||
/>
|
||||
</div>
|
||||
<div className='mr-2'>
|
||||
<p className='text-gray-400 text-xs font-semibold mb-2'>
|
||||
INTEGRATION
|
||||
</p>
|
||||
<div className='py-2.5 bg-white/[.07] rounded-md pl-4 pr-10 text-sm font-semibold text-gray-300'>
|
||||
{integration.integration.charAt(0).toUpperCase() +
|
||||
integration.integration.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mr-2'>
|
||||
<div className='text-gray-400 text-xs font-semibold mb-2'>APP</div>
|
||||
<ListBox
|
||||
data={!integration.isActive ? apps.map((app) => app.name) : null}
|
||||
selected={integrationApp}
|
||||
onChange={(app) => {
|
||||
setIntegrationApp(app);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{renderIntegrationSpecificParams(integration)}
|
||||
</div>
|
||||
<div className='flex items-end'>
|
||||
{integration.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 siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
||||
const siteId = siteApp?.siteId ? siteApp.siteId : null;
|
||||
|
||||
await updateIntegration({
|
||||
integrationId: integration._id,
|
||||
environment: integrationEnvironment.slug,
|
||||
app: integrationApp,
|
||||
isActive: true,
|
||||
target: integrationTarget
|
||||
? integrationTarget.toLowerCase()
|
||||
: null,
|
||||
context: integrationContext
|
||||
? reverseContextNetlifyMapping[integrationContext]
|
||||
: null,
|
||||
siteId,
|
||||
});
|
||||
|
||||
router.reload();
|
||||
}}
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
/>
|
||||
)}
|
||||
<div className='opacity-50 hover:opacity-100 duration-200 ml-2'>
|
||||
<Button
|
||||
onButtonPressed={async () => {
|
||||
await deleteIntegration({
|
||||
integrationId: integration._id,
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
color='red'
|
||||
size='icon-md'
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Integration;
|
||||
@@ -0,0 +1,45 @@
|
||||
import guidGenerator from '~/utilities/randomId';
|
||||
|
||||
import Integration from './Integration';
|
||||
|
||||
interface Props {
|
||||
integrations: any;
|
||||
environments: Array<{ name: string; slug: string }>;
|
||||
}
|
||||
|
||||
interface IntegrationType {
|
||||
_id: string;
|
||||
app?: string;
|
||||
environment: string;
|
||||
integration: string;
|
||||
integrationAuth: string;
|
||||
isActive: boolean;
|
||||
context: string;
|
||||
}
|
||||
|
||||
const ProjectIntegrationSection = ({
|
||||
integrations,
|
||||
environments = [],
|
||||
}: Props) => {
|
||||
return integrations.length > 0 ? (
|
||||
<div className='mb-12'>
|
||||
<div className='flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2'>
|
||||
<h1 className='font-semibold text-3xl'>Current Integrations</h1>
|
||||
<p className='text-base text-gray-400'>
|
||||
Manage your integrations of Infisical with third-party services.
|
||||
</p>
|
||||
</div>
|
||||
{integrations.map((integration: IntegrationType) => (
|
||||
<Integration
|
||||
key={guidGenerator()}
|
||||
integration={integration}
|
||||
environments={environments}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectIntegrationSection;
|
||||
Reference in New Issue
Block a user