mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 20:25:43 +00:00
Fix merge conflicts
This commit is contained in:
@@ -21,11 +21,10 @@ export default function Custom404() {
|
||||
</a>{" "}
|
||||
and we`ll fix it!{" "}
|
||||
</p>
|
||||
<Link
|
||||
href='/dashboard'
|
||||
className='bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold'
|
||||
>
|
||||
Go to Dashboard
|
||||
<Link href='/dashboard'>
|
||||
<div className="mt-8 bg-mineshaft-500 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black font-semibold cursor-default">
|
||||
Go to Dashboard
|
||||
</div>
|
||||
</Link>
|
||||
<Image
|
||||
src='/images/dragon-404.svg'
|
||||
|
||||
@@ -77,8 +77,8 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider>
|
||||
<AuthProvider>
|
||||
<WorkspaceProvider>
|
||||
<OrgProvider>
|
||||
<OrgProvider>
|
||||
<WorkspaceProvider>
|
||||
<SubscriptionProvider>
|
||||
<UserProvider>
|
||||
<NotificationProvider>
|
||||
@@ -90,8 +90,8 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
|
||||
</NotificationProvider>
|
||||
</UserProvider>
|
||||
</SubscriptionProvider>
|
||||
</OrgProvider>
|
||||
</WorkspaceProvider>
|
||||
</WorkspaceProvider>
|
||||
</OrgProvider>
|
||||
</AuthProvider>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -70,7 +70,7 @@ const completeAccountInformationSignup = async ({
|
||||
verifier,
|
||||
organizationName,
|
||||
providerAuthToken,
|
||||
attributionSource,
|
||||
...(attributionSource ? { attributionSource } : {})
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* Will create a new integration session and return it for the given org
|
||||
* @returns
|
||||
*/
|
||||
const createNewIntegrationSession = (organizationId: string) =>
|
||||
SecurityClient.fetchCall(`/api/v1/secret-scanning/create-installation-session/organization/${organizationId}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return res.json();
|
||||
}
|
||||
console.log("Failed to create integration session");
|
||||
console.log("response", res)
|
||||
return undefined;
|
||||
});
|
||||
|
||||
export default createNewIntegrationSession;
|
||||
@@ -0,0 +1,22 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* Will create a new integration session and return it for the given org
|
||||
* @returns
|
||||
*/
|
||||
const getInstallationStatus = (organizationId: string) =>
|
||||
SecurityClient.fetchCall(`/api/v1/secret-scanning/installation-status/organization/${organizationId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).appInstallationComplete;
|
||||
}
|
||||
console.log("Failed to check installation status");
|
||||
console.log("response", res)
|
||||
return undefined;
|
||||
});
|
||||
|
||||
export default getInstallationStatus;
|
||||
@@ -0,0 +1,58 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
export type GitRisks = {
|
||||
_id: string;
|
||||
description: string;
|
||||
startLine: string;
|
||||
endLine: string;
|
||||
startColumn: string;
|
||||
endColumn: string;
|
||||
match: string;
|
||||
secret: string;
|
||||
file: string;
|
||||
symlinkFile: string;
|
||||
commit: string;
|
||||
entropy: string;
|
||||
author: string;
|
||||
email: string;
|
||||
date: string;
|
||||
message: string;
|
||||
tags: string[];
|
||||
ruleID: string;
|
||||
fingerprint: string;
|
||||
status: string;
|
||||
|
||||
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||
isResolved: boolean; // New field for marking risks as resolved
|
||||
riskOwner: string | null; // New field for setting a risk owner (nullable string)
|
||||
installationId: string,
|
||||
repositoryId: string,
|
||||
repositoryLink: string
|
||||
repositoryFullName: string
|
||||
pusher: {
|
||||
name: string,
|
||||
email: string
|
||||
},
|
||||
createdAt: string,
|
||||
organization: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create a new integration session and return it for the given org
|
||||
* @returns
|
||||
*/
|
||||
const getRisksByOrganization = (oranizationId: string): Promise<GitRisks[]> =>
|
||||
SecurityClient.fetchCall(`/api/v1/secret-scanning/organization/${oranizationId}/risks`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).risks;
|
||||
}
|
||||
console.log("Failed to fetch risks");
|
||||
return undefined;
|
||||
});
|
||||
|
||||
export default getRisksByOrganization;
|
||||
@@ -0,0 +1,25 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* Will create a new integration session and return it for the given org
|
||||
* @returns
|
||||
*/
|
||||
const linkGitAppInstallationWithOrganization = (installationId: string, sessionId: string) =>
|
||||
SecurityClient.fetchCall("/api/v1/secret-scanning/link-installation", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
installationId,
|
||||
sessionId
|
||||
})
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return true
|
||||
}
|
||||
console.log("Failed to link installation to organization");
|
||||
return undefined;
|
||||
});
|
||||
|
||||
export default linkGitAppInstallationWithOrganization;
|
||||
@@ -0,0 +1,33 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
export enum RiskStatus {
|
||||
RESOLVED_FALSE_POSITIVE = "RESOLVED_FALSE_POSITIVE",
|
||||
RESOLVED_REVOKED = "RESOLVED_REVOKED",
|
||||
RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED",
|
||||
UNRESOLVED = "UNRESOLVED",
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Will create a new integration session and return it for the given org
|
||||
* @returns
|
||||
*/
|
||||
const updateRiskStatus = (organizationId: string, riskId: string, status: RiskStatus) =>
|
||||
SecurityClient.fetchCall(`/api/v1/secret-scanning/organization/${organizationId}/risks/${riskId}/status`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
status
|
||||
})
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return res.json();
|
||||
}
|
||||
console.log("Failed to link installation to organization");
|
||||
return undefined;
|
||||
});
|
||||
|
||||
export default updateRiskStatus;
|
||||
@@ -1,4 +1,5 @@
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function CliRedirect() {
|
||||
return (
|
||||
@@ -8,9 +9,12 @@ export default function CliRedirect() {
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<div className='flex flex-col items-center justify-center text-gray-200 h-screen w-screen'>
|
||||
<p className='text-4xl mt-32'>Head back to your terminal!</p>
|
||||
<p className='mt-2 mb-1 text-lg'>
|
||||
You've successfully logged into infisical-cli
|
||||
<div className="mb-8 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
|
||||
</div>
|
||||
<p className='text-3xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center'>Head back to your terminal</p>
|
||||
<p className='mb-1 text-lg text-light text-mineshaft-400'>
|
||||
You've successfully logged in to the Infisical CLI
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
import getOrganizations from "./api/organization/getOrgs";
|
||||
|
||||
export default function DashboardRedirect() {
|
||||
const router = useRouter();
|
||||
@@ -12,14 +12,14 @@ export default function DashboardRedirect() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let userWorkspace;
|
||||
let userOrg;
|
||||
try {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push(`/dashboard/${ localStorage.getItem("projectData.id")}`);
|
||||
if (localStorage.getItem("orgData.id")) {
|
||||
router.push(`/org/${localStorage.getItem("orgData.id")}/overview`);
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push(`/dashboard/${ userWorkspace}`);
|
||||
const userOrgs = await getOrganizations();
|
||||
userOrg = userOrgs[0]._id;
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faCheckCircle,
|
||||
faHandPeace,
|
||||
faNetworkWired,
|
||||
faPlug,
|
||||
faPlus,
|
||||
faStar,
|
||||
faUserPlus
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
||||
import { TabsObject } from "@app/components/v2/Tabs";
|
||||
|
||||
import registerUserAction from "../api/userActions/registerUserAction";
|
||||
|
||||
type ItemProps = {
|
||||
text: string;
|
||||
subText: string;
|
||||
complete: boolean;
|
||||
icon: IconProp;
|
||||
time: string;
|
||||
userAction?: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
const learningItem = ({
|
||||
text,
|
||||
subText,
|
||||
complete,
|
||||
icon,
|
||||
time,
|
||||
userAction,
|
||||
link
|
||||
}: ItemProps): JSX.Element => {
|
||||
if (link) {
|
||||
return (
|
||||
<a
|
||||
target={`${link.includes("https") ? "_blank" : "_self"}`}
|
||||
rel="noopener noreferrer"
|
||||
className={`w-full ${complete && "opacity-30 duration-200 hover:opacity-100"}`}
|
||||
href={link}
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction && userAction !== "first_time_secrets_pushed") {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="group relative mb-3 flex h-[5.5rem] w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-bunker-500 py-2 pl-2 pr-6 shadow-xl duration-200 hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="mr-4 flex flex-row items-center">
|
||||
<FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" />
|
||||
{complete && (
|
||||
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-primary" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="mt-0.5 text-xl font-semibold">{text}</div>
|
||||
<div className="text-sm font-normal">{subText}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`w-28 pr-4 text-right text-sm font-semibold ${complete && "text-primary"}`}
|
||||
>
|
||||
{complete ? "Complete!" : `About ${time}`}
|
||||
</div>
|
||||
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction) {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="relative my-1.5 flex h-[5.5rem] w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-dashed border-bunker-400 bg-bunker-700 py-2 pl-2 pr-6 shadow-xl duration-200 hover:bg-bunker-500"
|
||||
>
|
||||
<div className="mr-4 flex flex-row items-center">
|
||||
<FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" />
|
||||
{complete && (
|
||||
<div className="absolute left-11 top-10 h-7 w-7 rounded-full bg-bunker-700">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="absolute left-12 top-16 h-5 w-5 text-4xl text-primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="mt-0.5 text-xl font-semibold">{text}</div>
|
||||
<div className="mt-0.5 text-sm font-normal">{subText}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`w-28 pr-4 text-right text-sm font-semibold ${complete && "text-primary"}`}>
|
||||
{complete ? "Complete!" : `About ${time}`}
|
||||
</div>
|
||||
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This tab is called Home because in the future it will include some company news,
|
||||
* updates, roadmap, relavant blogs, etc. Currently it only has the setup instruction
|
||||
* for the new users
|
||||
*/
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false);
|
||||
const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false);
|
||||
const [hasUserStarred, setHasUserStarred] = useState(false);
|
||||
const [hasUserPushedSecrets, setHasUserPushedSecrets] = useState(false);
|
||||
const [usersInOrg, setUsersInOrg] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onboardingCheck({
|
||||
setHasUserClickedIntro,
|
||||
setHasUserClickedSlack,
|
||||
setHasUserPushedSecrets,
|
||||
setHasUserStarred,
|
||||
setUsersInOrg
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-6 w-full pt-4 lg:mx-0">
|
||||
<Head>
|
||||
<title>Infisical Guide</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="relative mx-auto flex max-w-3xl flex-col items-center px-6 py-6 text-lg text-gray-300 lg:max-w-4xl xl:max-w-5xl">
|
||||
<div className="mt-12 w-full text-left text-5xl font-bold">Your quick start guide</div>
|
||||
<div className="mb-14 w-full pt-2 pb-4 text-left text-lg text-bunker-300">
|
||||
Click on the items below and follow the instructions.
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 hidden h-min lg:block">
|
||||
<Image
|
||||
src="/images/dragon-book.svg"
|
||||
height={250}
|
||||
width={400}
|
||||
alt="start guise dragon illustration"
|
||||
/>
|
||||
</div>
|
||||
{learningItem({
|
||||
text: "Get to know Infisical",
|
||||
subText: "",
|
||||
complete: hasUserClickedIntro,
|
||||
icon: faHandPeace,
|
||||
time: "3 min",
|
||||
userAction: "intro_cta_clicked",
|
||||
link: "https://www.youtube.com/watch?v=PK23097-25I"
|
||||
})}
|
||||
{learningItem({
|
||||
text: "Add your secrets",
|
||||
subText: "Click to see example secrets, and add your own.",
|
||||
complete: hasUserPushedSecrets,
|
||||
icon: faPlus,
|
||||
time: "2 min",
|
||||
userAction: "first_time_secrets_pushed",
|
||||
link: `/dashboard/${router.query.id}`
|
||||
})}
|
||||
<div className="group relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-bunker-500 pl-2 pr-2 pt-4 pb-2 shadow-xl duration-200">
|
||||
<div className="mb-4 flex w-full flex-row items-center pr-4">
|
||||
<div className="mr-4 flex w-full flex-row items-center">
|
||||
<FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" />
|
||||
{false && (
|
||||
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-green" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start pl-0.5">
|
||||
<div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div>
|
||||
<div className="text-sm font-normal">
|
||||
Replace .env files with a more secure and efficient alternative.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green"}`}>
|
||||
About 2 min
|
||||
</div>
|
||||
</div>
|
||||
<TabsObject />
|
||||
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
|
||||
</div>
|
||||
{learningItem({
|
||||
text: "Integrate Infisical with your infrastructure",
|
||||
subText: "Connect Infisical to various 3rd party services and platforms.",
|
||||
complete: false,
|
||||
icon: faPlug,
|
||||
time: "15 min",
|
||||
link: "https://infisical.com/docs/integrations/overview"
|
||||
})}
|
||||
{learningItem({
|
||||
text: "Invite your teammates",
|
||||
subText: "",
|
||||
complete: usersInOrg,
|
||||
icon: faUserPlus,
|
||||
time: "2 min",
|
||||
link: `/settings/org/${router.query.id}?invite`
|
||||
})}
|
||||
{learningItem({
|
||||
text: "Join Infisical Slack",
|
||||
subText: "Have any questions? Ask us!",
|
||||
complete: hasUserClickedSlack,
|
||||
icon: faSlack,
|
||||
time: "1 min",
|
||||
userAction: "slack_cta_clicked",
|
||||
link: "https://join.slack.com/t/infisical-users/shared_invite/zt-1wehzfnzn-1aMo5JcGENJiNAC2SD8Jlg"
|
||||
})}
|
||||
{learningItem({
|
||||
text: "Star Infisical on GitHub",
|
||||
subText: "Like what we're doing? You know what to do! :)",
|
||||
complete: hasUserStarred,
|
||||
icon: faStar,
|
||||
time: "1 min",
|
||||
userAction: "star_cta_clicked",
|
||||
link: "https://github.com/Infisical/infisical"
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Home.requireAuth = true;
|
||||
@@ -0,0 +1,198 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthBitBucketWorkspaces,
|
||||
useGetIntegrationAuthById,
|
||||
} from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
import createIntegration from "../../api/integrations/createIntegration";
|
||||
|
||||
export default function BitBucketCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
const [targetEnvironmentId, setTargetEnvironmentId] = useState("");
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: targetEnvironments } = useGetIntegrationAuthBitBucketWorkspaces((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
workspaceSlug: targetEnvironmentId
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||
} else {
|
||||
setTargetAppId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (targetEnvironments) {
|
||||
if (targetEnvironments.length > 0) {
|
||||
setTargetEnvironmentId(targetEnvironments[0].slug);
|
||||
} else {
|
||||
setTargetEnvironmentId("none");
|
||||
}
|
||||
}
|
||||
}, [targetEnvironments]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
const targetApp = integrationAuthApps?.find(
|
||||
(integrationAuthApp) => integrationAuthApp.appId === targetAppId
|
||||
);
|
||||
const targetEnvironment = targetEnvironments?.find(
|
||||
(environment) => environment.slug === targetEnvironmentId
|
||||
);
|
||||
|
||||
if (!targetApp || !targetApp.appId || !targetEnvironment) return;
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp.name,
|
||||
appId: targetApp.appId,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: targetEnvironment.name,
|
||||
targetEnvironmentId: targetEnvironment.slug,
|
||||
targetService: null,
|
||||
targetServiceId: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetEnvironments ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">BitBucket 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={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="BitBucket Workspace">
|
||||
<Select
|
||||
value={targetEnvironmentId}
|
||||
onValueChange={(val) => setTargetEnvironmentId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={targetEnvironments.length === 0}
|
||||
>
|
||||
{targetEnvironments.length > 0 ? (
|
||||
targetEnvironments.map((targetEnvironment) => (
|
||||
<SelectItem
|
||||
value={targetEnvironment.slug as string}
|
||||
key={`target-environment-${targetEnvironment.slug as string}`}
|
||||
>
|
||||
{targetEnvironment.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-environment-none">
|
||||
No workspaces found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="BitBucket Repo">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.appId as string}
|
||||
key={`target-app-${integrationAuthApp.appId as string}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No repositories found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
BitBucketCreateIntegrationPage.requireAuth = true;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import AuthorizeIntegration from "../../../api/integrations/authorizeIntegration";
|
||||
|
||||
export default function BitBucketOAuth2CallbackPage() {
|
||||
const router = useRouter();
|
||||
const { code, state } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
// validate state
|
||||
if (state !== localStorage.getItem("latestCSRFToken")) return;
|
||||
localStorage.removeItem("latestCSRFToken");
|
||||
|
||||
const integrationAuth = await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem("projectData.id") as string,
|
||||
code: code as string,
|
||||
integration: "bitbucket"
|
||||
});
|
||||
|
||||
router.push(`/integrations/bitbucket/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return <div />;
|
||||
}
|
||||
|
||||
BitBucketOAuth2CallbackPage.requireAuth = true;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken";
|
||||
|
||||
export default function Cloud66CreateIntegrationPage() {
|
||||
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("Access token cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await saveIntegrationAccessToken({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "cloud-66",
|
||||
accessId: null,
|
||||
accessToken: apiKey,
|
||||
url: null,
|
||||
namespace: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/cloud-66/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Cloud 66 Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Cloud 66 Personal Access Token"
|
||||
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 Cloud 66
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Cloud66CreateIntegrationPage.requireAuth = true;
|
||||
@@ -0,0 +1,155 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
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 Cloud66CreateIntegrationPage() {
|
||||
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: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [targetApp, setTargetApp] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp("none");
|
||||
}
|
||||
}
|
||||
}, [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,
|
||||
targetEnvironmentId: null,
|
||||
targetService: null,
|
||||
targetServiceId: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Cloud 66 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={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Cloud 66 Application" className="mt-4">
|
||||
<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 applications found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
Cloud66CreateIntegrationPage.requireAuth = true;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken";
|
||||
|
||||
export default function CodefreshCreateIntegrationPage() {
|
||||
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: "codefresh",
|
||||
accessId: null,
|
||||
accessToken: apiKey,
|
||||
url: null,
|
||||
namespace: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/codefresh/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Codefresh Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Codefresh 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 Codefresh
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CodefreshCreateIntegrationPage.requireAuth = true;
|
||||
@@ -0,0 +1,155 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
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 CodefreshCreateIntegrationPage() {
|
||||
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: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [targetApp, setTargetApp] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp("none");
|
||||
}
|
||||
}
|
||||
}, [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,
|
||||
targetEnvironmentId: null,
|
||||
targetService: null,
|
||||
targetServiceId: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Codefresh 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={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Codefresh Service" className="mt-4">
|
||||
<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 services found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
CodefreshCreateIntegrationPage.requireAuth = true;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken";
|
||||
|
||||
export default function DigitalOceanAppPlatformCreateIntegrationPage() {
|
||||
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: "digital-ocean-app-platform",
|
||||
accessId: null,
|
||||
accessToken: apiKey,
|
||||
url: null,
|
||||
namespace: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/digital-ocean-app-platform/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Digital Ocean App Platform Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Digital Ocean 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 Digital Ocean App Platform
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DigitalOceanAppPlatformCreateIntegrationPage.requireAuth = true;
|
||||
@@ -0,0 +1,155 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
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 DigitalOceanAppPlatformCreateIntegrationPage() {
|
||||
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: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [targetApp, setTargetApp] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp("none");
|
||||
}
|
||||
}
|
||||
}, [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,
|
||||
targetEnvironmentId: null,
|
||||
targetService: null,
|
||||
targetServiceId: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Digital Ocean App Platform 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={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Digital Ocean App Platform Service" className="mt-4">
|
||||
<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 services found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
DigitalOceanAppPlatformCreateIntegrationPage.requireAuth = true;
|
||||
@@ -1,140 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import axios from "axios"
|
||||
|
||||
// import ListBox from '@app/components/basic/Listbox';
|
||||
import InitialLoginStep from "@app/components/login/InitialLoginStep";
|
||||
import MFAStep from "@app/components/login/MFAStep";
|
||||
import PasswordInputStep from "@app/components/login/PasswordInputStep";
|
||||
import { useProviderAuth } from "@app/hooks/useProviderAuth";
|
||||
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
|
||||
|
||||
import { fetchUserDetails } from "~/hooks/api/users/queries";
|
||||
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [step, setStep] = useState(1);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// const lang = router.locale ?? 'en';
|
||||
const {
|
||||
providerAuthToken,
|
||||
email: providerEmail,
|
||||
setProviderAuthToken,
|
||||
isProviderUserCompleted
|
||||
} = useProviderAuth();
|
||||
|
||||
if (providerAuthToken && isProviderUserCompleted === false) {
|
||||
router.push(`/signup?providerAuthToken=${encodeURIComponent(providerAuthToken)}`);
|
||||
}
|
||||
|
||||
// const setLanguage = async (to: string) => {
|
||||
// router.push('/login', '/login', { locale: to });
|
||||
// localStorage.setItem('lang', to);
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
// TODO(akhilmhdh): workspace will be controlled by a workspace context
|
||||
const redirectToDashboard = async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
||||
|
||||
// user details
|
||||
const userDetails = await fetchUserDetails()
|
||||
// send details back to client
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
if (queryParams && queryParams.get("callback_port")) {
|
||||
const callbackPort = queryParams.get("callback_port")
|
||||
|
||||
// send post request to cli with details
|
||||
const cliUrl = `http://localhost:${callbackPort}`
|
||||
const instance = axios.create()
|
||||
await instance.post(cliUrl, { email: userDetails.email, privateKey: localStorage.getItem("PRIVATE_KEY"), JTWToken: getAuthToken() })
|
||||
}
|
||||
router.push(`/dashboard/${userWorkspace}`);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
};
|
||||
if (isLoggedIn()) {
|
||||
redirectToDashboard();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderView = (loginStep: number) => {
|
||||
if (providerAuthToken && step === 1) {
|
||||
return (
|
||||
<PasswordInputStep
|
||||
email={providerEmail}
|
||||
password={password}
|
||||
providerAuthToken={providerAuthToken}
|
||||
setPassword={setPassword}
|
||||
setProviderAuthToken={setProviderAuthToken}
|
||||
setStep={setStep}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (loginStep === 1) {
|
||||
return <InitialLoginStep
|
||||
setStep={setStep}
|
||||
email={email}
|
||||
setEmail={setEmail}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (step === 2) {
|
||||
return (
|
||||
<MFAStep
|
||||
email={email || providerEmail}
|
||||
password={password}
|
||||
providerAuthToken={providerAuthToken}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <div />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("login.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("login.og-title") ?? ""} />
|
||||
<meta name="og:description" content={t("login.og-description") ?? ""} />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||
</div>
|
||||
</Link>
|
||||
{renderView(step)}
|
||||
{/* <div className="absolute right-4 top-0 mt-4 flex items-center justify-center">
|
||||
<div className="mx-auto w-48">
|
||||
<ListBox
|
||||
isSelected={lang}
|
||||
onChange={setLanguage}
|
||||
data={['en', 'ko', 'fr', 'pt-BR']}
|
||||
isFull
|
||||
text={`${t('common.language')}: `}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Login } from "@app/views/Login";
|
||||
|
||||
export default function LoginPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("login.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("login.og-title") ?? ""} />
|
||||
<meta name="og:description" content={t("login.og-description") ?? ""} />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<Login />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
import { LoginSSO } from "@app/views/Login";
|
||||
|
||||
export default function LoginSSOPage() {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const token = router.query.token as string;
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("login.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("login.og-title") ?? ""} />
|
||||
<meta name="og:description" content={t("login.og-description") ?? ""} />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<LoginSSO providerAuthToken={token} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Button from "@app/components/basic/buttons/Button";
|
||||
|
||||
import getOrganizationUserProjects from "./api/organization/GetOrgUserProjects";
|
||||
|
||||
export default function NoProjects() {
|
||||
const router = useRouter();
|
||||
|
||||
const redirectUser = async () => {
|
||||
const workspaces = await getOrganizationUserProjects({
|
||||
orgId: String(localStorage.getItem("orgData.id"))
|
||||
});
|
||||
if (workspaces.length > 0) {
|
||||
router.push(`/dashboard/${workspaces[0]._id}`);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// on initial load - run auth check
|
||||
(async () => {
|
||||
await redirectUser();
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mr-auto flex h-full w-11/12 flex-col items-center justify-center text-center text-lg text-gray-300">
|
||||
<Head>
|
||||
<title>No Projects | Infisical</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
<div className="mb-6 mt-16 mr-16">
|
||||
<Image src="/images/dragon-cant-find.svg" height={270} width={436} alt="google logo" />
|
||||
</div>
|
||||
<div className="mb-8 rounded-md bg-mineshaft-900 border border-mineshaft-700 px-4 py-6 text-bunker-300 shadow-xl">
|
||||
<div className="max-w-md">
|
||||
You are not part of any projects in this organization yet. When you are, they will appear
|
||||
here.
|
||||
</div>
|
||||
<div className="mt-4 max-w-md">
|
||||
Create a new project, or ask other organization members to give you necessary permissions.
|
||||
</div>
|
||||
<div className="mx-2 mt-6">
|
||||
<Button text="Check again" onButtonPressed={redirectUser} size="md" color="mineshaft" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NoProjects.requireAuth = true;
|
||||
+1
@@ -11,6 +11,7 @@ export default function SettingsBilling() {
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("billing.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
<BillingSettingsPage />
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MembersPage } from "@app/views/Org/MembersPage";
|
||||
|
||||
export default function SettingsOrg() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MembersPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsOrg.requireAuth = true;
|
||||
@@ -0,0 +1,514 @@
|
||||
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faArrowRight, faCheckCircle, faHandPeace, faMagnifyingGlass, faNetworkWired, faPlug, faPlus, faUserPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
||||
import { Button, Checkbox, FormControl, Input, Modal, ModalContent, UpgradePlanModal } from "@app/components/v2";
|
||||
import { TabsObject } from "@app/components/v2/Tabs";
|
||||
import { useSubscription, useUser, useWorkspace } from "@app/context";
|
||||
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useUploadWsKey } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto";
|
||||
import registerUserAction from "../../../api/userActions/registerUserAction";
|
||||
|
||||
const features = [{
|
||||
"_id": 0,
|
||||
"name": "Kubernetes Operator",
|
||||
"description": "Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes."
|
||||
}]
|
||||
|
||||
type ItemProps = {
|
||||
text: string;
|
||||
subText: string;
|
||||
complete: boolean;
|
||||
icon: IconProp;
|
||||
time: string;
|
||||
userAction?: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
const learningItem = ({
|
||||
text,
|
||||
subText,
|
||||
complete,
|
||||
icon,
|
||||
time,
|
||||
userAction,
|
||||
link
|
||||
}: ItemProps): JSX.Element => {
|
||||
if (link) {
|
||||
return (
|
||||
<a
|
||||
target={`${link.includes("https") ? "_blank" : "_self"}`}
|
||||
rel="noopener noreferrer"
|
||||
className={`w-full ${complete && "opacity-30 duration-200 hover:opacity-100"}`}
|
||||
href={link}
|
||||
>
|
||||
<div className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""} mb-3 rounded-md`}>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction && userAction !== "first_time_secrets_pushed") {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${complete? "bg-gradient-to-r from-[#0e1f01] to-mineshaft-700 border-mineshaft-900 cursor-default" : "bg-mineshaft-800 hover:bg-mineshaft-700 border-mineshaft-600 shadow-xl cursor-pointer"} duration-200 text-mineshaft-100`}
|
||||
>
|
||||
<div className="mr-4 flex flex-row items-center">
|
||||
<FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" />
|
||||
{complete && (
|
||||
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-primary" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="mt-0.5 text-xl font-semibold">{text}</div>
|
||||
<div className="text-sm font-normal">{subText}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`w-32 pr-8 text-right text-sm font-semibold ${complete && "text-primary"}`}
|
||||
>
|
||||
{complete ? "Complete!" : `About ${time}`}
|
||||
</div>
|
||||
{/* {complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />} */}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction) {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="relative my-1.5 flex h-[5.5rem] w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-dashed border-bunker-400 bg-bunker-700 py-2 pl-2 pr-6 shadow-xl duration-200 hover:bg-bunker-500"
|
||||
>
|
||||
<div className="mr-4 flex flex-row items-center">
|
||||
<FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" />
|
||||
{complete && (
|
||||
<div className="absolute left-11 top-10 h-7 w-7 rounded-full bg-bunker-700">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="absolute left-12 top-16 h-5 w-5 text-4xl text-primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="mt-0.5 text-xl font-semibold">{text}</div>
|
||||
<div className="mt-0.5 text-sm font-normal">{subText}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`w-28 pr-4 text-right text-sm font-semibold ${complete && "text-primary"}`}>
|
||||
{complete ? "Complete!" : `About ${time}`}
|
||||
</div>
|
||||
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const learningItemSquare = ({
|
||||
text,
|
||||
subText,
|
||||
complete,
|
||||
icon,
|
||||
time,
|
||||
userAction,
|
||||
link
|
||||
}: ItemProps): JSX.Element => {
|
||||
return (
|
||||
<a
|
||||
target={`${link?.includes("https") ? "_blank" : "_self"}`}
|
||||
rel="noopener noreferrer"
|
||||
className={`w-full ${complete && "opacity-30 duration-200 hover:opacity-100"}`}
|
||||
href={link}
|
||||
>
|
||||
<div className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""} rounded-md w-full`}>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction && userAction !== "first_time_secrets_pushed") {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${complete? "bg-gradient-to-r from-[#0e1f01] to-mineshaft-700 border-mineshaft-900 cursor-default" : "bg-mineshaft-800 hover:bg-mineshaft-700 border-mineshaft-600 shadow-xl cursor-pointer"} duration-200 text-mineshaft-100`}
|
||||
>
|
||||
<div className="flex flex-col items-center w-full px-6 py-4">
|
||||
<div className="flex flex-row items-start justify-between w-full">
|
||||
<FontAwesomeIcon icon={icon} className="w-16 text-5xl text-mineshaft-200 group-hover:text-mineshaft-100 duration-100 pt-2" />
|
||||
{complete && (
|
||||
<div className="absolute left-14 top-12 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-primary" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`text-right text-sm font-normal text-mineshaft-300 ${complete ? "text-primary font-semibold" : ""}`}
|
||||
>
|
||||
{complete ? "Complete!" : `About ${time}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-start w-full pt-4">
|
||||
<div className="mt-0.5 text-lg font-medium">{text}</div>
|
||||
<div className="text-sm font-normal text-mineshaft-300">{subText}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
const formSchema = yup.object({
|
||||
name: yup.string().required().label("Project Name").trim(),
|
||||
addMembers: yup.bool().required().label("Add Members")
|
||||
});
|
||||
|
||||
type TAddProjectFormData = yup.InferType<typeof formSchema>;
|
||||
|
||||
// #TODO: Update all the workspaceIds
|
||||
|
||||
export default function Organization() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { workspaces } = useWorkspace();
|
||||
const orgWorkspaces = workspaces?.filter(workspace => workspace.organization === localStorage.getItem("orgData.id")) || []
|
||||
const currentOrg = String(router.query.id);
|
||||
const { createNotification } = useNotificationContext();
|
||||
const addWsUser = useAddUserToWs();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"addNewWs",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
const {
|
||||
control,
|
||||
formState: { isSubmitting },
|
||||
reset,
|
||||
handleSubmit
|
||||
} = useForm<TAddProjectFormData>({
|
||||
resolver: yupResolver(formSchema)
|
||||
});
|
||||
|
||||
const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false);
|
||||
const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false);
|
||||
const [hasUserPushedSecrets, setHasUserPushedSecrets] = useState(false);
|
||||
const [usersInOrg, setUsersInOrg] = useState(false);
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const createWs = useCreateWorkspace();
|
||||
const { user } = useUser();
|
||||
const uploadWsKey = useUploadWsKey();
|
||||
|
||||
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
|
||||
// type check
|
||||
if (!currentOrg) return;
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
workspace: { _id: newWorkspaceId }
|
||||
}
|
||||
} = await createWs.mutateAsync({
|
||||
organizationId: currentOrg,
|
||||
workspaceName: name
|
||||
});
|
||||
|
||||
const randomBytes = crypto.randomBytes(16).toString("hex");
|
||||
const PRIVATE_KEY = String(localStorage.getItem("PRIVATE_KEY"));
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: user.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
|
||||
await uploadWsKey.mutateAsync({
|
||||
encryptedKey: ciphertext,
|
||||
nonce,
|
||||
userId: user?._id,
|
||||
workspaceId: newWorkspaceId
|
||||
});
|
||||
|
||||
if (addMembers) {
|
||||
// not using hooks because need at this point only
|
||||
const orgUsers = await fetchOrgUsers(currentOrg);
|
||||
orgUsers.forEach(({ status, user: orgUser }) => {
|
||||
// skip if status of org user is not accepted
|
||||
// this orgUser is the person who created the ws
|
||||
if (status !== "accepted" || user.email === orgUser.email) return;
|
||||
addWsUser.mutate({ email: orgUser.email, workspaceId: newWorkspaceId });
|
||||
});
|
||||
}
|
||||
createNotification({ text: "Workspace created", type: "success" });
|
||||
handlePopUpClose("addNewWs");
|
||||
router.push(`/project/${newWorkspaceId}/secrets`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({ text: "Failed to create workspace", type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const isAddingProjectsAllowed = subscription?.workspaceLimit ? (subscription.workspacesUsed < subscription.workspaceLimit) : true;
|
||||
|
||||
useEffect(() => {
|
||||
onboardingCheck({
|
||||
setHasUserClickedIntro,
|
||||
setHasUserClickedSlack,
|
||||
setHasUserPushedSecrets,
|
||||
setUsersInOrg
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex max-w-7xl mx-auto flex-col justify-start bg-bunker-800 md:h-screen">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4">
|
||||
<p className="mr-4 font-semibold text-white">Projects</p>
|
||||
<div className="w-full flex flex-row mt-6">
|
||||
<Input
|
||||
className="h-[2.3rem] text-sm bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||
placeholder="Search by project name..."
|
||||
value={searchFilter}
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
/>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (isAddingProjectsAllowed) {
|
||||
handlePopUpOpen("addNewWs")
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
className="ml-2"
|
||||
>
|
||||
Add New Project
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 w-full grid gap-4 grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{orgWorkspaces.filter(ws => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())).map(workspace => <div key={workspace._id} className="h-40 min-w-72 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between">
|
||||
<div className="text-lg text-mineshaft-100 mt-0">{workspace.name}</div>
|
||||
<div className="text-sm text-mineshaft-300 mt-0 pb-6">{(workspace.environments?.length || 0)} environments</div>
|
||||
<button type="button" onClick={() => {
|
||||
router.push(`/project/${workspace._id}/secrets`);
|
||||
localStorage.setItem("projectData.id", workspace._id);
|
||||
}}>
|
||||
<div className="group cursor-default ml-auto hover:bg-primary-800/20 text-sm text-mineshaft-300 hover:text-mineshaft-200 bg-mineshaft-900 py-2 px-4 rounded-full w-max border border-mineshaft-600 hover:border-primary-500/80">Explore <FontAwesomeIcon icon={faArrowRight} className="pl-1.5 pr-0.5 group-hover:pl-2 group-hover:pr-0 duration-200" /></div>
|
||||
</button>
|
||||
</div>)}
|
||||
</div>
|
||||
{orgWorkspaces.length === 0 && (
|
||||
<div className="w-full rounded-md bg-mineshaft-800 border border-mineshaft-700 px-4 py-6 text-mineshaft-300 text-base">
|
||||
<FontAwesomeIcon icon={faFolderOpen} className="w-full text-center text-5xl mb-4 mt-2 text-mineshaft-400" />
|
||||
<div className="text-center font-light">
|
||||
You are not part of any projects in this organization yet. When you are, they will appear
|
||||
here.
|
||||
</div>
|
||||
<div className="mt-0.5 text-center font-light">
|
||||
Create a new project, or ask other organization members to give you necessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{((new Date()).getTime() - (new Date(user?.createdAt)).getTime()) < 30 * 24 * 60 * 60 * 1000 && <div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4">
|
||||
<p className="mr-4 font-semibold text-white mb-4">Onboarding Guide</p>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3 w-full mb-3">
|
||||
{learningItemSquare({
|
||||
text: "Watch Infisical demo",
|
||||
subText: "Set up Infisical in 3 min.",
|
||||
complete: hasUserClickedIntro,
|
||||
icon: faHandPeace,
|
||||
time: "3 min",
|
||||
userAction: "intro_cta_clicked",
|
||||
link: "https://www.youtube.com/watch?v=PK23097-25I"
|
||||
})}
|
||||
{orgWorkspaces.length !== 0 && learningItemSquare({
|
||||
text: "Add your secrets",
|
||||
subText: "Drop a .env file or type your secrets.",
|
||||
complete: hasUserPushedSecrets,
|
||||
icon: faPlus,
|
||||
time: "1 min",
|
||||
userAction: "first_time_secrets_pushed",
|
||||
link: `/project/${orgWorkspaces[0]?._id}/secrets`
|
||||
})}
|
||||
{learningItemSquare({
|
||||
text: "Invite your teammates",
|
||||
subText: "Infisical is better used as a team.",
|
||||
complete: usersInOrg,
|
||||
icon: faUserPlus,
|
||||
time: "2 min",
|
||||
link: `/org/${router.query.id}/members?action=invite`
|
||||
})}
|
||||
<div className="block xl:hidden 2xl:block">{learningItemSquare({
|
||||
text: "Join Infisical Slack",
|
||||
subText: "Have any questions? Ask us!",
|
||||
complete: hasUserClickedSlack,
|
||||
icon: faSlack,
|
||||
time: "1 min",
|
||||
userAction: "slack_cta_clicked",
|
||||
link: "https://infisical.com/slack"
|
||||
})}</div>
|
||||
</div>
|
||||
{orgWorkspaces.length !== 0 && <div className="group text-mineshaft-100 relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 shadow-xl duration-200">
|
||||
<div className="mb-4 flex w-full flex-row items-center pr-4">
|
||||
<div className="mr-4 flex w-full flex-row items-center">
|
||||
<FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" />
|
||||
{false && (
|
||||
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-green" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start pl-0.5">
|
||||
<div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div>
|
||||
<div className="text-sm font-normal">
|
||||
Replace .env files with a more secure and efficient alternative.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green"}`}>
|
||||
About 2 min
|
||||
</div>
|
||||
</div>
|
||||
<TabsObject />
|
||||
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
|
||||
</div>}
|
||||
{orgWorkspaces.length !== 0 && learningItem({
|
||||
text: "Integrate Infisical with your infrastructure",
|
||||
subText: "Connect Infisical to various 3rd party services and platforms.",
|
||||
complete: false,
|
||||
icon: faPlug,
|
||||
time: "15 min",
|
||||
link: "https://infisical.com/docs/integrations/overview"
|
||||
})}
|
||||
</div>}
|
||||
<div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4 pb-6">
|
||||
<p className="mr-4 font-semibold text-white">Explore More</p>
|
||||
<div className="mt-4 w-full grid grid-flow-dense gap-4" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(256px, 4fr))" }}>
|
||||
{features.map(feature => <div key={feature._id} className="h-44 w-96 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between">
|
||||
<div className="text-lg text-mineshaft-100 mt-0">{feature.name}</div>
|
||||
<div className="text-[15px] font-light text-mineshaft-300 mb-4 mt-2">{feature.description}</div>
|
||||
<div className="w-full flex items-center">
|
||||
<div className="text-mineshaft-300 text-[15px] font-light">Setup time: 20 min</div>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group cursor-default ml-auto hover:bg-primary-800/20 text-sm text-mineshaft-300 hover:text-mineshaft-200 bg-mineshaft-900 py-2 px-4 rounded-full w-max border border-mineshaft-600 hover:border-primary-500/80"
|
||||
href="https://infisical.com/docs/documentation/getting-started/kubernetes"
|
||||
>
|
||||
Learn more <FontAwesomeIcon icon={faArrowRight} className="pl-1.5 pr-0.5 group-hover:pl-2 group-hover:pr-0 duration-200"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={popUp.addNewWs.isOpen}
|
||||
onOpenChange={(isModalOpen) => {
|
||||
handlePopUpToggle("addNewWs", isModalOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Create a new project"
|
||||
subTitle="This project will contain your secrets and configurations."
|
||||
>
|
||||
<form onSubmit={handleSubmit(onCreateProject)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="Type your project name" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-4 pl-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="addMembers"
|
||||
defaultValue
|
||||
render={({ field: { onBlur, value, onChange } }) => (
|
||||
<Checkbox
|
||||
id="add-project-layout"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
Add all members of my organization to this project
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-7 flex items-center">
|
||||
<Button
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
key="layout-create-project-submit"
|
||||
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>
|
||||
</Modal>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You have exceeded the number of projects allowed on the free plan."
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Organization.requireAuth = true;
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
import { Button } from "@app/components/v2";
|
||||
import { SecretScanningLogsTable } from "@app/views/SecretScanning/components";
|
||||
|
||||
import createNewIntegrationSession from "../../../api/secret-scanning/createSecretScanningSession";
|
||||
import getInstallationStatus from "../../../api/secret-scanning/getInstallationStatus";
|
||||
import linkGitAppInstallationWithOrganization from "../../../api/secret-scanning/linkGitAppInstallationWithOrganization";
|
||||
|
||||
export default function SecretScanning() {
|
||||
const router = useRouter()
|
||||
const queryParams = router.query
|
||||
const [integrationEnabled, setIntegrationStatus] = useState(false)
|
||||
|
||||
useEffect(()=>{
|
||||
const linkInstallation = async () => {
|
||||
if (typeof queryParams.state === "string" && typeof queryParams.installation_id === "string"){
|
||||
try {
|
||||
const isLinked = await linkGitAppInstallationWithOrganization(queryParams.installation_id as string, queryParams.state as string)
|
||||
if (isLinked){
|
||||
router.reload()
|
||||
}
|
||||
|
||||
console.log("installation verification complete")
|
||||
}catch (e){
|
||||
console.log("app installation is stale, start new session", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fetchInstallationStatus = async () => {
|
||||
const status = await getInstallationStatus(String(localStorage.getItem("orgData.id")))
|
||||
setIntegrationStatus(status)
|
||||
}
|
||||
|
||||
fetchInstallationStatus()
|
||||
linkInstallation()
|
||||
},[queryParams.state, queryParams.installation_id])
|
||||
|
||||
const generateNewIntegrationSession = async () => {
|
||||
const session = await createNewIntegrationSession(String(localStorage.getItem("orgData.id")))
|
||||
router.push(`https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Secret scanning</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||
<div className="max-w-7xl px-6 w-full">
|
||||
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
|
||||
<div className="mb-6 text-lg text-mineshaft-300">Automatically monitor your GitHub activity and prevent secret leaks</div>
|
||||
<div className="relative flex justify-between bg-mineshaft-800 border border-mineshaft-600 rounded-md p-6 mb-6">
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex flex-row mb-1">Secret Scanning Status: {integrationEnabled ? <p className="text-green ml-1.5 font-semibold">Enabled</p> : <p className="text-red ml-1.5 font-semibold">Not enabled</p>}</div>
|
||||
<div>{integrationEnabled ? <p className="text-mineshaft-300">Your GitHub organization is connected to Infisical, and is being continuously monitored for secret leaks.</p> : <p className="text-mineshaft-300">Connect your GitHub organization to Infisical.</p>}</div>
|
||||
</div>
|
||||
{integrationEnabled ? (
|
||||
<div>
|
||||
<div className="absolute right-[2.5rem] top-[2.5rem] animate-ping rounded-full h-6 w-6 bg-green flex items-center justify-center"/>
|
||||
<div className="absolute right-[2.63rem] top-[2.63rem] animate-ping rounded-full h-5 w-5 bg-green flex items-center justify-center"/>
|
||||
<div className="absolute right-[2.82rem] top-[2.82rem] animate-ping rounded-full h-3.5 w-3.5 bg-green flex items-center justify-center"/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center h-[3.25rem]">
|
||||
<Button
|
||||
variant="solid"
|
||||
colorSchema="primary"
|
||||
onClick={generateNewIntegrationSession}
|
||||
className="py-2 h-min"
|
||||
>
|
||||
Integrate with GitHub
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SecretScanningLogsTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SecretScanning.requireAuth = true;
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useState } from "react";
|
||||
import { FormEvent, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -24,6 +24,7 @@ const client = new jsrp.client();
|
||||
export default function PasswordReset() {
|
||||
const [verificationToken, setVerificationToken] = useState("");
|
||||
const [step, setStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [backupKey, setBackupKey] = useState("");
|
||||
const [privateKey, setPrivateKey] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
@@ -38,7 +39,8 @@ export default function PasswordReset() {
|
||||
const email = (parsedUrl.to as string)?.replace(" ", "+").trim();
|
||||
|
||||
// Unencrypt the private key with a backup key
|
||||
const getEncryptedKeyHandler = async () => {
|
||||
const getEncryptedKeyHandler = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const result = await getBackupEncryptedPrivateKey({ verificationToken });
|
||||
|
||||
@@ -57,7 +59,8 @@ export default function PasswordReset() {
|
||||
};
|
||||
|
||||
// If everything is correct, reset the password
|
||||
const resetPasswordHandler = async () => {
|
||||
const resetPasswordHandler = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const errorCheck = passwordCheck({
|
||||
password: newPassword,
|
||||
setPasswordErrorLength,
|
||||
@@ -125,6 +128,7 @@ export default function PasswordReset() {
|
||||
if (response?.status === 200) {
|
||||
router.push("/login");
|
||||
}
|
||||
setLoading(false)
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -162,7 +166,7 @@ export default function PasswordReset() {
|
||||
|
||||
// Input backup key
|
||||
const stepInputBackupKey = (
|
||||
<div className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
|
||||
<form onSubmit={getEncryptedKeyHandler} className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
|
||||
<p className="mx-auto mb-4 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||
Enter your backup key
|
||||
</p>
|
||||
@@ -186,18 +190,19 @@ export default function PasswordReset() {
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button
|
||||
type="submit"
|
||||
text="Submit Backup Key"
|
||||
onButtonPressed={() => getEncryptedKeyHandler()}
|
||||
onButtonPressed={() => {}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
// Enter new password
|
||||
const stepEnterNewPassword = (
|
||||
<div className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
|
||||
<form onSubmit={resetPasswordHandler} className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
|
||||
<p className="mx-auto flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||
Enter new password
|
||||
</p>
|
||||
@@ -269,13 +274,15 @@ export default function PasswordReset() {
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button
|
||||
type="submit"
|
||||
text="Submit New Password"
|
||||
onButtonPressed={() => resetPasswordHandler()}
|
||||
onButtonPressed={() => setLoading(true)}
|
||||
size="lg"
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IPAllowlistPage } from "@app/views/Project/IPAllowListPage";
|
||||
|
||||
const ProjectAllowlist = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IPAllowlistPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectAllowlist;
|
||||
|
||||
ProjectAllowlist.requireAuth = true;
|
||||
+5
-9
@@ -5,14 +5,13 @@ import { useRouter } from "next/router";
|
||||
|
||||
import Button from "@app/components/basic/buttons/Button";
|
||||
import EventFilter from "@app/components/basic/EventFilter";
|
||||
import NavHeader from "@app/components/navigation/NavHeader";
|
||||
import { UpgradePlanModal } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import ActivitySideBar from "@app/ee/components/ActivitySideBar";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import getProjectLogs from "../../ee/api/secrets/GetProjectLogs";
|
||||
import ActivityTable from "../../ee/components/ActivityTable";
|
||||
import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs";
|
||||
import ActivityTable from "../../../../ee/components/ActivityTable";
|
||||
|
||||
interface LogData {
|
||||
_id: string;
|
||||
@@ -152,20 +151,17 @@ export default function Activity() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-6 lg:mx-0 w-full h-full">
|
||||
<div className="mx-auto w-full h-full max-w-7xl">
|
||||
<Head>
|
||||
<title>Audit Logs</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
<div className="ml-6">
|
||||
<NavHeader pageName="Audit Logs" isProjectRelated />
|
||||
</div>
|
||||
{currentSidebarAction && (
|
||||
<ActivitySideBar toggleSidebar={toggleSidebar} currentAction={currentSidebarAction} />
|
||||
)}
|
||||
<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">
|
||||
<div className="flex flex-col justify-between items-start mx-4 mb-4 text-xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl mt-6">
|
||||
<p className="font-semibold mr-4 text-bunker-100">{t("activity.title")}</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">{t("activity.subtitle")}</p>
|
||||
+12
-14
@@ -9,20 +9,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Button from "@app/components/basic/buttons/Button";
|
||||
import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMemberDialog";
|
||||
import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable";
|
||||
import NavHeader from "@app/components/navigation/NavHeader";
|
||||
import guidGenerator from "@app/components/utilities/randomId";
|
||||
import { Input } from "@app/components/v2";
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
} from "../../components/utilities/cryptography/crypto";
|
||||
import getOrganizationUsers from "../api/organization/GetOrgUsers";
|
||||
import getUser from "../api/user/getUser";
|
||||
} from "../../../../components/utilities/cryptography/crypto";
|
||||
import getOrganizationUsers from "../../../api/organization/GetOrgUsers";
|
||||
import getUser from "../../../api/user/getUser";
|
||||
// import DeleteUserDialog from '@app/components/basic/dialog/DeleteUserDialog';
|
||||
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
|
||||
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
|
||||
import uploadKeys from "../api/workspace/uploadKeys";
|
||||
import addUserToWorkspace from "../../../api/workspace/addUserToWorkspace";
|
||||
import getWorkspaceUsers from "../../../api/workspace/getWorkspaceUsers";
|
||||
import uploadKeys from "../../../api/workspace/uploadKeys";
|
||||
|
||||
interface UserProps {
|
||||
firstName: string;
|
||||
@@ -149,14 +148,11 @@ export default function Users() {
|
||||
};
|
||||
|
||||
return userList ? (
|
||||
<div className="flex max-w-[calc(100vw-240px)] flex-col justify-start bg-bunker-800 md:h-screen">
|
||||
<div className="flex max-w-7xl mx-auto flex-col justify-start bg-bunker-800 md:h-screen">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="ml-6">
|
||||
<NavHeader pageName={t("settings.members.title")} isProjectRelated />
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4">
|
||||
<p className="mr-4 font-semibold text-white">{t("settings.members.title")}</p>
|
||||
</div>
|
||||
@@ -174,7 +170,7 @@ export default function Users() {
|
||||
setEmail={setEmail}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="absolute right-4 top-36 flex w-full flex-row items-start px-6 pb-1">
|
||||
<div className="flex w-full flex-row items-start px-6 pb-1">
|
||||
<div className="flex w-full max-w-sm flex flex-row ml-auto">
|
||||
<Input
|
||||
className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||
@@ -187,14 +183,16 @@ export default function Users() {
|
||||
<div className="ml-2 flex min-w-max flex-row items-start justify-start">
|
||||
<Button
|
||||
text={String(t("section.members.add-member"))}
|
||||
onButtonPressed={openAddModal}
|
||||
onButtonPressed={() => {
|
||||
openAddModal();
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="no-scrollbar::-webkit-scrollbar block overflow-x-scroll px-6 pb-6 no-scrollbar">
|
||||
<div className="block overflow-x-auto px-6 pb-6">
|
||||
<ProjectUsersTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import { isLoggedIn } from "@app/reactQuery";
|
||||
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const [password, setPassword] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
// TODO(akhilmhdh): workspace will be controlled by a workspace context
|
||||
const redirectToDashboard = async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
||||
router.push(`/dashboard/${userWorkspace}`);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
};
|
||||
if (isLoggedIn()) {
|
||||
redirectToDashboard();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("login.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("login.og-title") ?? ""} />
|
||||
<meta name="og:description" content={t("login.og-description") ?? ""} />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="mx-auto w-full max-w-md md:px-6">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
|
||||
What’s your email?
|
||||
</p>
|
||||
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="email"
|
||||
placeholder="Enter your email..."
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[20rem] md:min-w-[22rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={() => {}}
|
||||
isFullWidth
|
||||
className="h-14"
|
||||
>
|
||||
{t("login.login")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center mt-4">
|
||||
<button
|
||||
onClick={() => {router.push("/login")}}
|
||||
type="button"
|
||||
className="text-bunker-300 text-sm hover:underline mt-2 hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer"
|
||||
>
|
||||
{t("login.other-option")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
export default function SecretScanning() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(()=>{
|
||||
router.push(`${router.asPath.split("secret-scanning")[0]}/org/${localStorage.getItem("orgData.id")}/secret-scanning${router.asPath.split("secret-scanning")[1]}`)
|
||||
}, [])
|
||||
|
||||
|
||||
return <div/>;
|
||||
}
|
||||
|
||||
SecretScanning.requireAuth = true;
|
||||
@@ -13,10 +13,8 @@ import TeamInviteStep from "@app/components/signup/TeamInviteStep";
|
||||
import UserInfoStep from "@app/components/signup/UserInfoStep";
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { useProviderAuth } from "@app/hooks/useProviderAuth";
|
||||
|
||||
import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode";
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
import checkEmailVerificationCode from "@app/pages/api/auth/CheckEmailVerificationCode";
|
||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
||||
|
||||
/**
|
||||
* @returns the signup page
|
||||
@@ -35,21 +33,12 @@ export default function SignUp() {
|
||||
const [isSignupWithEmail, setIsSignupWithEmail] = useState(false);
|
||||
const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { email: providerEmail, providerAuthToken, isProviderUserCompleted } = useProviderAuth();
|
||||
|
||||
if (providerAuthToken && isProviderUserCompleted) {
|
||||
router.push(`/login?providerAuthToken=${encodeURIComponent(providerAuthToken)}`);
|
||||
}
|
||||
|
||||
if (providerAuthToken && step < 3) {
|
||||
setStep(3);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const tryAuth = async () => {
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
router.push(`/dashboard/${userWorkspaces[0]._id}`);
|
||||
const userOrgs = await getOrganizations();
|
||||
router.push(`/org/${userOrgs[0]._id}/overview`);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
@@ -90,8 +79,8 @@ export default function SignUp() {
|
||||
}
|
||||
|
||||
if (!serverDetails?.emailConfigured && step === 5) {
|
||||
getWorkspaces().then((userWorkspaces) => {
|
||||
router.push(`/dashboard/${userWorkspaces[0]._id}`);
|
||||
getOrganizations().then((userOrgs) => {
|
||||
router.push(`/org/${userOrgs[0]._id}/overview`);
|
||||
});
|
||||
}
|
||||
}, [step]);
|
||||
@@ -121,7 +110,7 @@ export default function SignUp() {
|
||||
return (
|
||||
<UserInfoStep
|
||||
incrementStep={incrementStep}
|
||||
email={email || providerEmail}
|
||||
email={email}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
name={name}
|
||||
@@ -130,7 +119,7 @@ export default function SignUp() {
|
||||
setOrganizationName={setOrganizationName}
|
||||
attributionSource={attributionSource}
|
||||
setAttributionSource={setAttributionSource}
|
||||
providerAuthToken={providerAuthToken}
|
||||
providerAuthToken={undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -139,7 +128,7 @@ export default function SignUp() {
|
||||
return (
|
||||
<DownloadBackupPDF
|
||||
incrementStep={incrementStep}
|
||||
email={email || providerEmail}
|
||||
email={email}
|
||||
password={password}
|
||||
name={name}
|
||||
/>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
import { SignupSSO } from "@app/views/Signup";
|
||||
|
||||
export default function SignupSSOPage() {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const token = router.query.token as string;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("signup.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("signup.og-title") as string} />
|
||||
<meta name="og:description" content={t("signup.og-description") as string} />
|
||||
</Head>
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
|
||||
</div>
|
||||
<SignupSSO providerAuthToken={token} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -46,6 +46,7 @@ type Errors = {
|
||||
|
||||
export default function SignupInvite() {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
@@ -212,7 +213,7 @@ export default function SignupInvite() {
|
||||
} else {
|
||||
// user will be redirected to dashboard
|
||||
// if not logged in gets kicked out to login
|
||||
router.push("/dashboard");
|
||||
router.push(`/org/${organizationId}/overview`);
|
||||
}
|
||||
} else {
|
||||
console.log("ERROR", response);
|
||||
@@ -338,22 +339,10 @@ export default function SignupInvite() {
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued
|
||||
});
|
||||
router.push("/noprojects/");
|
||||
router.push(`/org/${organizationId}/overview`);
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
{/* <div
|
||||
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
|
||||
onClick={() => {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
|
||||
} else {
|
||||
router.push("/noprojects")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Later
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { FormEvent, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
@@ -12,6 +12,7 @@ import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import SendEmailOnPasswordReset from "./api/auth/SendEmailOnPasswordReset";
|
||||
|
||||
export default function VerifyEmail() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [step, setStep] = useState(1);
|
||||
const { data: serverDetails } = useFetchServerStatus();
|
||||
@@ -27,6 +28,18 @@ export default function VerifyEmail() {
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
if (serverDetails?.emailConfigured) {
|
||||
sendVerificationEmail();
|
||||
} else {
|
||||
handlePopUpOpen("setUpEmail");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-start bg-bunker-800 px-6">
|
||||
<Head>
|
||||
@@ -45,7 +58,7 @@ export default function VerifyEmail() {
|
||||
</div>
|
||||
</Link>
|
||||
{step === 1 && (
|
||||
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker py-4 px-6 pt-8 drop-shadow-xl">
|
||||
<form onSubmit={onSubmit} className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker px-6 py-4 pt-8 drop-shadow-xl">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||
Forgot your password?
|
||||
</p>
|
||||
@@ -67,20 +80,10 @@ export default function VerifyEmail() {
|
||||
</div>
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button
|
||||
text="Continue"
|
||||
onButtonPressed={() => {
|
||||
if (serverDetails?.emailConfigured) {
|
||||
sendVerificationEmail();
|
||||
} else {
|
||||
handlePopUpOpen("setUpEmail");
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
<Button type="submit" text="Continue" size="lg" onButtonPressed={() => {}} loading={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker py-4 px-6 pt-8 drop-shadow-xl">
|
||||
|
||||
Reference in New Issue
Block a user