mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 15:29:46 +00:00
Fix merge conflicts
This commit is contained in:
@@ -38,7 +38,6 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
|
||||
const router = useRouter();
|
||||
const [myRole, setMyRole] = useState('member');
|
||||
const [userProjectMemberships, setUserProjectMemberships] = useState<any[]>([]);
|
||||
console.log(123, userData)
|
||||
|
||||
const workspaceId = router.query.id as string;
|
||||
// Delete the row in the table (e.g. a user)
|
||||
@@ -198,15 +197,15 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
|
||||
</div>
|
||||
</td>
|
||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
<td className="flex items-center max-h-16 overflow-x-auto w-full max-w-xl">
|
||||
{userProjectMemberships[row.userId]
|
||||
? userProjectMemberships[row.userId]?.map((project: any) => (
|
||||
<div key={project.id} className='mx-1 min-w-max px-1.5 bg-mineshaft-500 rounded-sm text-sm text-bunker-200 flex items-center'>
|
||||
<span className='mb-0.5 cursor-default'>{project.name}</span>
|
||||
</div>
|
||||
))
|
||||
: <span className='ml-1 text-bunker-100 rounded-sm px-1 py-0.5 text-sm bg-red/80'>This user isn't part of any projects yet.</span>}
|
||||
</td>
|
||||
<div className="flex items-center max-h-16 overflow-x-auto w-full max-w-xl break-all">
|
||||
{userProjectMemberships[row.userId]
|
||||
? userProjectMemberships[row.userId]?.map((project: any) => (
|
||||
<div key={project._id} className='mx-1 min-w-max px-1.5 bg-mineshaft-500 rounded-sm text-sm text-bunker-200 flex items-center'>
|
||||
<span className='mb-0.5 cursor-default'>{project.name}</span>
|
||||
</div>
|
||||
))
|
||||
: <span className='ml-1 text-bunker-100 rounded-sm px-1 py-0.5 text-sm bg-red/80'>This user isn't part of any projects yet.</span>}
|
||||
</div>
|
||||
</td>
|
||||
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
||||
{myUser !== row.email &&
|
||||
|
||||
@@ -44,9 +44,9 @@ const CloudIntegration = ({
|
||||
tabIndex={0}
|
||||
className={`relative ${
|
||||
cloudIntegrationOption.isAvailable
|
||||
? 'hover:bg-white/10 duration-200 cursor-pointer'
|
||||
? 'cursor-pointer duration-200 hover:bg-white/10'
|
||||
: 'opacity-50'
|
||||
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
|
||||
} flex h-32 flex-row items-center rounded-md bg-white/5 p-4`}
|
||||
onClick={() => {
|
||||
if (!cloudIntegrationOption.isAvailable) return;
|
||||
setSelectedIntegrationOption(cloudIntegrationOption);
|
||||
@@ -61,22 +61,22 @@ const CloudIntegration = ({
|
||||
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 className="ml-4 max-w-xs text-3xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
<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">
|
||||
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
{cloudIntegrationOption.name}
|
||||
</div>
|
||||
)}
|
||||
{cloudIntegrationOption.isAvailable &&
|
||||
integrationAuths
|
||||
.map((authorization) => authorization.integration)
|
||||
.map((authorization) => authorization?.integration)
|
||||
.includes(cloudIntegrationOption.slug) && (
|
||||
<div className="absolute group z-40 top-0 right-0 flex flex-row">
|
||||
<div className="group absolute top-0 right-0 z-40 flex flex-row">
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
@@ -86,8 +86,7 @@ const CloudIntegration = ({
|
||||
const deletedIntegrationAuth = await deleteIntegrationAuth({
|
||||
integrationAuthId: integrationAuths
|
||||
.filter(
|
||||
(authorization) =>
|
||||
authorization.integration === cloudIntegrationOption.slug
|
||||
(authorization) => authorization.integration === cloudIntegrationOption.slug
|
||||
)
|
||||
.map((authorization) => authorization._id)[0]
|
||||
});
|
||||
@@ -96,20 +95,20 @@ const CloudIntegration = ({
|
||||
integrationAuth: deletedIntegrationAuth
|
||||
});
|
||||
}}
|
||||
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"
|
||||
className="flex w-max cursor-pointer flex-row items-center rounded-b-md bg-red py-0.5 px-2 text-xs opacity-0 duration-200 group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faX} className="text-xs mr-2 py-px" />
|
||||
<FontAwesomeIcon icon={faX} className="mr-2 py-px text-xs" />
|
||||
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" />
|
||||
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-primary py-0.5 px-2 text-xs text-black opacity-90 duration-200 group-hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
|
||||
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">
|
||||
<div className="group absolute top-0 right-0 z-50 flex flex-row">
|
||||
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-yellow py-0.5 px-2 text-xs text-black opacity-90">
|
||||
Coming Soon
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,8 +45,8 @@ type Props = {
|
||||
handleDeleteIntegration: (args: { integration: Integration }) => void;
|
||||
};
|
||||
|
||||
const IntegrationTile = ({
|
||||
integration,
|
||||
const IntegrationTile = ({
|
||||
integration,
|
||||
integrations,
|
||||
bot,
|
||||
setBot,
|
||||
@@ -57,7 +57,7 @@ const IntegrationTile = ({
|
||||
|
||||
// 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) || {
|
||||
environments.find(({ slug }) => slug === integration?.environment) || {
|
||||
name: '',
|
||||
slug: ''
|
||||
}
|
||||
@@ -69,11 +69,10 @@ const IntegrationTile = ({
|
||||
|
||||
useEffect(() => {
|
||||
const loadIntegration = async () => {
|
||||
|
||||
const tempApps: [IntegrationApp] = await getIntegrationApps({
|
||||
integrationAuthId: integration.integrationAuth
|
||||
integrationAuthId: integration?.integrationAuth
|
||||
});
|
||||
|
||||
|
||||
setApps(tempApps);
|
||||
|
||||
if (integration?.app) {
|
||||
@@ -90,15 +89,16 @@ const IntegrationTile = ({
|
||||
case 'vercel':
|
||||
setIntegrationTargetEnvironment(
|
||||
integration?.targetEnvironment
|
||||
? integration.targetEnvironment.charAt(0).toUpperCase() + integration.targetEnvironment.substring(1)
|
||||
: 'Development'
|
||||
? integration.targetEnvironment.charAt(0).toUpperCase() +
|
||||
integration.targetEnvironment.substring(1)
|
||||
: 'Development'
|
||||
);
|
||||
break;
|
||||
case 'netlify':
|
||||
setIntegrationTargetEnvironment(
|
||||
integration?.targetEnvironment
|
||||
? contextNetlifyMapping[integration.targetEnvironment]
|
||||
: 'Local development'
|
||||
integration?.targetEnvironment
|
||||
? contextNetlifyMapping[integration.targetEnvironment]
|
||||
: 'Local development'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
@@ -108,7 +108,7 @@ const IntegrationTile = ({
|
||||
|
||||
loadIntegration();
|
||||
}, []);
|
||||
|
||||
|
||||
const handleStartIntegration = async () => {
|
||||
const reformatTargetEnvironment = (targetEnvironment: string) => {
|
||||
switch (integration.integration) {
|
||||
@@ -119,13 +119,13 @@ const IntegrationTile = ({
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
||||
const appId = siteApp?.appId ?? null;
|
||||
const owner = siteApp?.owner ?? null;
|
||||
|
||||
|
||||
// return updated integration
|
||||
const updatedIntegration = await updateIntegration({
|
||||
integrationId: integration._id,
|
||||
@@ -136,15 +136,15 @@ const IntegrationTile = ({
|
||||
targetEnvironment: reformatTargetEnvironment(integrationTargetEnvironment),
|
||||
owner
|
||||
});
|
||||
|
||||
|
||||
setIntegrations(
|
||||
integrations.map((i) => i._id === updatedIntegration._id ? updatedIntegration : i)
|
||||
integrations.map((i) => (i._id === updatedIntegration._id ? updatedIntegration : i))
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const renderIntegrationSpecificParams = (integration: Integration) => {
|
||||
try {
|
||||
@@ -152,7 +152,7 @@ const IntegrationTile = ({
|
||||
case 'vercel':
|
||||
return (
|
||||
<div>
|
||||
<div className="text-gray-400 text-xs font-semibold mb-2 w-60">ENVIRONMENT</div>
|
||||
<div className="mb-2 w-60 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
|
||||
<ListBox
|
||||
data={!integration.isActive ? ['Development', 'Preview', 'Production'] : null}
|
||||
isSelected={integrationTargetEnvironment}
|
||||
@@ -164,7 +164,7 @@ const IntegrationTile = ({
|
||||
case 'netlify':
|
||||
return (
|
||||
<div>
|
||||
<div className="text-gray-400 text-xs font-semibold mb-2">CONTEXT</div>
|
||||
<div className="mb-2 text-xs font-semibold text-gray-400">CONTEXT</div>
|
||||
<ListBox
|
||||
data={
|
||||
!integration.isActive
|
||||
@@ -189,10 +189,10 @@ const IntegrationTile = ({
|
||||
if (!integrationApp) return <div />;
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between">
|
||||
<div className="mx-6 mb-8 flex max-w-5xl justify-between rounded-md bg-white/5 p-6">
|
||||
<div className="flex">
|
||||
<div>
|
||||
<p className="text-gray-400 text-xs font-semibold mb-2">ENVIRONMENT</p>
|
||||
<p className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</p>
|
||||
<ListBox
|
||||
data={!integration.isActive ? environments.map(({ name }) => name) : null}
|
||||
isSelected={integrationEnvironment.name}
|
||||
@@ -208,7 +208,7 @@ const IntegrationTile = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<FontAwesomeIcon icon={faArrowRight} className="mx-4 text-gray-400 mt-8" />
|
||||
<FontAwesomeIcon icon={faArrowRight} className="mx-4 mt-8 text-gray-400" />
|
||||
</div>
|
||||
<div className="mr-2">
|
||||
<p className="text-gray-400 text-xs font-semibold mb-2">INTEGRATION</p>
|
||||
@@ -218,7 +218,7 @@ const IntegrationTile = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mr-2">
|
||||
<div className="text-gray-400 text-xs font-semibold mb-2">APP</div>
|
||||
<div className="mb-2 text-xs font-semibold text-gray-400">APP</div>
|
||||
<ListBox
|
||||
data={!integration.isActive ? apps.map((app) => app.name) : null}
|
||||
isSelected={integrationApp}
|
||||
@@ -231,9 +231,9 @@ const IntegrationTile = ({
|
||||
</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 className="flex max-w-5xl flex-row items-center rounded-md bg-white/5 p-2 px-4">
|
||||
<FontAwesomeIcon icon={faRotate} className="mr-2.5 animate-spin text-lg text-primary" />
|
||||
<div className="font-semibold text-gray-300">In Sync</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
@@ -243,11 +243,13 @@ const IntegrationTile = ({
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
|
||||
<div className="ml-2 opacity-50 duration-200 hover:opacity-100">
|
||||
<Button
|
||||
onButtonPressed={() => handleDeleteIntegration({
|
||||
integration
|
||||
})}
|
||||
onButtonPressed={() =>
|
||||
handleDeleteIntegration({
|
||||
integration
|
||||
})
|
||||
}
|
||||
color="red"
|
||||
size="icon-md"
|
||||
icon={faX}
|
||||
|
||||
@@ -26,7 +26,7 @@ interface Integration {
|
||||
}
|
||||
|
||||
const ProjectIntegrationSection = ({
|
||||
integrations,
|
||||
integrations,
|
||||
setIntegrations,
|
||||
bot,
|
||||
setBot,
|
||||
@@ -35,22 +35,20 @@ const ProjectIntegrationSection = ({
|
||||
}: 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 integrations with third-party services.
|
||||
</p>
|
||||
<div className="mx-4 mb-4 mt-6 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
|
||||
<h1 className="text-3xl font-semibold">Current Integrations</h1>
|
||||
<p className="text-base text-gray-400">Manage integrations with third-party services.</p>
|
||||
</div>
|
||||
{integrations.map((integration: Integration) => {
|
||||
return (
|
||||
<IntegrationTile
|
||||
key={`integration-${integration._id.toString()}`}
|
||||
integration={integration}
|
||||
key={`integration-${integration?._id.toString()}`}
|
||||
integration={integration}
|
||||
integrations={integrations}
|
||||
bot={bot}
|
||||
setBot={setBot}
|
||||
setIntegrations={setIntegrations}
|
||||
environments={environments}
|
||||
environments={environments}
|
||||
handleDeleteIntegration={handleDeleteIntegration}
|
||||
/>
|
||||
);
|
||||
@@ -59,6 +57,6 @@ const ProjectIntegrationSection = ({
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectIntegrationSection;
|
||||
};
|
||||
|
||||
export default ProjectIntegrationSection;
|
||||
|
||||
@@ -38,6 +38,13 @@ export const Secondary: Story = {
|
||||
}
|
||||
};
|
||||
|
||||
export const Star: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
variant: 'star'
|
||||
}
|
||||
};
|
||||
|
||||
export const Danger: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
|
||||
@@ -31,7 +31,9 @@ const buttonVariants = cva(
|
||||
variant: {
|
||||
solid: '',
|
||||
outline: ['bg-transparent', 'border-2', 'border-solid'],
|
||||
plain: ''
|
||||
plain: '',
|
||||
// a constant color not in use on hover or click goes colorSchema color
|
||||
star: 'text-bunker-200 bg-mineshaft-500'
|
||||
},
|
||||
isDisabled: {
|
||||
true: 'bg-mineshaft opacity-40 cursor-not-allowed',
|
||||
@@ -53,6 +55,16 @@ const buttonVariants = cva(
|
||||
}
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'star',
|
||||
className: 'hover:bg-primary hover:text-black'
|
||||
},
|
||||
{
|
||||
colorSchema: 'danger',
|
||||
variant: 'star',
|
||||
className: 'hover:bg-red hover:text-white'
|
||||
},
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'outline',
|
||||
|
||||
@@ -46,7 +46,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
className={twMerge(
|
||||
'relative left-4 top-1 overflow-hidden rounded-md bg-bunker-800 border border-mineshaft-500 drop-shadow-xl font-inter text-bunker-100 shadow-md z-[100]',
|
||||
'relative top-1 overflow-hidden rounded-md bg-bunker-800 font-inter text-bunker-100 shadow-md z-[100]',
|
||||
dropdownContainerClassName
|
||||
)}
|
||||
position={position}
|
||||
|
||||
@@ -44,3 +44,5 @@ const plansProd: Mapping = {
|
||||
};
|
||||
|
||||
export const plans = plansProd || plansDev;
|
||||
|
||||
export const leaveConfirmDefaultMessage = 'Do you want to save your results before leaving this page?';
|
||||
|
||||
@@ -27,7 +27,7 @@ export const OrgProvider = ({ children }: Props): JSX.Element => {
|
||||
const value = useMemo<TOrgContext>(
|
||||
() => ({
|
||||
orgs: userOrgs,
|
||||
currentOrg: (userOrgs || []).find(({ _id }) => _id === currentWsOrgID),
|
||||
currentOrg: (userOrgs || []).find(({ _id }) => _id === currentWsOrgID) || (userOrgs || [])[0],
|
||||
isLoading
|
||||
}),
|
||||
[currentWsOrgID, userOrgs, isLoading]
|
||||
|
||||
@@ -9,7 +9,6 @@ import getActionData from '@app/ee/api/secrets/GetActionData';
|
||||
import patienceDiff from '@app/ee/utilities/findTextDifferences';
|
||||
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
|
||||
|
||||
import DashboardInputField from '../../components/dashboard/DashboardInputField';
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric
|
||||
@@ -130,7 +129,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
<div
|
||||
className={`absolute border-l border-mineshaft-500 ${
|
||||
isLoading ? 'bg-bunker-800' : 'bg-bunker'
|
||||
} fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}
|
||||
} fixed h-[calc(100vh-56px)] w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full mb-8">
|
||||
@@ -142,7 +141,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-min overflow-y-auto">
|
||||
<div className="h-min">
|
||||
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
||||
<p className="font-semibold text-lg text-bunker-200">
|
||||
{t(`activity:event.${actionMetaData?.name}`)}
|
||||
@@ -157,7 +156,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
<FontAwesomeIcon icon={faXmark} className="w-4 h-4 text-bunker-300 cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col px-4">
|
||||
<div className="flex flex-col px-4 overflow-y-auto h-[calc(100vh-120px)] overflow-y-autp">
|
||||
{(actionMetaData?.name === 'readSecrets' ||
|
||||
actionMetaData?.name === 'addSecrets' ||
|
||||
actionMetaData?.name === 'deleteSecrets') &&
|
||||
@@ -166,14 +165,9 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
<div className="text-xs text-bunker-200 mt-4 pl-1 ph-no-capture">
|
||||
{item.newSecretVersion.key}
|
||||
</div>
|
||||
<DashboardInputField
|
||||
onChangeHandler={() => {}}
|
||||
type="value"
|
||||
position={1}
|
||||
value={item.newSecretVersion.value}
|
||||
isDuplicate={false}
|
||||
blurred={false}
|
||||
/>
|
||||
<div className='w-full font-mono text-sm break-all bg-mineshaft-600 px-2 py-0.5 rounded-md border border-mineshaft-500 text-bunker-200'>
|
||||
{item.newSecretVersion.value ? <span> {item.newSecretVersion.value} </span> : <span className='text-bunker-400'> EMPTY </span>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{actionMetaData?.name === 'updateSecrets' &&
|
||||
@@ -182,8 +176,8 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
<div className="text-xs text-bunker-200 mt-4 pl-1">
|
||||
{item.newSecretVersion.key}
|
||||
</div>
|
||||
<div className="text-bunker-100 font-mono rounded-md overflow-hidden">
|
||||
<div className="bg-red/30 px-2 ph-no-capture">
|
||||
<div className="break-all text-bunker-200 font-mono rounded-md overflow-hidden border border-mineshaft-500">
|
||||
<div className="bg-red/40 px-2 ph-no-capture">
|
||||
-{' '}
|
||||
{patienceDiff(
|
||||
item.oldSecretVersion.value.split(''),
|
||||
@@ -194,14 +188,14 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
character.bIndex !== -1 && (
|
||||
<span
|
||||
key={`actionData.${id + 1}.line.${lineId + 1}`}
|
||||
className={`${character.aIndex === -1 && 'bg-red-700/80'}`}
|
||||
className={`${character.aIndex === -1 && 'text-bunker-100 bg-red-700/80'}`}
|
||||
>
|
||||
{character.line}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-green-500/30 px-2 ph-no-capture">
|
||||
<div className="break-all bg-green-500/40 px-2 ph-no-capture">
|
||||
+{' '}
|
||||
{patienceDiff(
|
||||
item.oldSecretVersion.value.split(''),
|
||||
@@ -212,7 +206,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
character.aIndex !== -1 && (
|
||||
<span
|
||||
key={`actionData.${id + 1}.linev2.${lineId + 1}`}
|
||||
className={`${character.bIndex === -1 && 'bg-green-700/80'}`}
|
||||
className={`${character.bIndex === -1 && 'text-bunker-100 bg-green-700/80'}`}
|
||||
>
|
||||
{character.line}
|
||||
</span>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { useLeaveConfirm } from './useLeaveConfirm';
|
||||
export { usePopUp } from './usePopUp';
|
||||
export { useToggle } from './useToggle';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { leaveConfirmDefaultMessage } from '@app/const';
|
||||
|
||||
type LeaveConfirmProps = {
|
||||
initialValue: boolean,
|
||||
message?: string
|
||||
}
|
||||
|
||||
interface LeaveConfirmReturn {
|
||||
hasUnsavedChanges: boolean,
|
||||
setHasUnsavedChanges: Dispatch<SetStateAction<boolean>>,
|
||||
}
|
||||
|
||||
export function useLeaveConfirm({
|
||||
initialValue,
|
||||
message = leaveConfirmDefaultMessage,
|
||||
}: LeaveConfirmProps): LeaveConfirmReturn {
|
||||
const router = useRouter()
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState<boolean>(initialValue);
|
||||
|
||||
const onRouteChangeStart = useCallback(() => {
|
||||
if (hasUnsavedChanges) {
|
||||
if (window.confirm(message)) {
|
||||
return true
|
||||
}
|
||||
throw new Error("Abort route change by user's confirmation.")
|
||||
}
|
||||
return false;
|
||||
}, [hasUnsavedChanges])
|
||||
|
||||
const handleWindowClose = useCallback((e: any) => {
|
||||
if (!hasUnsavedChanges) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.returnValue = message;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
router.events.on("routeChangeStart", onRouteChangeStart);
|
||||
window.addEventListener('beforeunload', handleWindowClose);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", onRouteChangeStart);
|
||||
window.removeEventListener('beforeunload', handleWindowClose);
|
||||
}
|
||||
}, [onRouteChangeStart, handleWindowClose]);
|
||||
|
||||
return {
|
||||
hasUnsavedChanges,
|
||||
setHasUnsavedChanges,
|
||||
};
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { workspaces, currentWorkspace } = useWorkspace();
|
||||
const { currentOrg } = useOrganization();
|
||||
workspaces = workspaces.filter(ws => ws.organization === currentOrg?._id)
|
||||
workspaces = workspaces.filter((ws) => ws.organization === currentOrg?._id);
|
||||
const { user } = useUser();
|
||||
|
||||
const createWs = useCreateWorkspace();
|
||||
@@ -98,7 +98,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
const putUserInWorkSpace = async () => {
|
||||
if (tempLocalStorage('orgData.id') === '') {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem('orgData.id', userOrgs[0]._id);
|
||||
localStorage.setItem('orgData.id', userOrgs[0]?._id);
|
||||
}
|
||||
|
||||
const orgUserProjects = await getOrganizationUserProjects({
|
||||
@@ -124,7 +124,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
!['callback', 'create', 'authorize'].includes(intendedWorkspaceId) &&
|
||||
!['callback', 'create', 'authorize'].includes(intendedWorkspaceId) && userWorkspaces[0]?._id !== undefined &&
|
||||
!userWorkspaces
|
||||
.map((workspace: { _id: string }) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
@@ -219,50 +219,58 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
<aside className="w-full border-r border-mineshaft-500 bg-mineshaft-900 md:w-60">
|
||||
<nav className="items-between flex h-full flex-col justify-between">
|
||||
<div>
|
||||
{currentWorkspace
|
||||
? <div className="w-full p-4 mt-3 mb-4">
|
||||
<p className="text-xs font-semibold ml-1.5 mb-1 uppercase text-gray-400">Project</p>
|
||||
<Select
|
||||
defaultValue={currentWorkspace?._id}
|
||||
value={currentWorkspace?._id}
|
||||
className="w-full py-2.5 bg-mineshaft-600 font-medium"
|
||||
onValueChange={(value) => {
|
||||
router.push(`/dashboard/${value}`);
|
||||
}}
|
||||
position="popper"
|
||||
dropdownContainerClassName="left-0 text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 z-50"
|
||||
>
|
||||
{workspaces.map(({ _id, name }) => (
|
||||
<SelectItem key={`ws-layout-list-${_id}`} value={_id} className={`${currentWorkspace?._id === _id && "bg-mineshaft-600"}`}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<hr className="mt-1 mb-1 h-px border-0 bg-gray-700" />
|
||||
<div className="w-full">
|
||||
<Button
|
||||
className="w-full py-2 text-bunker-200 bg-mineshaft-500 hover:bg-primary/90 hover:text-black"
|
||||
color="mineshaft"
|
||||
size="sm"
|
||||
onClick={() => handlePopUpOpen('addNewWs')}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
</div>
|
||||
</Select>
|
||||
</div>
|
||||
: <div className="w-full p-4 mt-3 mb-4">
|
||||
<Button
|
||||
className="w-full py-2 text-bunker-200 bg-mineshaft-500 hover:bg-primary/90 hover:text-black"
|
||||
color="mineshaft"
|
||||
size="sm"
|
||||
onClick={() => handlePopUpOpen('addNewWs')}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
</div>}
|
||||
<div className={`${currentWorkspace ? "block" : "hidden"}`}>
|
||||
{currentWorkspace ? (
|
||||
<div className="w-full p-4 mt-3 mb-4">
|
||||
<p className="text-xs font-semibold ml-1.5 mb-1 uppercase text-gray-400">
|
||||
Project
|
||||
</p>
|
||||
<Select
|
||||
defaultValue={currentWorkspace?._id}
|
||||
value={currentWorkspace?._id}
|
||||
className="w-full py-2.5 bg-mineshaft-600 font-medium"
|
||||
onValueChange={(value) => {
|
||||
router.push(`/dashboard/${value}`);
|
||||
}}
|
||||
position="popper"
|
||||
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 z-50"
|
||||
>
|
||||
{workspaces.map(({ _id, name }) => (
|
||||
<SelectItem
|
||||
key={`ws-layout-list-${_id}`}
|
||||
value={_id}
|
||||
className={`${currentWorkspace?._id === _id && 'bg-mineshaft-600'}`}
|
||||
>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<hr className="mt-1 mb-1 h-px border-0 bg-gray-700" />
|
||||
<div className="w-full">
|
||||
<Button
|
||||
className="w-full py-2 text-bunker-200 bg-mineshaft-500 hover:bg-primary/90 hover:text-black"
|
||||
color="mineshaft"
|
||||
size="sm"
|
||||
onClick={() => handlePopUpOpen('addNewWs')}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
</div>
|
||||
</Select>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full p-4 mt-3 mb-4">
|
||||
<Button
|
||||
className="w-full py-2 text-bunker-200 bg-mineshaft-500 hover:bg-primary/90 hover:text-black"
|
||||
color="mineshaft"
|
||||
size="sm"
|
||||
onClick={() => handlePopUpOpen('addNewWs')}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className={`${currentWorkspace ? 'block' : 'hidden'}`}>
|
||||
<Menu>
|
||||
<Link href={`/dashboard/${currentWorkspace?._id}`} passHref>
|
||||
<a>
|
||||
@@ -393,7 +401,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className='pl-1 mt-4'>
|
||||
<div className="pl-1 mt-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="addMembers"
|
||||
@@ -415,11 +423,19 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
key="layout-create-project-submit"
|
||||
className=""
|
||||
className="mr-4"
|
||||
type="submit"
|
||||
>
|
||||
Create Project
|
||||
</Button>
|
||||
<Button
|
||||
key="layout-cancel-create-project"
|
||||
onClick={() => handlePopUpClose('addNewWs')}
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
|
||||
@@ -35,9 +35,11 @@ import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
|
||||
import getSecretsForProject from '@app/components/utilities/secrets/getSecretsForProject';
|
||||
import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps';
|
||||
import { IconButton } from '@app/components/v2';
|
||||
import { leaveConfirmDefaultMessage } from '@app/const';
|
||||
import getProjectSercetSnapshotsCount from '@app/ee/api/secrets/GetProjectSercetSnapshotsCount';
|
||||
import performSecretRollback from '@app/ee/api/secrets/PerformSecretRollback';
|
||||
import PITRecoverySidebar from '@app/ee/components/PITRecoverySidebar';
|
||||
import { useLeaveConfirm } from '@app/hooks';
|
||||
|
||||
import addSecrets from '../api/files/AddSecrets';
|
||||
import deleteSecrets from '../api/files/DeleteSecrets';
|
||||
@@ -116,7 +118,6 @@ function findDuplicates(arr: any[]) {
|
||||
export default function Dashboard() {
|
||||
const [data, setData] = useState<SecretDataProps[] | null>();
|
||||
const [initialData, setInitialData] = useState<SecretDataProps[] | null | undefined>([]);
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [blurred, setBlurred] = useState(true);
|
||||
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
||||
@@ -137,6 +138,7 @@ export default function Dashboard() {
|
||||
const [dropZoneData, setDropZoneData] = useState<SecretDataProps[]>();
|
||||
const [projectTags, setProjectTags] = useState<Tag[]>([]);
|
||||
|
||||
const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({initialValue: false});
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
@@ -153,36 +155,6 @@ export default function Dashboard() {
|
||||
setAtSecretsAreaTop(false);
|
||||
}
|
||||
};
|
||||
// #TODO: fix save message for changing reroutes
|
||||
// const beforeRouteHandler = (url) => {
|
||||
// const warningText =
|
||||
// "Do you want to save your results bfore leaving this page?";
|
||||
// if (!buttonReady) return;
|
||||
// if (router.asPath !== url && !confirm(warningText)) {
|
||||
// // router.events.emit('routeChangeError');
|
||||
// // setData(data)
|
||||
// savePush();
|
||||
// throw `Route change to "${url}" was aborted (this error can be safely ignored).`;
|
||||
// } else {
|
||||
// setButtonReady(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// prompt the user if they try and leave with unsaved changes
|
||||
useEffect(() => {
|
||||
const warningText = 'Do you want to save your results before leaving this page?';
|
||||
const handleWindowClose = (e: any) => {
|
||||
if (!buttonReady) return;
|
||||
e.preventDefault();
|
||||
e.returnValue = warningText;
|
||||
};
|
||||
window.addEventListener('beforeunload', handleWindowClose);
|
||||
// router.events.on('routeChangeStart', beforeRouteHandler);
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleWindowClose);
|
||||
// router.events.off('routeChangeStart', beforeRouteHandler);
|
||||
};
|
||||
}, [buttonReady]);
|
||||
|
||||
// TODO(akhilmhdh): change to FP
|
||||
const sortValuesHandler = (
|
||||
@@ -318,7 +290,7 @@ export default function Dashboard() {
|
||||
};
|
||||
|
||||
const deleteRow = ({ ids, secretName }: { ids: string[]; secretName: string }) => {
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
toggleSidebar('None');
|
||||
createNotification({
|
||||
text: `${secretName || 'Secret'} has been deleted. Remember to save changes.`,
|
||||
@@ -332,27 +304,27 @@ export default function Dashboard() {
|
||||
|
||||
const modifyValue = (value: string, pos: number) => {
|
||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, value } : e)));
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
const modifyValueOverride = (valueOverride: string | undefined, pos: number) => {
|
||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, valueOverride } : e)));
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
const modifyKey = (key: string, pos: number) => {
|
||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, key } : e)));
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
const modifyComment = (comment: string, pos: number) => {
|
||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, comment } : e)));
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
const modifyTags = (tags: Tag[], pos: number) => {
|
||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, tags } : e)));
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
// For speed purposes and better perforamance, we are using useCallback
|
||||
@@ -422,7 +394,7 @@ export default function Dashboard() {
|
||||
}
|
||||
|
||||
// Once "Save changes" is clicked, disable that button
|
||||
setButtonReady(false);
|
||||
setHasUnsavedChanges(false);
|
||||
|
||||
const secretsToBeDeleted = initialData!
|
||||
.filter(
|
||||
@@ -566,7 +538,7 @@ export default function Dashboard() {
|
||||
);
|
||||
return filteredOldData.concat(filteredNewData);
|
||||
});
|
||||
setButtonReady(true);
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
const addData = (newData: SecretDataProps[]) => {
|
||||
@@ -587,6 +559,26 @@ export default function Dashboard() {
|
||||
deleteRow({ ids, secretName });
|
||||
};
|
||||
|
||||
const handleOnEnvironmentChange = (envName: string) => {
|
||||
if(hasUnsavedChanges) {
|
||||
if (!window.confirm(leaveConfirmDefaultMessage)) return;
|
||||
}
|
||||
|
||||
const selectedWorkspaceEnv = workspaceEnvs.find(({ name }: { name: string }) => envName === name) || {
|
||||
name: 'unknown',
|
||||
slug: 'unknown',
|
||||
isWriteDenied: false,
|
||||
isReadDenied: false
|
||||
};
|
||||
|
||||
if (selectedWorkspaceEnv) {
|
||||
if (snapshotData) setSelectedSnapshotEnv(selectedWorkspaceEnv);
|
||||
else setSelectedEnv(selectedWorkspaceEnv);
|
||||
}
|
||||
|
||||
setHasUnsavedChanges(false);
|
||||
};
|
||||
|
||||
return data ? (
|
||||
<div className="bg-bunker-800 max-h-screen h-full relative flex flex-col justify-between text-white dark">
|
||||
<Head>
|
||||
@@ -655,16 +647,7 @@ export default function Dashboard() {
|
||||
<ListBox
|
||||
isSelected={selectedEnv.name}
|
||||
data={workspaceEnvs.map(({ name }) => name)}
|
||||
onChange={(envName) =>
|
||||
setSelectedEnv(
|
||||
workspaceEnvs.find(({ name }) => envName === name) || {
|
||||
name: 'unknown',
|
||||
slug: 'unknown',
|
||||
isWriteDenied: false,
|
||||
isReadDenied: false
|
||||
}
|
||||
)
|
||||
}
|
||||
onChange={handleOnEnvironmentChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -681,14 +664,14 @@ export default function Dashboard() {
|
||||
icon={faClockRotateLeft}
|
||||
/>}
|
||||
</div>
|
||||
{(data?.length !== 0 || buttonReady) && !snapshotData && (
|
||||
{(data?.length !== 0 || hasUnsavedChanges) && !snapshotData && (
|
||||
<div className="flex justify-start max-w-sm mt-1">
|
||||
<Button
|
||||
text={String(t('common:save-changes'))}
|
||||
onButtonPressed={savePush}
|
||||
color="primary"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
active={hasUnsavedChanges}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled={String(t('common:saved'))}
|
||||
loading={saveLoading}
|
||||
@@ -723,11 +706,11 @@ export default function Dashboard() {
|
||||
text: `Rollback has been performed successfully.`,
|
||||
type: 'success'
|
||||
});
|
||||
setButtonReady(false);
|
||||
setHasUnsavedChanges(false);
|
||||
}}
|
||||
color="primary"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
active={hasUnsavedChanges}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -742,31 +725,13 @@ export default function Dashboard() {
|
||||
<ListBox
|
||||
isSelected={selectedEnv.name}
|
||||
data={workspaceEnvs.map(({ name }) => name)}
|
||||
onChange={(envName) =>
|
||||
setSelectedEnv(
|
||||
workspaceEnvs.find(({ name }) => envName === name) || {
|
||||
name: 'unknown',
|
||||
slug: 'unknown',
|
||||
isWriteDenied: false,
|
||||
isReadDenied: false
|
||||
}
|
||||
)
|
||||
}
|
||||
onChange={handleOnEnvironmentChange}
|
||||
/>
|
||||
) : (
|
||||
<ListBox
|
||||
isSelected={selectedSnapshotEnv?.name || ''}
|
||||
data={workspaceEnvs.map(({ name }) => name)}
|
||||
onChange={(envName) =>
|
||||
setSelectedSnapshotEnv(
|
||||
workspaceEnvs.find(({ name }) => envName === name) || {
|
||||
name: 'unknown',
|
||||
slug: 'unknown',
|
||||
isWriteDenied: false,
|
||||
isReadDenied: false
|
||||
}
|
||||
)
|
||||
}
|
||||
onChange={handleOnEnvironmentChange}
|
||||
/>
|
||||
)}
|
||||
<div className="h-10 w-full bg-mineshaft-700 hover:bg-white/10 ml-2 rounded-md flex flex-row items-center">
|
||||
@@ -970,7 +935,7 @@ export default function Dashboard() {
|
||||
setErrorDragAndDrop={setErrorDragAndDrop}
|
||||
createNewFile={addRow}
|
||||
errorDragAndDrop={errorDragAndDrop}
|
||||
setButtonReady={setButtonReady}
|
||||
setButtonReady={setHasUnsavedChanges}
|
||||
keysExist
|
||||
numCurrentRows={data.length}
|
||||
/>
|
||||
@@ -986,7 +951,7 @@ export default function Dashboard() {
|
||||
setErrorDragAndDrop={setErrorDragAndDrop}
|
||||
createNewFile={addRow}
|
||||
errorDragAndDrop={errorDragAndDrop}
|
||||
setButtonReady={setButtonReady}
|
||||
setButtonReady={setHasUnsavedChanges}
|
||||
numCurrentRows={data.length}
|
||||
keysExist={false}
|
||||
/>
|
||||
@@ -1013,7 +978,7 @@ export default function Dashboard() {
|
||||
modifyValue={listenChangeValue}
|
||||
modifyValueOverride={listenChangeValueOverride}
|
||||
modifyComment={listenChangeComment}
|
||||
buttonReady={buttonReady}
|
||||
buttonReady={hasUnsavedChanges}
|
||||
workspaceEnvs={workspaceEnvs}
|
||||
selectedEnv={selectedEnv!}
|
||||
workspaceId={workspaceId}
|
||||
|
||||
@@ -198,6 +198,9 @@ export default function Integrations() {
|
||||
case 'flyio':
|
||||
link = `${window.location.origin}/integrations/flyio/authorize`
|
||||
break;
|
||||
case 'circleci':
|
||||
link = `${window.location.origin}/integrations/circleci/authorize`
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -241,6 +244,9 @@ export default function Integrations() {
|
||||
case 'flyio':
|
||||
link = `${window.location.origin}/integrations/flyio/create?integrationAuthId=${integrationAuth._id}`;
|
||||
break;
|
||||
case 'circleci':
|
||||
link = `${window.location.origin}/integrations/circleci/create?integrationAuthId=${integrationAuth._id}`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
} from '../../../components/v2';
|
||||
import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken";
|
||||
|
||||
export default function CircleCICreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [apiKeyErrorText, setApiKeyErrorText] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setApiKeyErrorText('');
|
||||
if (apiKey.length === 0) {
|
||||
setApiKeyErrorText('API Key cannot be blank');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await saveIntegrationAccessToken({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
integration: 'circleci',
|
||||
accessToken: apiKey,
|
||||
accessId: null,
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(
|
||||
`/integrations/circleci/create?integrationAuthId=${integrationAuth._id}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center'>CircleCI Integration</CardTitle>
|
||||
<FormControl
|
||||
label="CircleCI API Key"
|
||||
errorText={apiKeyErrorText}
|
||||
isError={apiKeyErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder=''
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to CircleCI
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CircleCICreateIntegrationPage.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
|
||||
@@ -0,0 +1,124 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Select,
|
||||
SelectItem
|
||||
} from '../../../components/v2';
|
||||
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth';
|
||||
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
|
||||
import createIntegration from "../../api/integrations/createIntegration";
|
||||
|
||||
export default function CircleCICreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
setTargetApp(integrationAuthApps[0]?.name);
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null,
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(
|
||||
`/integrations/${localStorage.getItem('projectData.id')}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetApp) ? (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center'>CircleCI Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="CircleCI Service"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : <div />
|
||||
}
|
||||
|
||||
CircleCICreateIntegrationPage.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
|
||||
Reference in New Issue
Block a user