Merge remote-tracking branch 'upstream/main' into feat/39

This commit is contained in:
이강준
2022-12-26 22:11:09 +09:00
432 changed files with 20869 additions and 5690 deletions

View File

@@ -1,9 +1,8 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { faCircle, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useState } from 'react';
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId";
import guidGenerator from '../utilities/randomId';
interface InputFieldProps {
static?: boolean;
@@ -21,9 +20,11 @@ interface InputFieldProps {
onChangeHandler: (value: string) => void;
}
const InputField = (props: InputFieldProps) => {
const InputField = (
props: InputFieldProps &
Pick<JSX.IntrinsicElements['input'], 'autoComplete' | 'id'>
) => {
const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter();
if (props.static === true) {
return (
@@ -43,6 +44,8 @@ const InputField = (props: InputFieldProps) => {
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none"
name={props.name}
readOnly
autoComplete={props.autoComplete}
id={props.id}
/>
</div>
);
@@ -70,27 +73,30 @@ const InputField = (props: InputFieldProps) => {
</div>
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
props.error ? "border-red" : "border-mineshaft-500"
props.error ? 'border-red' : 'border-mineshaft-500'
} rounded-md`}
>
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={passwordVisible === false ? props.type : "text"}
type={passwordVisible === false ? props.type : 'text'}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className={`${
props.blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
: ""
? 'text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400'
: ''
} ${
props.error ? "focus:ring-red/50" : "focus:ring-primary/50"
props.error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
name={props.name}
spellCheck="false"
autoComplete={props.autoComplete}
id={props.id}
/>
{props.label?.includes("Password") && (
{props.label?.includes('Password') && (
<button
type="button"
onClick={() => {
setPasswordVisible(!passwordVisible);
}}
@@ -107,7 +113,7 @@ const InputField = (props: InputFieldProps) => {
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
<p className="ml-2"></p>
{props.value
.split("")
.split('')
.slice(0, 54)
.map(() => (
<FontAwesomeIcon

View File

@@ -18,12 +18,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
import checkUserAction from "~/pages/api/userActions/checkUserAction";
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import uploadKeys from "~/pages/api/workspace/uploadKeys";
import NavBarDashboard from "../navigation/NavBarDashboard";
import onboardingCheck from "../utilities/checks/OnboardingCheck";
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
import {
decryptAssymmetric,
@@ -40,12 +42,14 @@ interface LayoutProps {
export default function Layout({ children }: LayoutProps) {
const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ "1": "2" }]);
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
const [newWorkspaceName, setNewWorkspaceName] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
useState(0);
const { t } = useTranslation();
@@ -217,25 +221,26 @@ export default function Layout({ children }: LayoutProps) {
}
};
putUserInWorkSpace();
onboardingCheck({ setTotalOnboardingActionsDone });
}, []);
useEffect(() => {
try {
if (
workspaceMapping[Number(workspaceSelected)] &&
`${workspaceMapping[Number(workspaceSelected)]}` !==
workspaceMapping[workspaceSelected as any] &&
`${workspaceMapping[workspaceSelected as any]}` !==
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
) {
router.push(
"/dashboard/" +
workspaceMapping[Number(workspaceSelected)] +
workspaceMapping[workspaceSelected as any] +
"?Development"
);
localStorage.setItem(
"projectData.id",
`${workspaceMapping[Number(workspaceSelected)]}`
`${workspaceMapping[workspaceSelected as any]}`
);
}
} catch (error) {
@@ -246,6 +251,10 @@ export default function Layout({ children }: LayoutProps) {
return (
<>
<div className="fixed w-full hidden md:block flex flex-col h-screen">
<script
src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.2.2/cdn.js"
defer
></script>
<NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
@@ -329,18 +338,44 @@ export default function Layout({ children }: LayoutProps) {
<FontAwesomeIcon icon={faBookOpen} />
</p>
Infisical Guide
<img
src={`/images/progress-${
totalOnboardingActionsDone == 0 ? "0" : ""
}${totalOnboardingActionsDone == 1 ? "14" : ""}${
totalOnboardingActionsDone == 2 ? "28" : ""
}${totalOnboardingActionsDone == 3 ? "43" : ""}${
totalOnboardingActionsDone == 4 ? "57" : ""
}${totalOnboardingActionsDone == 5 ? "71" : ""}.svg`}
height={58}
width={58}
alt="progress bar"
className="absolute right-2 -top-2"
></img>
</div>
) : (
<Link
href={`/home/` + workspaceMapping[workspaceSelected as any]}
>
<div
className={`flex p-2.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5 mt-max border border-dashed border-bunker-400`}
className={`relative flex p-2.5 overflow-visible text-white h-10 text-sm rounded cursor-pointer bg-white/10 hover:bg-primary-50/[0.15] mt-max`}
>
<p className="w-10 flex items-center justify-center text-lg">
<FontAwesomeIcon icon={faBookOpen} />
</p>
Infisical Guide
<img
src={`/images/progress-${
totalOnboardingActionsDone == 0 ? "0" : ""
}${totalOnboardingActionsDone == 1 ? "14" : ""}${
totalOnboardingActionsDone == 2 ? "28" : ""
}${totalOnboardingActionsDone == 3 ? "43" : ""}${
totalOnboardingActionsDone == 4 ? "57" : ""
}${totalOnboardingActionsDone == 5 ? "71" : ""}.svg`}
height={58}
width={58}
alt="progress bar"
className="absolute right-2 -top-2"
></img>
</div>
</Link>
)}

View File

@@ -10,10 +10,10 @@ import { Listbox, Transition } from "@headlessui/react";
interface ListBoxProps {
selected: string;
onChange: () => void;
data: string[];
text: string;
buttonAction: () => void;
onChange: (arg: string) => void;
data: string[] | null;
text?: string;
buttonAction?: () => void;
isFull?: boolean;
}

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { ButtonHTMLAttributes } from "react";
import Image from "next/image";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import {
@@ -9,7 +9,7 @@ import {
const classNames = require("classnames");
type ButtonProps = {
text: string;
text?: string;
onButtonPressed: () => void;
loading?: boolean;
color?: string;
@@ -18,6 +18,7 @@ type ButtonProps = {
active?: boolean;
iconDisabled?: string;
textDisabled?: string;
type?: ButtonHTMLAttributes<any>['type'];
};
/**
@@ -91,6 +92,7 @@ export default function Button(props: ButtonProps): JSX.Element {
const button = (
<button
disabled={!activityStatus}
type={props.type}
onClick={props.onButtonPressed}
className={styleButton}
>

View File

@@ -0,0 +1,92 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
const ActivateBotDialog = ({
isOpen,
closeModal,
selectedIntegrationOption,
handleBotActivate,
handleIntegrationOption
}) => {
const submit = async () => {
try {
// 1. activate bot
await handleBotActivate();
// 2. start integration
await handleIntegrationOption({
integrationOption: selectedIntegrationOption
});
} catch (err) {
console.log(err);
}
closeModal();
}
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Grant Infisical access to your secrets
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.
</p>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text="Grant access"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
}
export default ActivateBotDialog;

View File

@@ -1,6 +1,5 @@
import { Fragment, useState } from "react";
import { useRouter } from "next/router";
import useTranslate from "next-translate/useTranslation";
import { useTranslation } from "next-i18next";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Dialog, Transition } from "@headlessui/react";
@@ -22,6 +21,8 @@ const expiryMapping = {
"1 day": 86400,
"7 days": 604800,
"1 month": 2592000,
"6 months": 15552000,
"12 months": 31104000,
};
const AddServiceTokenDialog = ({
@@ -30,13 +31,12 @@ const AddServiceTokenDialog = ({
workspaceId,
workspaceName,
}) => {
const router = useRouter();
const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState("");
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const { t } = useTranslate();
const { t } = useTranslation();
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey({ workspaceId });
@@ -169,7 +169,13 @@ const AddServiceTokenDialog = ({
<ListBox
selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={["1 day", "7 days", "1 month"]}
data={[
"1 day",
"7 days",
"1 month",
"6 months",
"12 months",
]}
width="full"
text={`${t("common:expired-in")}: `}
/>
@@ -205,7 +211,7 @@ const AddServiceTokenDialog = ({
</div>
</div>
<div className="w-full">
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-44">
<input
type="text"
value={serviceToken}

View File

@@ -0,0 +1,101 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
const IntegrationAccessTokenDialog = ({
isOpen,
closeModal,
selectedIntegrationOption,
handleBotActivate,
handleIntegrationOption
}) => {
const submit = async () => {
try {
// 1. activate bot
await handleBotActivate();
// 2. start integration
await handleIntegrationOption({
integrationOption: selectedIntegrationOption
});
} catch (err) {
console.log(err);
}
closeModal();
}
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Grant Infisical access to your secrets
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.
</p>
</div>
<div className="mt-6 max-w-max">
{/* <Button
onButtonPressed={submit}
color="mineshaft"
text="Grant access"
size="md"
/> */}
<InputField
label="Access token"
onChangeHandler={() => {}}
type="varName"
value={"Hello"}
placeholder=""
isRequired
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
}
export default IntegrationAccessTokenDialog;

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import { reverseEnvMapping } from "../../../public/data/frequentConstants";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
import guidGenerator from '../../utilities/randomId';
import Button from '../buttons/Button';
/**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.

View File

@@ -1,25 +1,25 @@
import React, { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import React, { useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import deleteUserFromOrganization from "~/pages/api/organization/deleteUserFromOrganization";
import changeUserRoleInWorkspace from "~/pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromWorkspace from "~/pages/api/workspace/deleteUserFromWorkspace";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import uploadKeys from "~/pages/api/workspace/uploadKeys";
import deleteUserFromOrganization from '~/pages/api/organization/deleteUserFromOrganization';
import changeUserRoleInWorkspace from '~/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from '~/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
import uploadKeys from '~/pages/api/workspace/uploadKeys';
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
import Listbox from "../Listbox";
import guidGenerator from '../../utilities/randomId';
import Button from '../buttons/Button';
import Listbox from '../Listbox';
const {
decryptAssymmetric,
encryptAssymmetric,
} = require("../../utilities/cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
encryptAssymmetric
} = require('../../utilities/cryptography/crypto');
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
const roles = ["admin", "user"];
const roles = ['admin', 'user'];
/**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
@@ -36,13 +36,13 @@ const UserTable = ({
isOrg,
onClick,
deleteUser,
setUserIdToBeDeleted,
setUserIdToBeDeleted
}) => {
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const router = useRouter();
const [myRole, setMyRole] = useState("member");
const [myRole, setMyRole] = useState('member');
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
@@ -57,7 +57,7 @@ const UserTable = ({
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length),
...roleSelected.slice(index + 1, userData?.length)
]);
};
@@ -76,10 +76,10 @@ const UserTable = ({
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
},
publicKey: userData[index].publicKey
}
],
...userData.slice(index + 1, userData?.length),
...userData.slice(index + 1, userData?.length)
]);
};
@@ -88,22 +88,22 @@ const UserTable = ({
}, [userData, myUser]);
const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey({workspaceId: router.query.id});
let result = await getLatestFileKey({ workspaceId: router.query.id });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: publicKey,
privateKey: PRIVATE_KEY,
privateKey: PRIVATE_KEY
});
uploadKeys(router.query.id, id, ciphertext, nonce);
@@ -158,24 +158,24 @@ const UserTable = ({
</td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
{row.status == "granted" &&
((myRole == "admin" && row.role != "owner") ||
myRole == "owner") &&
{row.status == 'granted' &&
((myRole == 'admin' && row.role != 'owner') ||
myRole == 'owner') &&
myUser !== row.email ? (
<Listbox
selected={row.role}
onChange={(e) => handleRoleUpdate(index, e)}
data={
myRole == "owner"
? ["owner", "admin", "member"]
: ["admin", "member"]
myRole == 'owner'
? ['owner', 'admin', 'member']
: ['admin', 'member']
}
text="Role: "
membershipId={row.membershipId}
/>
) : (
row.status != "invited" &&
row.status != "verified" && (
row.status != 'invited' &&
row.status != 'verified' && (
<Listbox
selected={row.role}
text="Role: "
@@ -183,8 +183,8 @@ const UserTable = ({
/>
)
)}
{(row.status == "invited" ||
row.status == "verified") && (
{(row.status == 'invited' ||
row.status == 'verified') && (
<div className="w-full pl-9">
<Button
onButtonPressed={() =>
@@ -199,7 +199,7 @@ const UserTable = ({
/>
</div>
)}
{row.status == "completed" && myUser !== row.email && (
{row.status == 'completed' && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button
onButtonPressed={() =>
@@ -214,7 +214,7 @@ const UserTable = ({
</div>
{myUser !== row.email &&
// row.role != "admin" &&
myRole != "member" ? (
myRole != 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center">
<Button
onButtonPressed={(e) =>