mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
lint(frontend): apply two space instead of tab
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import checkAuth from "~/pages/api/auth/CheckAuth";
|
||||
|
||||
import { publicPaths } from "~/const";
|
||||
import checkAuth from "~/pages/api/auth/CheckAuth";
|
||||
|
||||
// #TODO: finish spinner only when the data loads fully
|
||||
// #TODO: Redirect somewhere if the page does not exist
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import posthog from "posthog-js";
|
||||
|
||||
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config";
|
||||
import {
|
||||
ENV,
|
||||
POSTHOG_API_KEY,
|
||||
POSTHOG_HOST,
|
||||
TELEMETRY_ENABLED,
|
||||
} from "../utilities/config";
|
||||
|
||||
export const initPostHog = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
if (ENV == "production" && TELEMETRY_ENABLED) {
|
||||
posthog.init(POSTHOG_API_KEY, {
|
||||
api_host: POSTHOG_HOST,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
if (ENV == "production" && TELEMETRY_ENABLED) {
|
||||
posthog.init(POSTHOG_API_KEY, {
|
||||
api_host: POSTHOG_HOST,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return posthog;
|
||||
return posthog;
|
||||
};
|
||||
|
||||
@@ -3,17 +3,15 @@ import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function Error({ text }) {
|
||||
return (
|
||||
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
className="text-white mt-1.5 mb-2 mx-2"
|
||||
/>
|
||||
{text && (
|
||||
<p className="relative top-0 text-white mr-2 text-sm py-1">
|
||||
{text}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
className="text-white mt-1.5 mb-2 mx-2"
|
||||
/>
|
||||
{text && (
|
||||
<p className="relative top-0 text-white mr-2 text-sm py-1">{text}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faCircle,
|
||||
faCircleExclamation,
|
||||
faE,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faCircle,
|
||||
faCircleExclamation,
|
||||
faE,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
@@ -14,36 +14,36 @@ import guidGenerator from "../utilities/randomId";
|
||||
import Error from "./Error";
|
||||
|
||||
const InputField = (props) => {
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const router = useRouter();
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
if (props.static === true) {
|
||||
return (
|
||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
||||
{props.label}
|
||||
</p>
|
||||
{props.text && (
|
||||
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
||||
)}
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={props.type}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.isRequired}
|
||||
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
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
||||
{/* {props.label == "Password" && router.asPath != "/login" && (
|
||||
if (props.static === true) {
|
||||
return (
|
||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
||||
{props.label}
|
||||
</p>
|
||||
{props.text && (
|
||||
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
||||
)}
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={props.type}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.isRequired}
|
||||
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
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
||||
{/* {props.label == "Password" && router.asPath != "/login" && (
|
||||
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
@@ -59,73 +59,71 @@ const InputField = (props) => {
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
props.error ? "border-red" : "border-mineshaft-500"
|
||||
} rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
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"
|
||||
: ""
|
||||
} ${
|
||||
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"
|
||||
/>
|
||||
{props.label?.includes("Password") && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
}}
|
||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{props.blurred && (
|
||||
<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("")
|
||||
.slice(0, 54)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* {props.error && (
|
||||
</div>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
props.error ? "border-red" : "border-mineshaft-500"
|
||||
} rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
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"
|
||||
: ""
|
||||
} ${
|
||||
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"
|
||||
/>
|
||||
{props.label?.includes("Password") && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
}}
|
||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{props.blurred && (
|
||||
<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("")
|
||||
.slice(0, 54)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* {props.error && (
|
||||
<div className="absolute z-20 flex items-end justify-end mt-4 mr-1.5 self-end">
|
||||
<Error />
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
||||
{props.errorText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
||||
{props.errorText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(InputField);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React from "react";
|
||||
import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faAngleDown,
|
||||
faCheck,
|
||||
faPlus,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
|
||||
@@ -11,98 +15,97 @@ import { Listbox, Transition } from "@headlessui/react";
|
||||
* @returns
|
||||
*/
|
||||
export default function ListBox({
|
||||
selected,
|
||||
onChange,
|
||||
data,
|
||||
text,
|
||||
buttonAction,
|
||||
width,
|
||||
workspaceMapping = [],
|
||||
selected,
|
||||
onChange,
|
||||
data,
|
||||
text,
|
||||
buttonAction,
|
||||
width,
|
||||
workspaceMapping = [],
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Listbox value={selected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
width == "full" ? "w-full" : "w-52"
|
||||
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
{text}
|
||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
||||
{" "}
|
||||
{selected}
|
||||
</span>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5"/>
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Button>
|
||||
{data && (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
{data.map((person, personIdx) => (
|
||||
<Listbox.Option
|
||||
key={personIdx}
|
||||
className={({ active, selected }) =>
|
||||
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
||||
selected
|
||||
? "bg-white/10 text-gray-400 font-bold"
|
||||
: ""
|
||||
} ${
|
||||
active & !selected
|
||||
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
||||
: "text-gray-400"
|
||||
} `
|
||||
}
|
||||
value={person}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate text-primary${
|
||||
selected
|
||||
? "font-medium"
|
||||
: "font-normal"
|
||||
}`}
|
||||
>
|
||||
{person}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md ml-1"/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{buttonAction && (
|
||||
<button
|
||||
onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
>
|
||||
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2">
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg"/>
|
||||
</span>
|
||||
Add Project
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
</Listbox>
|
||||
);
|
||||
return (
|
||||
<Listbox value={selected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
width == "full" ? "w-full" : "w-52"
|
||||
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
{text}
|
||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
||||
{" "}
|
||||
{selected}
|
||||
</span>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Button>
|
||||
{data && (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
{data.map((person, personIdx) => (
|
||||
<Listbox.Option
|
||||
key={personIdx}
|
||||
className={({ active, selected }) =>
|
||||
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
||||
selected ? "bg-white/10 text-gray-400 font-bold" : ""
|
||||
} ${
|
||||
active & !selected
|
||||
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
||||
: "text-gray-400"
|
||||
} `
|
||||
}
|
||||
value={person}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate text-primary${
|
||||
selected ? "font-medium" : "font-normal"
|
||||
}`}
|
||||
>
|
||||
{person}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md ml-1"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{buttonAction && (
|
||||
<button
|
||||
onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
>
|
||||
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2">
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
</span>
|
||||
Add Project
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
</Listbox>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,105 +7,105 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
var classNames = require("classnames");
|
||||
|
||||
export default function Button({
|
||||
text,
|
||||
onButtonPressed,
|
||||
link,
|
||||
loading,
|
||||
color,
|
||||
size,
|
||||
icon,
|
||||
active = true,
|
||||
iconDisabled,
|
||||
textDisabled
|
||||
text,
|
||||
onButtonPressed,
|
||||
link,
|
||||
loading,
|
||||
color,
|
||||
size,
|
||||
icon,
|
||||
active = true,
|
||||
iconDisabled,
|
||||
textDisabled,
|
||||
}) {
|
||||
let styleButton = classNames(
|
||||
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
||||
let styleButton = classNames(
|
||||
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
||||
|
||||
// Setting background colors and hover modes
|
||||
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
|
||||
color == "mineshaft" && !active && "bg-mineshaft",
|
||||
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
|
||||
(color == "primary" || !color) && !active && "bg-primary",
|
||||
color == "red" && "bg-red",
|
||||
|
||||
// Changing the opacity when active vs when not
|
||||
active ? "opacity-100 cursor-pointer" : "opacity-40",
|
||||
// Setting background colors and hover modes
|
||||
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
|
||||
color == "mineshaft" && !active && "bg-mineshaft",
|
||||
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
|
||||
(color == "primary" || !color) && !active && "bg-primary",
|
||||
color == "red" && "bg-red",
|
||||
|
||||
// Setting the button sizes
|
||||
size == "md" && "h-10 w-full px-2 md:px-4",
|
||||
size == "lg" && "h-12 w-full px-2 md:px-8",
|
||||
!size && "md:py-1 px-3 md:px-8",
|
||||
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
|
||||
size == "icon-sm" && "h-9 w-9 flex items-center justify-center",
|
||||
);
|
||||
// Changing the opacity when active vs when not
|
||||
active ? "opacity-100 cursor-pointer" : "opacity-40",
|
||||
|
||||
let styleMainDiv = classNames(
|
||||
"relative font-medium flex items-center",
|
||||
// Setting the button sizes
|
||||
size == "md" && "h-10 w-full px-2 md:px-4",
|
||||
size == "lg" && "h-12 w-full px-2 md:px-8",
|
||||
!size && "md:py-1 px-3 md:px-8",
|
||||
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
|
||||
size == "icon-sm" && "h-9 w-9 flex items-center justify-center"
|
||||
);
|
||||
|
||||
// Setting the text color for the text and icon
|
||||
color == "mineshaft" && "text-gray-400",
|
||||
color != "mineshaft" && color != "red" && "text-black",
|
||||
color == "red" && "text-gray-200",
|
||||
active && color != "red" ? "group-hover:text-black" : "",
|
||||
let styleMainDiv = classNames(
|
||||
"relative font-medium flex items-center",
|
||||
|
||||
size == "icon" && "flex items-center justify-center",
|
||||
);
|
||||
// Setting the text color for the text and icon
|
||||
color == "mineshaft" && "text-gray-400",
|
||||
color != "mineshaft" && color != "red" && "text-black",
|
||||
color == "red" && "text-gray-200",
|
||||
active && color != "red" ? "group-hover:text-black" : "",
|
||||
|
||||
let textStyle = classNames(
|
||||
"relative duration-200",
|
||||
size == "icon" && "flex items-center justify-center"
|
||||
);
|
||||
|
||||
// Show the loading sign if the loading indicator is on
|
||||
loading == true ? "opacity-0" : "opacity-100",
|
||||
size == "md" && "text-sm",
|
||||
size == "lg" && "text-lg"
|
||||
);
|
||||
let textStyle = classNames(
|
||||
"relative duration-200",
|
||||
|
||||
const button = (
|
||||
<button
|
||||
disabled={!active}
|
||||
onClick={onButtonPressed}
|
||||
className={styleButton}
|
||||
>
|
||||
<div className={styleMainDiv}>
|
||||
<div
|
||||
className={`${
|
||||
loading == true ? "opacity-100" : "opacity-0"
|
||||
} absolute flex items-center px-2 duration-200`}
|
||||
>
|
||||
<Image
|
||||
src="/images/loading/loadingblack.gif"
|
||||
height={25}
|
||||
width={42}
|
||||
alt="loading animation"
|
||||
className={`rounded-xl`}
|
||||
></Image>
|
||||
</div>
|
||||
{icon &&
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
}
|
||||
{iconDisabled &&
|
||||
<FontAwesomeIcon
|
||||
icon={iconDisabled}
|
||||
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
}
|
||||
{(text || textDisabled) &&
|
||||
<p
|
||||
className={textStyle}
|
||||
>
|
||||
{active ? text : textDisabled}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
// Show the loading sign if the loading indicator is on
|
||||
loading == true ? "opacity-0" : "opacity-100",
|
||||
size == "md" && "text-sm",
|
||||
size == "lg" && "text-lg"
|
||||
);
|
||||
|
||||
if (link) {
|
||||
return <Link href={link}>{button}</Link>;
|
||||
}
|
||||
const button = (
|
||||
<button
|
||||
disabled={!active}
|
||||
onClick={onButtonPressed}
|
||||
className={styleButton}
|
||||
>
|
||||
<div className={styleMainDiv}>
|
||||
<div
|
||||
className={`${
|
||||
loading == true ? "opacity-100" : "opacity-0"
|
||||
} absolute flex items-center px-2 duration-200`}
|
||||
>
|
||||
<Image
|
||||
src="/images/loading/loadingblack.gif"
|
||||
height={25}
|
||||
width={42}
|
||||
alt="loading animation"
|
||||
className={`rounded-xl`}
|
||||
></Image>
|
||||
</div>
|
||||
{icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={`flex my-auto font-extrabold ${
|
||||
size == "icon-sm" ? "text-sm" : "text-md"
|
||||
} ${(text || textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
)}
|
||||
{iconDisabled && (
|
||||
<FontAwesomeIcon
|
||||
icon={iconDisabled}
|
||||
className={`flex my-auto font-extrabold ${
|
||||
size == "icon-sm" ? "text-sm" : "text-md"
|
||||
} ${(text || textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
)}
|
||||
{(text || textDisabled) && (
|
||||
<p className={textStyle}>{active ? text : textDisabled}</p>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
return button;
|
||||
if (link) {
|
||||
return <Link href={link}>{button}</Link>;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -7,91 +7,92 @@ import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
|
||||
const AddIncidentContactDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
incidentContacts,
|
||||
setIncidentContacts,
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
incidentContacts,
|
||||
setIncidentContacts,
|
||||
}) => {
|
||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||
|
||||
const submit = () => {
|
||||
setIncidentContacts(
|
||||
incidentContacts?.length > 0
|
||||
? incidentContacts.concat([incidentContactEmail])
|
||||
: [incidentContactEmail]
|
||||
);
|
||||
addIncidentContact(localStorage.getItem("orgData.id"), incidentContactEmail);
|
||||
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>
|
||||
const submit = () => {
|
||||
setIncidentContacts(
|
||||
incidentContacts?.length > 0
|
||||
? incidentContacts.concat([incidentContactEmail])
|
||||
: [incidentContactEmail]
|
||||
);
|
||||
addIncidentContact(
|
||||
localStorage.getItem("orgData.id"),
|
||||
incidentContactEmail
|
||||
);
|
||||
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"
|
||||
>
|
||||
Add an Incident Contact
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This contact will be notified in the
|
||||
unlikely event of a severe incident.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={
|
||||
setIncidentContactEmail
|
||||
}
|
||||
type="varName"
|
||||
value={incidentContactEmail}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Add Incident Contact"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<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"
|
||||
>
|
||||
Add an Incident Contact
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This contact will be notified in the unlikely event of a
|
||||
severe incident.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setIncidentContactEmail}
|
||||
type="varName"
|
||||
value={incidentContactEmail}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Add Incident Contact"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIncidentContactDialog;
|
||||
|
||||
@@ -6,146 +6,133 @@ import Button from "../buttons/Button";
|
||||
import ListBox from "../Listbox";
|
||||
|
||||
const AddProjectMemberDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
data,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
data,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" 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>
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" 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 rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
{data?.length > 0 ? (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
Add a member to your project
|
||||
</Dialog.Title>
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
All the users in your organization
|
||||
are already invited.
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className="mt-2 mb-4">
|
||||
{data?.length > 0 ? (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
The user will receive an email
|
||||
with the instructions.
|
||||
</p>
|
||||
<div className="">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
>
|
||||
If you are looking to add users to your org,
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id +
|
||||
"?invite"
|
||||
)
|
||||
}
|
||||
>
|
||||
click here.
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">
|
||||
Add more users to the
|
||||
organization first.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
{data?.length > 0 && (
|
||||
<ListBox
|
||||
selected={
|
||||
email ? email : data[0]
|
||||
}
|
||||
onChange={setEmail}
|
||||
data={data}
|
||||
width="full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
{data?.length > 0 ? (
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={submitModal}
|
||||
color="mineshaft"
|
||||
text="Add Member"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Add Users to Organization"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<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 rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
{data?.length > 0 ? (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
Add a member to your project
|
||||
</Dialog.Title>
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
All the users in your organization are already invited.
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className="mt-2 mb-4">
|
||||
{data?.length > 0 ? (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
The user will receive an email with the instructions.
|
||||
</p>
|
||||
<div className="">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push("/settings/org/" + router.query.id)
|
||||
}
|
||||
>
|
||||
If you are looking to add users to your org,
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" + router.query.id + "?invite"
|
||||
)
|
||||
}
|
||||
>
|
||||
click here.
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">
|
||||
Add more users to the organization first.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
{data?.length > 0 && (
|
||||
<ListBox
|
||||
selected={email ? email : data[0]}
|
||||
onChange={setEmail}
|
||||
data={data}
|
||||
width="full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
{data?.length > 0 ? (
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={submitModal}
|
||||
color="mineshaft"
|
||||
text="Add Member"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
router.push("/settings/org/" + router.query.id)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Add Users to Organization"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddProjectMemberDialog;
|
||||
|
||||
@@ -8,276 +8,254 @@ import nacl from "tweetnacl";
|
||||
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
|
||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} from "../../utilities/cryptography/crypto";
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import ListBox from "../Listbox";
|
||||
|
||||
const envMapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
};
|
||||
|
||||
const expiryMapping = {
|
||||
"1 day": 86400,
|
||||
"7 days": 604800,
|
||||
"1 month": 2592000,
|
||||
"1 day": 86400,
|
||||
"7 days": 604800,
|
||||
"1 month": 2592000,
|
||||
};
|
||||
|
||||
const AddServiceTokenDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
workspaceName,
|
||||
isOpen,
|
||||
closeModal,
|
||||
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 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 generateServiceToken = async () => {
|
||||
const latestFileKey = await getLatestFileKey(workspaceId);
|
||||
const generateServiceToken = async () => {
|
||||
const latestFileKey = await getLatestFileKey(workspaceId);
|
||||
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||
nonce: latestFileKey.latestKey.nonce,
|
||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||
});
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||
nonce: latestFileKey.latestKey.nonce,
|
||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||
});
|
||||
|
||||
// generate new public/private key pair
|
||||
const pair = nacl.box.keyPair();
|
||||
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
||||
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
||||
// generate new public/private key pair
|
||||
const pair = nacl.box.keyPair();
|
||||
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
||||
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
||||
|
||||
// encrypt workspace key under newly-generated public key
|
||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey,
|
||||
privateKey,
|
||||
});
|
||||
// encrypt workspace key under newly-generated public key
|
||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey,
|
||||
privateKey,
|
||||
});
|
||||
|
||||
let newServiceToken = await addServiceToken({
|
||||
name: serviceTokenName,
|
||||
workspaceId,
|
||||
environment: envMapping[serviceTokenEnv],
|
||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce,
|
||||
});
|
||||
let newServiceToken = await addServiceToken({
|
||||
name: serviceTokenName,
|
||||
workspaceId,
|
||||
environment: envMapping[serviceTokenEnv],
|
||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce,
|
||||
});
|
||||
|
||||
const serviceToken = newServiceToken + "," + privateKey;
|
||||
setServiceToken(serviceToken);
|
||||
};
|
||||
const serviceToken = newServiceToken + "," + privateKey;
|
||||
setServiceToken(serviceToken);
|
||||
};
|
||||
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
var copyText = document.getElementById("serviceToken");
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
var copyText = document.getElementById("serviceToken");
|
||||
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
|
||||
setServiceTokenCopied(true);
|
||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
setServiceTokenCopied(true);
|
||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
|
||||
const closeAddServiceTokenModal = () => {
|
||||
closeModal();
|
||||
setServiceTokenName("");
|
||||
setServiceToken("");
|
||||
};
|
||||
const closeAddServiceTokenModal = () => {
|
||||
closeModal();
|
||||
setServiceTokenName("");
|
||||
setServiceToken("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" 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-bunker-700 bg-opacity-80" />
|
||||
</Transition.Child>
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" 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-bunker-700 bg-opacity-80" />
|
||||
</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"
|
||||
>
|
||||
{serviceToken == "" ? (
|
||||
<Dialog.Panel className="w-full max-w-md transform 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 z-50"
|
||||
>
|
||||
Add a service token for{" "}
|
||||
{workspaceName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Specify the name,
|
||||
environment, and expiry
|
||||
period. When a token is
|
||||
generated, you will only be
|
||||
able to see it once before
|
||||
it disappears. Make sure to
|
||||
save it somewhere.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<InputField
|
||||
label="Service Token Name"
|
||||
onChangeHandler={
|
||||
setServiceTokenName
|
||||
}
|
||||
type="varName"
|
||||
value={serviceTokenName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<ListBox
|
||||
selected={serviceTokenEnv}
|
||||
onChange={setServiceTokenEnv}
|
||||
data={[
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
"Testing",
|
||||
]}
|
||||
width="full"
|
||||
text="Environment: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<ListBox
|
||||
selected={serviceTokenExpiresIn}
|
||||
onChange={
|
||||
setServiceTokenExpiresIn
|
||||
}
|
||||
data={[
|
||||
"1 day",
|
||||
"7 days",
|
||||
"1 month",
|
||||
]}
|
||||
width="full"
|
||||
text="Expires in: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
generateServiceToken()
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Add Service Token"
|
||||
textDisabled="Add Service Token"
|
||||
size="md"
|
||||
active={
|
||||
serviceTokenName == ""
|
||||
? false
|
||||
: true
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
) : (
|
||||
<Dialog.Panel className="w-full max-w-md transform 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 z-50"
|
||||
>
|
||||
Copy your service token
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Once you close this popup,
|
||||
you will never see your
|
||||
service token again
|
||||
</p>
|
||||
</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">
|
||||
<input
|
||||
type="text"
|
||||
value={serviceToken}
|
||||
id="serviceToken"
|
||||
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||
></input>
|
||||
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||
{serviceToken}
|
||||
</div>
|
||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<button
|
||||
onClick={
|
||||
copyToClipboard
|
||||
}
|
||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||
>
|
||||
{serviceTokenCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="pr-0.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCopy}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||
Click to Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
closeAddServiceTokenModal()
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Close"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
)}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<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"
|
||||
>
|
||||
{serviceToken == "" ? (
|
||||
<Dialog.Panel className="w-full max-w-md transform 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 z-50"
|
||||
>
|
||||
Add a service token for {workspaceName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Specify the name, environment, and expiry period. When
|
||||
a token is generated, you will only be able to see it
|
||||
once before it disappears. Make sure to save it
|
||||
somewhere.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<InputField
|
||||
label="Service Token Name"
|
||||
onChangeHandler={setServiceTokenName}
|
||||
type="varName"
|
||||
value={serviceTokenName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<ListBox
|
||||
selected={serviceTokenEnv}
|
||||
onChange={setServiceTokenEnv}
|
||||
data={[
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
"Testing",
|
||||
]}
|
||||
width="full"
|
||||
text="Environment: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<ListBox
|
||||
selected={serviceTokenExpiresIn}
|
||||
onChange={setServiceTokenExpiresIn}
|
||||
data={["1 day", "7 days", "1 month"]}
|
||||
width="full"
|
||||
text="Expires in: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() => generateServiceToken()}
|
||||
color="mineshaft"
|
||||
text="Add Service Token"
|
||||
textDisabled="Add Service Token"
|
||||
size="md"
|
||||
active={serviceTokenName == "" ? false : true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
) : (
|
||||
<Dialog.Panel className="w-full max-w-md transform 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 z-50"
|
||||
>
|
||||
Copy your service token
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Once you close this popup, you will never see your
|
||||
service token again
|
||||
</p>
|
||||
</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">
|
||||
<input
|
||||
type="text"
|
||||
value={serviceToken}
|
||||
id="serviceToken"
|
||||
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||
></input>
|
||||
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||
{serviceToken}
|
||||
</div>
|
||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||
>
|
||||
{serviceTokenCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="pr-0.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
)}
|
||||
</button>
|
||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||
Click to Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() => closeAddServiceTokenModal()}
|
||||
color="mineshaft"
|
||||
text="Close"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
)}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddServiceTokenDialog;
|
||||
|
||||
@@ -7,107 +7,103 @@ import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
|
||||
const AddUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
currentPlan,
|
||||
orgName
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
currentPlan,
|
||||
orgName,
|
||||
}) => {
|
||||
const submit = () => {
|
||||
submitModal(email);
|
||||
};
|
||||
const router = useRouter();
|
||||
const submit = () => {
|
||||
submitModal(email);
|
||||
};
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" 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>
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" 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-lg 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 z-50"
|
||||
>
|
||||
Invite others to {orgName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
An invite is specific to an email address
|
||||
and expires after 1 day. For security reasons,
|
||||
you will need to separately add members to projects.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="varName"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{currentPlan == STRIPE_PRODUCT_STARTER && <div className="flex flex-row">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/billing/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
>
|
||||
You can add up to 5 members on a Free tier.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/billing/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
>
|
||||
Upgrade now.
|
||||
</button>
|
||||
</div>}
|
||||
<div className="mt-4 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
{/* <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">
|
||||
<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-lg 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 z-50"
|
||||
>
|
||||
Invite others to {orgName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
An invite is specific to an email address and expires
|
||||
after 1 day. For security reasons, you will need to
|
||||
separately add members to projects.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="varName"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{currentPlan == STRIPE_PRODUCT_STARTER && (
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
}
|
||||
>
|
||||
You can add up to 5 members on a Free tier.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
}
|
||||
>
|
||||
Upgrade now.
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
{/* <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-xl font-medium leading-6 text-gray-300 z-50"
|
||||
@@ -139,13 +135,13 @@ const AddUserDialog = ({
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel> */}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUserDialog;
|
||||
|
||||
@@ -12,94 +12,93 @@ import { Checkbox } from "../table/Checkbox";
|
||||
* @returns
|
||||
*/
|
||||
const AddWorkspaceDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
workspaceName,
|
||||
setWorkspaceName,
|
||||
error,
|
||||
loading,
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
workspaceName,
|
||||
setWorkspaceName,
|
||||
error,
|
||||
loading,
|
||||
}) => {
|
||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||
const submit = () => {
|
||||
submitModal(workspaceName, addAllUsers);
|
||||
};
|
||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||
const submit = () => {
|
||||
submitModal(workspaceName, addAllUsers);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" 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>
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" 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 z-50">
|
||||
<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-2xl 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"
|
||||
>
|
||||
Create a new project
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This project will contain your
|
||||
environmental variables.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28 mt-4">
|
||||
<InputField
|
||||
label="Project Name"
|
||||
onChangeHandler={setWorkspaceName}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
error={error.length > 0}
|
||||
errorText={error}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 ml-1">
|
||||
<Checkbox
|
||||
addAllUsers={addAllUsers}
|
||||
setAddAllUsers={setAddAllUsers}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 max-w-min">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
loading={loading}
|
||||
color="mineshaft"
|
||||
text="Create"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<div className="fixed inset-0 overflow-y-auto z-50">
|
||||
<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-2xl 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"
|
||||
>
|
||||
Create a new project
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This project will contain your environmental variables.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28 mt-4">
|
||||
<InputField
|
||||
label="Project Name"
|
||||
onChangeHandler={setWorkspaceName}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
error={error.length > 0}
|
||||
errorText={error}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 ml-1">
|
||||
<Checkbox
|
||||
addAllUsers={addAllUsers}
|
||||
setAddAllUsers={setAddAllUsers}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 max-w-min">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
loading={loading}
|
||||
color="mineshaft"
|
||||
text="Create"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddWorkspaceDialog;
|
||||
|
||||
@@ -1,78 +1,83 @@
|
||||
import { Fragment, useState } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import InputField from '../InputField';
|
||||
import InputField from "../InputField";
|
||||
|
||||
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
|
||||
|
||||
const DeleteUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
userIdToBeDeleted,
|
||||
}) => {
|
||||
const submit = () => {
|
||||
submitModal(userIdToBeDeleted);
|
||||
};
|
||||
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-25" />
|
||||
</Transition.Child>
|
||||
|
||||
const DeleteUserDialog = ({isOpen, closeModal, submitModal, userIdToBeDeleted}) => {
|
||||
const submit = () => {
|
||||
submitModal(userIdToBeDeleted);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<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"
|
||||
enterTo="opacity-100"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</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-2xl bg-grey 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"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-grey 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"
|
||||
>
|
||||
Are you sure you want to remove this user from the
|
||||
workspace?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This action is irrevertible.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={submit}
|
||||
>
|
||||
Are you sure you want to remove this user from the workspace?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This action is irrevertible.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={submit}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={submit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={submit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteUserDialog;
|
||||
export default DeleteUserDialog;
|
||||
|
||||
@@ -3,11 +3,11 @@ import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faGear,
|
||||
faHouse,
|
||||
faLink,
|
||||
faMobile,
|
||||
faUser,
|
||||
faGear,
|
||||
faHouse,
|
||||
faLink,
|
||||
faMobile,
|
||||
faUser,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -20,319 +20,285 @@ import createWorkspace from "~/pages/api/workspace/createWorkspace";
|
||||
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
||||
|
||||
import NavBarDashboard from "../navigation/NavBarDashboard";
|
||||
import { decryptAssymmetric, encryptAssymmetric } from "../utilities/cryptography/crypto";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} from "../utilities/cryptography/crypto";
|
||||
import Button from "./buttons/Button";
|
||||
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
||||
import Listbox from "./Listbox";
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const router = useRouter();
|
||||
const [workspaceList, setWorkspaceList] = useState([]);
|
||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
||||
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||
let [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceList, setWorkspaceList] = useState([]);
|
||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
||||
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||
let [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
function closeModal() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
function closeModal() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
// TODO: what to do about the fact that 2ids can have the same name
|
||||
// TODO: what to do about the fact that 2ids can have the same name
|
||||
|
||||
/**
|
||||
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
||||
* @param {*} workspaceName
|
||||
*/
|
||||
async function submitModal(workspaceName, addAllUsers) {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 1500);
|
||||
const workspaces = await getWorkspaces();
|
||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||
if (!currentWorkspaces.includes(workspaceName)) {
|
||||
const newWorkspace = await createWorkspace(
|
||||
workspaceName,
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
let newWorkspaceId;
|
||||
try {
|
||||
newWorkspaceId = newWorkspace._id;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (addAllUsers) {
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
orgUsers.map(async (user) => {
|
||||
if (user.status == "accepted") {
|
||||
let result = await addUserToWorkspace(
|
||||
user.user.email,
|
||||
newWorkspaceId
|
||||
);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY =
|
||||
localStorage.getItem("PRIVATE_KEY");
|
||||
/**
|
||||
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
||||
* @param {*} workspaceName
|
||||
*/
|
||||
async function submitModal(workspaceName, addAllUsers) {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 1500);
|
||||
const workspaces = await getWorkspaces();
|
||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||
if (!currentWorkspaces.includes(workspaceName)) {
|
||||
const newWorkspace = await createWorkspace(
|
||||
workspaceName,
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
let newWorkspaceId;
|
||||
try {
|
||||
newWorkspaceId = newWorkspace._id;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (addAllUsers) {
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
orgUsers.map(async (user) => {
|
||||
if (user.status == "accepted") {
|
||||
let result = await addUserToWorkspace(
|
||||
user.user.email,
|
||||
newWorkspaceId
|
||||
);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
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,
|
||||
});
|
||||
// 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,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(
|
||||
newWorkspaceId,
|
||||
result.invitee._id,
|
||||
ciphertext,
|
||||
nonce
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||
setIsOpen(false);
|
||||
setNewWorkspaceName("");
|
||||
} else {
|
||||
setError("A project with this name already exists.");
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
uploadKeys(newWorkspaceId, result.invitee._id, ciphertext, nonce);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||
setIsOpen(false);
|
||||
setNewWorkspaceName("");
|
||||
} else {
|
||||
setError("A project with this name already exists.");
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
setIsOpen(true);
|
||||
}
|
||||
function openModal() {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href:
|
||||
"/dashboard/" +
|
||||
workspaceMapping[workspaceSelected] +
|
||||
"?Development",
|
||||
title: "Secrets",
|
||||
emoji: <FontAwesomeIcon icon={faHouse} />,
|
||||
},
|
||||
{
|
||||
href: "/users/" + workspaceMapping[workspaceSelected],
|
||||
title: "Members",
|
||||
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||
},
|
||||
{
|
||||
href: "/integrations/" + workspaceMapping[workspaceSelected],
|
||||
title: "Integrations",
|
||||
emoji: <FontAwesomeIcon icon={faLink} />,
|
||||
},
|
||||
{
|
||||
href: "/settings/project/" + workspaceMapping[workspaceSelected],
|
||||
title: "Project Settings",
|
||||
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||
},
|
||||
];
|
||||
const menuItems = [
|
||||
{
|
||||
href:
|
||||
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
|
||||
title: "Secrets",
|
||||
emoji: <FontAwesomeIcon icon={faHouse} />,
|
||||
},
|
||||
{
|
||||
href: "/users/" + workspaceMapping[workspaceSelected],
|
||||
title: "Members",
|
||||
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||
},
|
||||
{
|
||||
href: "/integrations/" + workspaceMapping[workspaceSelected],
|
||||
title: "Integrations",
|
||||
emoji: <FontAwesomeIcon icon={faLink} />,
|
||||
},
|
||||
{
|
||||
href: "/settings/project/" + workspaceMapping[workspaceSelected],
|
||||
title: "Project Settings",
|
||||
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(async () => {
|
||||
// Put a user in a workspace if they're not in one yet
|
||||
if (
|
||||
localStorage.getItem("orgData.id") == null ||
|
||||
localStorage.getItem("orgData.id") == ""
|
||||
) {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||
}
|
||||
useEffect(async () => {
|
||||
// Put a user in a workspace if they're not in one yet
|
||||
if (
|
||||
localStorage.getItem("orgData.id") == null ||
|
||||
localStorage.getItem("orgData.id") == ""
|
||||
) {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||
}
|
||||
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let userWorkspaces = orgUserProjects;
|
||||
if (
|
||||
userWorkspaces.length == 0 &&
|
||||
router.asPath != "/noprojects" &&
|
||||
!router.asPath.includes("settings")
|
||||
) {
|
||||
router.push("/noprojects");
|
||||
} else if (router.asPath != "/noprojects") {
|
||||
const intendedWorkspaceId = router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0];
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let userWorkspaces = orgUserProjects;
|
||||
if (
|
||||
userWorkspaces.length == 0 &&
|
||||
router.asPath != "/noprojects" &&
|
||||
!router.asPath.includes("settings")
|
||||
) {
|
||||
router.push("/noprojects");
|
||||
} else if (router.asPath != "/noprojects") {
|
||||
const intendedWorkspaceId = router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0];
|
||||
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
intendedWorkspaceId != "heroku" &&
|
||||
!userWorkspaces
|
||||
.map((workspace) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" + userWorkspaces[0]._id + "?Development"
|
||||
);
|
||||
} else {
|
||||
setWorkspaceList(
|
||||
userWorkspaces.map((workspace) => workspace.name)
|
||||
);
|
||||
setWorkspaceMapping(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [
|
||||
workspace.name,
|
||||
workspace._id,
|
||||
])
|
||||
)
|
||||
);
|
||||
setWorkspaceSelected(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [
|
||||
workspace._id,
|
||||
workspace.name,
|
||||
])
|
||||
)[
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
intendedWorkspaceId != "heroku" &&
|
||||
!userWorkspaces
|
||||
.map((workspace) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
) {
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
|
||||
} else {
|
||||
setWorkspaceList(userWorkspaces.map((workspace) => workspace.name));
|
||||
setWorkspaceMapping(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [workspace.name, workspace._id])
|
||||
)
|
||||
);
|
||||
setWorkspaceSelected(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [workspace._id, workspace.name])
|
||||
)[
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (
|
||||
workspaceMapping[workspaceSelected] &&
|
||||
workspaceMapping[workspaceSelected] !==
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" +
|
||||
workspaceMapping[workspaceSelected] +
|
||||
"?Development"
|
||||
);
|
||||
localStorage.setItem(
|
||||
"projectData.id",
|
||||
workspaceMapping[workspaceSelected]
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [workspaceSelected]);
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (
|
||||
workspaceMapping[workspaceSelected] &&
|
||||
workspaceMapping[workspaceSelected] !==
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development"
|
||||
);
|
||||
localStorage.setItem(
|
||||
"projectData.id",
|
||||
workspaceMapping[workspaceSelected]
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [workspaceSelected]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed w-full hidden md:block flex flex-col h-screen">
|
||||
<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">
|
||||
<nav>
|
||||
<div className="py-6"></div>
|
||||
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
PROJECT
|
||||
</div>
|
||||
{workspaceList.length > 0 ? (
|
||||
<Listbox
|
||||
selected={workspaceSelected}
|
||||
onChange={setWorkspaceSelected}
|
||||
data={workspaceList}
|
||||
buttonAction={openModal}
|
||||
text=""
|
||||
workspaceMapping={workspaceMapping}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
text="Add Project"
|
||||
onButtonPressed={openModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ul>
|
||||
{workspaceList.length > 0 &&
|
||||
menuItems.map(({ href, title, emoji }) => (
|
||||
<li className="mt-1.5 mx-2" key={title}>
|
||||
{router.asPath.split("/")[1] ===
|
||||
href.split("/")[1] &&
|
||||
([
|
||||
"project",
|
||||
"billing",
|
||||
"org",
|
||||
"personal",
|
||||
].includes(
|
||||
router.asPath.split("/")[2]
|
||||
)
|
||||
? router.asPath.split(
|
||||
"/"
|
||||
)[2] === href.split("/")[2]
|
||||
: true) ? (
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
|
||||
>
|
||||
<div className="bg-primary w-1 rounded-xl mr-1"></div>
|
||||
<p className="ml-2 mr-4">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
) : router.asPath ==
|
||||
"/noprojects" ? (
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded`}
|
||||
>
|
||||
<p className="ml-2 mr-4">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
) : (
|
||||
<Link href={href}>
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
|
||||
>
|
||||
<p className="ml-2 mr-4">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<AddWorkspaceDialog
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
submitModal={submitModal}
|
||||
workspaceName={newWorkspaceName}
|
||||
setWorkspaceName={setNewWorkspaceName}
|
||||
error={error}
|
||||
loading={loading}
|
||||
/>
|
||||
<main className="flex-1 bg-bunker-800">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faMobile}
|
||||
className="text-gray-300 text-7xl mb-8"
|
||||
/>
|
||||
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
||||
{" "}
|
||||
To use Infisical, please log in through a device with larger
|
||||
dimensions.{" "}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="fixed w-full hidden md:block flex flex-col h-screen">
|
||||
<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">
|
||||
<nav>
|
||||
<div className="py-6"></div>
|
||||
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
PROJECT
|
||||
</div>
|
||||
{workspaceList.length > 0 ? (
|
||||
<Listbox
|
||||
selected={workspaceSelected}
|
||||
onChange={setWorkspaceSelected}
|
||||
data={workspaceList}
|
||||
buttonAction={openModal}
|
||||
text=""
|
||||
workspaceMapping={workspaceMapping}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
text="Add Project"
|
||||
onButtonPressed={openModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ul>
|
||||
{workspaceList.length > 0 &&
|
||||
menuItems.map(({ href, title, emoji }) => (
|
||||
<li className="mt-1.5 mx-2" key={title}>
|
||||
{router.asPath.split("/")[1] === href.split("/")[1] &&
|
||||
(["project", "billing", "org", "personal"].includes(
|
||||
router.asPath.split("/")[2]
|
||||
)
|
||||
? router.asPath.split("/")[2] === href.split("/")[2]
|
||||
: true) ? (
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
|
||||
>
|
||||
<div className="bg-primary w-1 rounded-xl mr-1"></div>
|
||||
<p className="ml-2 mr-4">{emoji}</p>
|
||||
{title}
|
||||
</div>
|
||||
) : router.asPath == "/noprojects" ? (
|
||||
<div className={`flex p-2 text-white text-sm rounded`}>
|
||||
<p className="ml-2 mr-4">{emoji}</p>
|
||||
{title}
|
||||
</div>
|
||||
) : (
|
||||
<Link href={href}>
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
|
||||
>
|
||||
<p className="ml-2 mr-4">{emoji}</p>
|
||||
{title}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<AddWorkspaceDialog
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
submitModal={submitModal}
|
||||
workspaceName={newWorkspaceName}
|
||||
setWorkspaceName={setNewWorkspaceName}
|
||||
error={error}
|
||||
loading={loading}
|
||||
/>
|
||||
<main className="flex-1 bg-bunker-800">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faMobile}
|
||||
className="text-gray-300 text-7xl mb-8"
|
||||
/>
|
||||
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
||||
{" "}
|
||||
To use Infisical, please log in through a device with larger
|
||||
dimensions.{" "}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,49 +3,49 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function BottonRightPopup({
|
||||
buttonText,
|
||||
buttonLink,
|
||||
titleText,
|
||||
emoji,
|
||||
textLine1,
|
||||
textLine2,
|
||||
setCheckDocsPopUpVisible,
|
||||
buttonText,
|
||||
buttonLink,
|
||||
titleText,
|
||||
emoji,
|
||||
textLine1,
|
||||
textLine2,
|
||||
setCheckDocsPopUpVisible,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
class="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-xl absolute bottom-0 right-0 mr-6 mb-6"
|
||||
role="alert"
|
||||
>
|
||||
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
|
||||
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||
<div>{titleText}</div>
|
||||
<div class="ml-2.5">{emoji}</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-1"
|
||||
onClick={() => setCheckDocsPopUpVisible(false)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
||||
{textLine1}
|
||||
</div>
|
||||
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||
<div className="flex flex-row px-6 w-full">
|
||||
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||
href={buttonLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
class="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-xl absolute bottom-0 right-0 mr-6 mb-6"
|
||||
role="alert"
|
||||
>
|
||||
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
|
||||
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||
<div>{titleText}</div>
|
||||
<div class="ml-2.5">{emoji}</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-1"
|
||||
onClick={() => setCheckDocsPopUpVisible(false)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
||||
{textLine1}
|
||||
</div>
|
||||
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||
<div className="flex flex-row px-6 w-full">
|
||||
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||
href={buttonLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import React from "react";
|
||||
|
||||
export const Checkbox = ({ addAllUsers, setAddAllUsers }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-center">
|
||||
{addAllUsers == true ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-primary h-4 w-4"
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
></div>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-center">
|
||||
{addAllUsers == true ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-primary h-4 w-4"
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
></div>
|
||||
)}
|
||||
|
||||
<label className="ml-2 text-gray-500 text-sm">
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<label className="ml-2 text-gray-500 text-sm">
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,11 +8,11 @@ import Button from "../buttons/Button";
|
||||
const roles = ["admin", "user"];
|
||||
|
||||
const reverseEnvMapping = {
|
||||
"dev": "Development",
|
||||
"staging": "Staging",
|
||||
"prod": "Production",
|
||||
"test": "Testing"
|
||||
}
|
||||
dev: "Development",
|
||||
staging: "Staging",
|
||||
prod: "Production",
|
||||
test: "Testing",
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
|
||||
@@ -20,63 +20,66 @@ const reverseEnvMapping = {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const ServiceTokenTable = ({
|
||||
data,
|
||||
workspaceName
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const ServiceTokenTable = ({ data, workspaceName }) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-bunker-300">
|
||||
<tr>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.length > 0
|
||||
? data
|
||||
.map((row, index) => {
|
||||
return (
|
||||
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5 duration-100">
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.name}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{workspaceName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{reverseEnvMapping[row.environment]}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{new Date(row.expiresAt).toUTCString()}
|
||||
</td>
|
||||
<td className="py-2 border-mineshaft-700 border-t">
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={() => {}}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: <tr>
|
||||
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">No service tokens yet</td>
|
||||
</tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-bunker-300">
|
||||
<tr>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.length > 0 ? (
|
||||
data.map((row, index) => {
|
||||
return (
|
||||
<tr
|
||||
key={guidGenerator()}
|
||||
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
|
||||
>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.name}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{workspaceName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{reverseEnvMapping[row.environment]}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{new Date(row.expiresAt).toUTCString()}
|
||||
</td>
|
||||
<td className="py-2 border-mineshaft-700 border-t">
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={() => {}}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">
|
||||
No service tokens yet
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceTokenTable;
|
||||
|
||||
@@ -13,8 +13,8 @@ import Button from "../buttons/Button";
|
||||
import Listbox from "../Listbox";
|
||||
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} = require("../../utilities/cryptography/crypto");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
@@ -28,245 +28,214 @@ const roles = ["admin", "user"];
|
||||
* @returns
|
||||
*/
|
||||
const UserTable = ({
|
||||
userData,
|
||||
changeData,
|
||||
myUser,
|
||||
filter,
|
||||
resendInvite,
|
||||
isOrg,
|
||||
onClick,
|
||||
deleteUser,
|
||||
setUserIdToBeDeleted,
|
||||
userData,
|
||||
changeData,
|
||||
myUser,
|
||||
filter,
|
||||
resendInvite,
|
||||
isOrg,
|
||||
onClick,
|
||||
deleteUser,
|
||||
setUserIdToBeDeleted,
|
||||
}) => {
|
||||
const [roleSelected, setRoleSelected] = useState(
|
||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
||||
);
|
||||
const router = useRouter();
|
||||
const [myRole, setMyRole] = useState("member");
|
||||
const [roleSelected, setRoleSelected] = useState(
|
||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
||||
);
|
||||
const router = useRouter();
|
||||
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.
|
||||
const handleDelete = (membershipId, index, e) => {
|
||||
// setUserIdToBeDeleted(userId);
|
||||
// onClick();
|
||||
if (isOrg) {
|
||||
deleteUserFromOrganization(membershipId);
|
||||
} else {
|
||||
deleteUserFromWorkspace(membershipId);
|
||||
}
|
||||
changeData(userData.filter((v, i) => i !== index));
|
||||
setRoleSelected([
|
||||
...roleSelected.slice(0, index),
|
||||
...roleSelected.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
// 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.
|
||||
const handleDelete = (membershipId, index, e) => {
|
||||
// setUserIdToBeDeleted(userId);
|
||||
// onClick();
|
||||
if (isOrg) {
|
||||
deleteUserFromOrganization(membershipId);
|
||||
} else {
|
||||
deleteUserFromWorkspace(membershipId);
|
||||
}
|
||||
changeData(userData.filter((v, i) => i !== index));
|
||||
setRoleSelected([
|
||||
...roleSelected.slice(0, index),
|
||||
...roleSelected.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
|
||||
// Update the rold of a certain user
|
||||
const handleRoleUpdate = (index, e) => {
|
||||
changeUserRoleInWorkspace(userData[index].membershipId, e);
|
||||
changeData([
|
||||
...userData.slice(0, index),
|
||||
...[
|
||||
{
|
||||
key: userData[index].key,
|
||||
firstName: userData[index].firstName,
|
||||
lastName: userData[index].lastName,
|
||||
email: userData[index].email,
|
||||
role: e,
|
||||
status: userData[index].status,
|
||||
userId: userData[index].userId,
|
||||
membershipId: userData[index].membershipId,
|
||||
publicKey: userData[index].publicKey,
|
||||
},
|
||||
],
|
||||
...userData.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
// Update the rold of a certain user
|
||||
const handleRoleUpdate = (index, e) => {
|
||||
changeUserRoleInWorkspace(userData[index].membershipId, e);
|
||||
changeData([
|
||||
...userData.slice(0, index),
|
||||
...[
|
||||
{
|
||||
key: userData[index].key,
|
||||
firstName: userData[index].firstName,
|
||||
lastName: userData[index].lastName,
|
||||
email: userData[index].email,
|
||||
role: e,
|
||||
status: userData[index].status,
|
||||
userId: userData[index].userId,
|
||||
membershipId: userData[index].membershipId,
|
||||
publicKey: userData[index].publicKey,
|
||||
},
|
||||
],
|
||||
...userData.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
|
||||
}, [userData, myUser]);
|
||||
useEffect(() => {
|
||||
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
|
||||
}, [userData, myUser]);
|
||||
|
||||
const grantAccess = async (id, publicKey) => {
|
||||
let result = await getLatestFileKey(router.query.id);
|
||||
const grantAccess = async (id, publicKey) => {
|
||||
let result = await getLatestFileKey(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,
|
||||
});
|
||||
// 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,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(router.query.id, id, ciphertext, nonce);
|
||||
router.reload();
|
||||
};
|
||||
uploadKeys(router.query.id, id, ciphertext, nonce);
|
||||
router.reload();
|
||||
};
|
||||
|
||||
const deleteMembershipAndResendInvite = (email, membershipId) => {
|
||||
// deleteUserFromWorkspace(membershipId);
|
||||
resendInvite(email);
|
||||
};
|
||||
const deleteMembershipAndResendInvite = (email, membershipId) => {
|
||||
// deleteUserFromWorkspace(membershipId);
|
||||
resendInvite(email);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-gray-400">
|
||||
<tr>
|
||||
<th className="text-left pl-6 py-3.5">First Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Last Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Email</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userData?.filter(
|
||||
(user) =>
|
||||
user.firstName?.toLowerCase().includes(filter) ||
|
||||
user.lastName?.toLowerCase().includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
).length > 0 &&
|
||||
userData
|
||||
?.filter(
|
||||
(user) =>
|
||||
user.firstName
|
||||
?.toLowerCase()
|
||||
.includes(filter) ||
|
||||
user.lastName
|
||||
?.toLowerCase()
|
||||
.includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
)
|
||||
.map((row, index) => {
|
||||
return (
|
||||
<tr
|
||||
key={guidGenerator()}
|
||||
className="bg-bunker-800 hover:bg-bunker-800/5"
|
||||
>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.firstName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.lastName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.email}
|
||||
</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") &&
|
||||
myUser !== row.email ? (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
onChange={(e) =>
|
||||
handleRoleUpdate(
|
||||
index,
|
||||
e
|
||||
)
|
||||
}
|
||||
data={
|
||||
myRole == "owner"
|
||||
? [
|
||||
"owner",
|
||||
"admin",
|
||||
"member",
|
||||
]
|
||||
: [
|
||||
"admin",
|
||||
"member",
|
||||
]
|
||||
}
|
||||
text="Role: "
|
||||
membershipId={
|
||||
row.membershipId
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
row.status != "invited" &&
|
||||
row.status !=
|
||||
"verified" && (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
text="Role: "
|
||||
membershipId={
|
||||
row.membershipId
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{(row.status == "invited" ||
|
||||
row.status ==
|
||||
"verified") && (
|
||||
<div className="w-full pl-12">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteMembershipAndResendInvite(
|
||||
row.email,
|
||||
row.membershipId
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Resend Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{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={() =>
|
||||
grantAccess(
|
||||
row.userId,
|
||||
row.publicKey
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Grant Access"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{myUser !== row.email &&
|
||||
// row.role != "admin" &&
|
||||
myRole != "member" ? (
|
||||
<div className="opacity-50 hover:opacity-100 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={(e) =>
|
||||
handleDelete(
|
||||
row.membershipId,
|
||||
index,
|
||||
e
|
||||
)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-9 h-9"></div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-gray-400">
|
||||
<tr>
|
||||
<th className="text-left pl-6 py-3.5">First Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Last Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Email</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userData?.filter(
|
||||
(user) =>
|
||||
user.firstName?.toLowerCase().includes(filter) ||
|
||||
user.lastName?.toLowerCase().includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
).length > 0 &&
|
||||
userData
|
||||
?.filter(
|
||||
(user) =>
|
||||
user.firstName?.toLowerCase().includes(filter) ||
|
||||
user.lastName?.toLowerCase().includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
)
|
||||
.map((row, index) => {
|
||||
return (
|
||||
<tr
|
||||
key={guidGenerator()}
|
||||
className="bg-bunker-800 hover:bg-bunker-800/5"
|
||||
>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.firstName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.lastName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.email}
|
||||
</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") &&
|
||||
myUser !== row.email ? (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
onChange={(e) => handleRoleUpdate(index, e)}
|
||||
data={
|
||||
myRole == "owner"
|
||||
? ["owner", "admin", "member"]
|
||||
: ["admin", "member"]
|
||||
}
|
||||
text="Role: "
|
||||
membershipId={row.membershipId}
|
||||
/>
|
||||
) : (
|
||||
row.status != "invited" &&
|
||||
row.status != "verified" && (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
text="Role: "
|
||||
membershipId={row.membershipId}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{(row.status == "invited" ||
|
||||
row.status == "verified") && (
|
||||
<div className="w-full pl-12">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteMembershipAndResendInvite(
|
||||
row.email,
|
||||
row.membershipId
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Resend Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{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={() =>
|
||||
grantAccess(row.userId, row.publicKey)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Grant Access"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{myUser !== row.email &&
|
||||
// row.role != "admin" &&
|
||||
myRole != "member" ? (
|
||||
<div className="opacity-50 hover:opacity-100 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={(e) =>
|
||||
handleDelete(row.membershipId, index, e)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-9 h-9"></div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTable;
|
||||
|
||||
@@ -3,91 +3,82 @@ import React from "react";
|
||||
import StripeRedirect from "~/pages/api/organization/StripeRedirect";
|
||||
|
||||
export default function Plan({ plan }) {
|
||||
return (
|
||||
<div
|
||||
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "border-primary"
|
||||
: "border-chicago-700"
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "border-primary"
|
||||
: "border-chicago-700"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-between items-center relative z-10">
|
||||
<p
|
||||
className={`px-6 py-4 text-3xl font-semibold text-gray-400`}
|
||||
>
|
||||
{plan.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flwx-row items-end justify-start mb-4">
|
||||
<p className="pl-6 text-3xl font-semibold text-primary">
|
||||
{plan.price}
|
||||
</p>
|
||||
<p className="pl-3 mb-1 text-lg text-gray-400">
|
||||
{plan.priceExplanation}
|
||||
</p>
|
||||
</div>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.text}
|
||||
</p>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.subtext}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
{plan.current == false ? (
|
||||
<>
|
||||
{plan.buttonTextMain == "Schedule a Demo" ? (
|
||||
<a
|
||||
href="/scheduledemo"
|
||||
target='_blank rel="noopener"'
|
||||
>
|
||||
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
||||
{plan.buttonTextMain}
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
|
||||
plan.buttonTextMain == "Downgrade"
|
||||
? "hover:bg-red hover:text-white hover:border-red"
|
||||
: "hover:bg-primary hover:text-black hover:border-primary"
|
||||
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
|
||||
>
|
||||
<button
|
||||
onClick={() =>
|
||||
StripeRedirect({
|
||||
orgId: localStorage.getItem(
|
||||
"orgData.id"
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
{plan.buttonTextMain}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<a href="/pricing" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
||||
{plan.buttonTextSecondary}
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "bg-primary"
|
||||
: "bg-chicago-700"
|
||||
}`}
|
||||
>
|
||||
<p className="text-xs text-black font-semibold">
|
||||
CURRENT PLAN
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-between items-center relative z-10">
|
||||
<p className={`px-6 py-4 text-3xl font-semibold text-gray-400`}>
|
||||
{plan.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flwx-row items-end justify-start mb-4">
|
||||
<p className="pl-6 text-3xl font-semibold text-primary">
|
||||
{plan.price}
|
||||
</p>
|
||||
<p className="pl-3 mb-1 text-lg text-gray-400">
|
||||
{plan.priceExplanation}
|
||||
</p>
|
||||
</div>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.text}
|
||||
</p>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.subtext}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
{plan.current == false ? (
|
||||
<>
|
||||
{plan.buttonTextMain == "Schedule a Demo" ? (
|
||||
<a href="/scheduledemo" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
||||
{plan.buttonTextMain}
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
|
||||
plan.buttonTextMain == "Downgrade"
|
||||
? "hover:bg-red hover:text-white hover:border-red"
|
||||
: "hover:bg-primary hover:text-black hover:border-primary"
|
||||
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
|
||||
>
|
||||
<button
|
||||
onClick={() =>
|
||||
StripeRedirect({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
})
|
||||
}
|
||||
>
|
||||
{plan.buttonTextMain}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<a href="/pricing" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
||||
{plan.buttonTextSecondary}
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "bg-primary"
|
||||
: "bg-chicago-700"
|
||||
}`}
|
||||
>
|
||||
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,17 +10,17 @@ import guidGenerator from "../utilities/randomId";
|
||||
* @returns
|
||||
*/
|
||||
const findReferences = (text) => {
|
||||
var splitText = text.split("${");
|
||||
let textArray = [splitText[0]];
|
||||
for (var i = 1; i < splitText.length; i++) {
|
||||
let insideBrackets = "${" + splitText[i].split("}")[0];
|
||||
if (splitText[i].includes("}")) {
|
||||
insideBrackets += "}";
|
||||
}
|
||||
textArray.push(insideBrackets);
|
||||
textArray.push(splitText[i].split("}")[1]);
|
||||
}
|
||||
return textArray;
|
||||
var splitText = text.split("${");
|
||||
let textArray = [splitText[0]];
|
||||
for (var i = 1; i < splitText.length; i++) {
|
||||
let insideBrackets = "${" + splitText[i].split("}")[0];
|
||||
if (splitText[i].includes("}")) {
|
||||
insideBrackets += "}";
|
||||
}
|
||||
textArray.push(insideBrackets);
|
||||
textArray.push(splitText[i].split("}")[1]);
|
||||
}
|
||||
return textArray;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,110 +34,98 @@ const findReferences = (text) => {
|
||||
* @returns
|
||||
*/
|
||||
const DashboardInputField = ({
|
||||
index,
|
||||
onChangeHandler,
|
||||
type,
|
||||
value,
|
||||
blurred,
|
||||
index,
|
||||
onChangeHandler,
|
||||
type,
|
||||
value,
|
||||
blurred,
|
||||
}) => {
|
||||
if (type === "varName") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
type={type}
|
||||
value={value}
|
||||
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (type === "value") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
type={type}
|
||||
value={value}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
|
||||
: ""
|
||||
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
<div
|
||||
className={`${
|
||||
blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
|
||||
: ""
|
||||
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
|
||||
>
|
||||
{findReferences(value).map((texts, id) => {
|
||||
if (id % 2 == 0 || texts.length <= 2) {
|
||||
return (
|
||||
<span className="ph-no-capture" key={id}>
|
||||
{texts}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className="ph-no-capture text-yellow"
|
||||
key={id}
|
||||
>
|
||||
{texts.slice(0, 2)}
|
||||
<span className="ph-no-capture text-yellow-200/80">
|
||||
{texts.slice(2, texts.length - 1)}
|
||||
</span>
|
||||
{texts.slice(
|
||||
texts.length - 1,
|
||||
texts.length
|
||||
) == "}" ? (
|
||||
<span className="ph-no-capture text-yellow">
|
||||
{texts.slice(
|
||||
texts.length - 1,
|
||||
texts.length
|
||||
)}{" "}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ph-no-capture text-yellow-400">
|
||||
{texts.slice(
|
||||
texts.length - 1,
|
||||
texts.length
|
||||
)}{" "}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{blurred && (
|
||||
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||
<p className="ml-2"></p>
|
||||
{value
|
||||
.split("")
|
||||
.slice(0, 42)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (type === "varName") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
type={type}
|
||||
value={value}
|
||||
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (type === "value") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
type={type}
|
||||
value={value}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
|
||||
: ""
|
||||
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
<div
|
||||
className={`${
|
||||
blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
|
||||
: ""
|
||||
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
|
||||
>
|
||||
{findReferences(value).map((texts, id) => {
|
||||
if (id % 2 == 0 || texts.length <= 2) {
|
||||
return (
|
||||
<span className="ph-no-capture" key={id}>
|
||||
{texts}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="ph-no-capture text-yellow" key={id}>
|
||||
{texts.slice(0, 2)}
|
||||
<span className="ph-no-capture text-yellow-200/80">
|
||||
{texts.slice(2, texts.length - 1)}
|
||||
</span>
|
||||
{texts.slice(texts.length - 1, texts.length) == "}" ? (
|
||||
<span className="ph-no-capture text-yellow">
|
||||
{texts.slice(texts.length - 1, texts.length)}{" "}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ph-no-capture text-yellow-400">
|
||||
{texts.slice(texts.length - 1, texts.length)}{" "}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{blurred && (
|
||||
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||
<p className="ml-2"></p>
|
||||
{value
|
||||
.split("")
|
||||
.slice(0, 42)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(DashboardInputField);
|
||||
|
||||
@@ -9,178 +9,177 @@ import parse from "../utilities/file";
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
const DropZone = ({
|
||||
setData,
|
||||
setErrorDragAndDrop,
|
||||
createNewFile,
|
||||
errorDragAndDrop,
|
||||
addPresetRow,
|
||||
setButtonReady,
|
||||
keysExist,
|
||||
numCurrentRows
|
||||
setData,
|
||||
setErrorDragAndDrop,
|
||||
createNewFile,
|
||||
errorDragAndDrop,
|
||||
addPresetRow,
|
||||
setButtonReady,
|
||||
keysExist,
|
||||
numCurrentRows,
|
||||
}) => {
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// set dropEffect to copy i.e copy of the source item
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
};
|
||||
// set dropEffect to copy i.e copy of the source item
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
};
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// This function function immediately parses the file after it is dropped
|
||||
const handleDrop = async (e) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 5000);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
// This function function immediately parses the file after it is dropped
|
||||
const handleDrop = async (e) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 5000);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
|
||||
var file = e.dataTransfer.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const keyPairs = parse(event.target.result)
|
||||
const newData = Object.keys(keyPairs)
|
||||
.map((key, index) => [
|
||||
guidGenerator(),
|
||||
numCurrentRows + index,
|
||||
key,
|
||||
keyPairs[key],
|
||||
"shared",
|
||||
]);
|
||||
setData(newData);
|
||||
setButtonReady(true);
|
||||
};
|
||||
var file = e.dataTransfer.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const keyPairs = parse(event.target.result);
|
||||
const newData = Object.keys(keyPairs).map((key, index) => [
|
||||
guidGenerator(),
|
||||
numCurrentRows + index,
|
||||
key,
|
||||
keyPairs[key],
|
||||
"shared",
|
||||
]);
|
||||
setData(newData);
|
||||
setButtonReady(true);
|
||||
};
|
||||
|
||||
// If something is wrong show an error
|
||||
try {
|
||||
reader.readAsText(file);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setErrorDragAndDrop(true);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// If something is wrong show an error
|
||||
try {
|
||||
reader.readAsText(file);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setErrorDragAndDrop(true);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
|
||||
const handleFileSelect = (e) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 5000);
|
||||
var file = e.target.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const newData = event.target.result
|
||||
.split("\n")
|
||||
.map((line, index) => [
|
||||
guidGenerator(),
|
||||
numCurrentRows + index,
|
||||
line.split("=")[0],
|
||||
line.split("=").slice(1, line.split("=").length).join("="),
|
||||
"shared",
|
||||
]);
|
||||
setData(newData);
|
||||
setButtonReady(true);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
|
||||
const handleFileSelect = (e) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 5000);
|
||||
var file = e.target.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const newData = event.target.result
|
||||
.split("\n")
|
||||
.map((line, index) => [
|
||||
guidGenerator(),
|
||||
numCurrentRows + index,
|
||||
line.split("=")[0],
|
||||
line.split("=").slice(1, line.split("=").length).join("="),
|
||||
"shared",
|
||||
]);
|
||||
setData(newData);
|
||||
setButtonReady(true);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<div className="flex items-center justify-center pt-16 mb-16">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="google logo"
|
||||
></Image>
|
||||
</div>
|
||||
) : keysExist ? (
|
||||
<div
|
||||
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
||||
onDragEnter={(e) => handleDragEnter(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
>
|
||||
<input
|
||||
id="fileSelect"
|
||||
type="file"
|
||||
className="opacity-0 absolute w-full h-full"
|
||||
accept=".txt,.env"
|
||||
onChange={(e) => handleFileSelect(e)}
|
||||
/>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="my-3 max-w-xl opacity-80"></div>
|
||||
) : (
|
||||
<div className=""></div>
|
||||
)}
|
||||
<div className="flex flex-row">
|
||||
<FontAwesomeIcon
|
||||
icon={faUpload}
|
||||
className="text-gray-300 text-3xl mr-6"
|
||||
/>
|
||||
<p className="text-gray-300 mt-1">
|
||||
Drag and drop your .env file here to add more keys.
|
||||
</p>
|
||||
</div>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="mt-8 max-w-xl opacity-80">
|
||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
|
||||
onDragEnter={(e) => handleDragEnter(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
||||
<p className="">Drag and drop your .env file here.</p>
|
||||
<input
|
||||
id="fileSelect"
|
||||
type="file"
|
||||
className="opacity-0 absolute w-full h-full"
|
||||
accept=".txt,.env"
|
||||
onChange={(e) => handleFileSelect(e)}
|
||||
/>
|
||||
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
|
||||
<div className="border-t border-gray-700 w-1/5"></div>
|
||||
<p className="text-gray-400 text-xs mx-4">OR</p>
|
||||
<div className="border-t border-gray-700 w-1/5"></div>
|
||||
</div>
|
||||
<div className="z-10 mb-6">
|
||||
<Button
|
||||
color="mineshaft"
|
||||
text="Create a new .env file"
|
||||
onButtonPressed={createNewFile}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="opacity-80">
|
||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-3">
|
||||
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return loading ? (
|
||||
<div className="flex items-center justify-center pt-16 mb-16">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="google logo"
|
||||
></Image>
|
||||
</div>
|
||||
) : keysExist ? (
|
||||
<div
|
||||
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
||||
onDragEnter={(e) => handleDragEnter(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
>
|
||||
<input
|
||||
id="fileSelect"
|
||||
type="file"
|
||||
className="opacity-0 absolute w-full h-full"
|
||||
accept=".txt,.env"
|
||||
onChange={(e) => handleFileSelect(e)}
|
||||
/>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="my-3 max-w-xl opacity-80"></div>
|
||||
) : (
|
||||
<div className=""></div>
|
||||
)}
|
||||
<div className="flex flex-row">
|
||||
<FontAwesomeIcon
|
||||
icon={faUpload}
|
||||
className="text-gray-300 text-3xl mr-6"
|
||||
/>
|
||||
<p className="text-gray-300 mt-1">
|
||||
Drag and drop your .env file here to add more keys.
|
||||
</p>
|
||||
</div>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="mt-8 max-w-xl opacity-80">
|
||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
|
||||
onDragEnter={(e) => handleDragEnter(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
||||
<p className="">Drag and drop your .env file here.</p>
|
||||
<input
|
||||
id="fileSelect"
|
||||
type="file"
|
||||
className="opacity-0 absolute w-full h-full"
|
||||
accept=".txt,.env"
|
||||
onChange={(e) => handleFileSelect(e)}
|
||||
/>
|
||||
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
|
||||
<div className="border-t border-gray-700 w-1/5"></div>
|
||||
<p className="text-gray-400 text-xs mx-4">OR</p>
|
||||
<div className="border-t border-gray-700 w-1/5"></div>
|
||||
</div>
|
||||
<div className="z-10 mb-6">
|
||||
<Button
|
||||
color="mineshaft"
|
||||
text="Create a new .env file"
|
||||
onButtonPressed={createNewFile}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="opacity-80">
|
||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-3">
|
||||
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropZone;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable react/jsx-key */
|
||||
import React, { Fragment, useEffect,useState } from "react";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { faGithub,faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faAngleDown,
|
||||
faBook,
|
||||
faCoins,
|
||||
faEnvelope,
|
||||
faGear,
|
||||
faPlus,
|
||||
faRightFromBracket,
|
||||
faAngleDown,
|
||||
faBook,
|
||||
faCoins,
|
||||
faEnvelope,
|
||||
faGear,
|
||||
faPlus,
|
||||
faRightFromBracket,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
@@ -25,301 +25,269 @@ import getUser from "~/pages/api/user/getUser";
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
const supportOptions = [
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
|
||||
"Join Slack Forum",
|
||||
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
|
||||
"Read Docs",
|
||||
"https://infisical.com/docs/getting-started/introduction",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
|
||||
"Open a GitHub Issue",
|
||||
"https://github.com/Infisical/infisical-cli/issues",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
|
||||
"Send us an Email",
|
||||
"mailto:support@infisical.com",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
|
||||
"Join Slack Forum",
|
||||
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
|
||||
"Read Docs",
|
||||
"https://infisical.com/docs/getting-started/introduction",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
|
||||
"Open a GitHub Issue",
|
||||
"https://github.com/Infisical/infisical-cli/issues",
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
|
||||
"Send us an Email",
|
||||
"mailto:support@infisical.com",
|
||||
],
|
||||
];
|
||||
|
||||
export default function Navbar({ onButtonPressed }) {
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState({});
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const [currentOrg, setCurrentOrg] = useState([]);
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState({});
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const [currentOrg, setCurrentOrg] = useState([]);
|
||||
|
||||
useEffect(async () => {
|
||||
const userData = await getUser();
|
||||
setUser(userData);
|
||||
const orgsData = await getOrganizations();
|
||||
setOrgs(orgsData);
|
||||
const currentOrg = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setCurrentOrg(currentOrg);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
const userData = await getUser();
|
||||
setUser(userData);
|
||||
const orgsData = await getOrganizations();
|
||||
setOrgs(orgsData);
|
||||
const currentOrg = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setCurrentOrg(currentOrg);
|
||||
}, []);
|
||||
|
||||
const closeApp = async () => {
|
||||
console.log("Logging out...");
|
||||
await logout();
|
||||
router.push("/");
|
||||
};
|
||||
const closeApp = async () => {
|
||||
console.log("Logging out...");
|
||||
await logout();
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
|
||||
<div className="m-auto flex justify-start items-center mx-4">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex justify-center py-4">
|
||||
<Image
|
||||
src="/images/logotransparent.png"
|
||||
height={23}
|
||||
width={57}
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
<a className="text-2xl text-white font-semibold mx-2">
|
||||
Infisical
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex justify-start items-center mx-2 z-40">
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div className="mr-4">
|
||||
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
<FontAwesomeIcon
|
||||
className="text-xl"
|
||||
icon={faCircleQuestion}
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
|
||||
{supportOptions.map((option) => (
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
<a
|
||||
key={guidGenerator()}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href={option[2]}
|
||||
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
|
||||
>
|
||||
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
|
||||
{option[0]}
|
||||
<div className="text-sm">
|
||||
{option[1]}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
<Menu as="div" className="relative inline-block text-left mr-4">
|
||||
<div>
|
||||
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
{user?.firstName} {user?.lastName}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleDown}
|
||||
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
|
||||
<div className="px-1 py-1 ">
|
||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||
SIGNED IN AS
|
||||
</div>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/personal/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||
>
|
||||
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
|
||||
{user?.firstName?.charAt(0)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div>
|
||||
<p className="text-gray-300 px-2 pt-1 text-sm">
|
||||
{" "}
|
||||
{user?.firstName}{" "}
|
||||
{user?.lastName}
|
||||
</p>
|
||||
<p className="text-gray-400 px-2 pb-1 text-xs">
|
||||
{" "}
|
||||
{user?.email}
|
||||
</p>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faGear}
|
||||
className="text-lg text-gray-400 p-2 mr-1 rounded-md cursor-pointer hover:bg-white/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||
CURRENT ORGANIZATION
|
||||
</div>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" + router.query.id
|
||||
)
|
||||
}
|
||||
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||
>
|
||||
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
||||
{currentOrg?.name?.charAt(0)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="text-gray-300 px-2 text-sm">
|
||||
{currentOrg?.name}
|
||||
</p>
|
||||
<FontAwesomeIcon
|
||||
icon={faGear}
|
||||
className="text-lg text-gray-400 p-2 rounded-md cursor-pointer hover:bg-white/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
// onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/billing/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="text-lg pl-1.5 pr-3"
|
||||
icon={faCoins}
|
||||
/>
|
||||
<div className="text-sm">
|
||||
Usage & Billing
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
// onClick={buttonAction}
|
||||
className="cursor-pointer w-full mb-2"
|
||||
>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id +
|
||||
"?invite"
|
||||
)
|
||||
}
|
||||
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
|
||||
>
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faPlus}
|
||||
className="ml-1"
|
||||
/>
|
||||
</span>
|
||||
<div className="text-sm ml-1">
|
||||
Invite Members
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{orgs?.length > 1 && (
|
||||
<div className="px-1 pt-1">
|
||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||
OTHER ORGANIZATIONS
|
||||
</div>
|
||||
<div className="flex flex-col items-start px-1 mt-3 mb-2">
|
||||
{orgs
|
||||
.filter(
|
||||
(org) =>
|
||||
org._id !=
|
||||
localStorage.getItem(
|
||||
"orgData.id"
|
||||
)
|
||||
)
|
||||
.map((org) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
onClick={() => {
|
||||
localStorage.setItem(
|
||||
"orgData.id",
|
||||
org._id
|
||||
);
|
||||
router.reload();
|
||||
}}
|
||||
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
|
||||
>
|
||||
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
||||
{org.name.charAt(0)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="text-gray-300 px-2 text-sm">
|
||||
{org.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={closeApp}
|
||||
className={`${
|
||||
active
|
||||
? "bg-red font-semibold text-white"
|
||||
: "text-gray-400"
|
||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||
>
|
||||
<div className="relative flex justify-start items-center cursor-pointer select-none">
|
||||
<FontAwesomeIcon
|
||||
className="text-lg ml-1.5 mr-3"
|
||||
icon={faRightFromBracket}
|
||||
/>
|
||||
Log Out
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
|
||||
<div className="m-auto flex justify-start items-center mx-4">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex justify-center py-4">
|
||||
<Image
|
||||
src="/images/logotransparent.png"
|
||||
height={23}
|
||||
width={57}
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
<a className="text-2xl text-white font-semibold mx-2">Infisical</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex justify-start items-center mx-2 z-40">
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div className="mr-4">
|
||||
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
<FontAwesomeIcon className="text-xl" icon={faCircleQuestion} />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
|
||||
{supportOptions.map((option) => (
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
<a
|
||||
key={guidGenerator()}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href={option[2]}
|
||||
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
|
||||
>
|
||||
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
|
||||
{option[0]}
|
||||
<div className="text-sm">{option[1]}</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
<Menu as="div" className="relative inline-block text-left mr-4">
|
||||
<div>
|
||||
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
{user?.firstName} {user?.lastName}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleDown}
|
||||
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
|
||||
<div className="px-1 py-1 ">
|
||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||
SIGNED IN AS
|
||||
</div>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push("/settings/personal/" + router.query.id)
|
||||
}
|
||||
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||
>
|
||||
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
|
||||
{user?.firstName?.charAt(0)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div>
|
||||
<p className="text-gray-300 px-2 pt-1 text-sm">
|
||||
{" "}
|
||||
{user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
<p className="text-gray-400 px-2 pb-1 text-xs">
|
||||
{" "}
|
||||
{user?.email}
|
||||
</p>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faGear}
|
||||
className="text-lg text-gray-400 p-2 mr-1 rounded-md cursor-pointer hover:bg-white/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||
CURRENT ORGANIZATION
|
||||
</div>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push("/settings/org/" + router.query.id)
|
||||
}
|
||||
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||
>
|
||||
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
||||
{currentOrg?.name?.charAt(0)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="text-gray-300 px-2 text-sm">
|
||||
{currentOrg?.name}
|
||||
</p>
|
||||
<FontAwesomeIcon
|
||||
icon={faGear}
|
||||
className="text-lg text-gray-400 p-2 rounded-md cursor-pointer hover:bg-white/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
// onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
}
|
||||
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="text-lg pl-1.5 pr-3"
|
||||
icon={faCoins}
|
||||
/>
|
||||
<div className="text-sm">Usage & Billing</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
// onClick={buttonAction}
|
||||
className="cursor-pointer w-full mb-2"
|
||||
>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" + router.query.id + "?invite"
|
||||
)
|
||||
}
|
||||
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
|
||||
>
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="ml-1" />
|
||||
</span>
|
||||
<div className="text-sm ml-1">Invite Members</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{orgs?.length > 1 && (
|
||||
<div className="px-1 pt-1">
|
||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||
OTHER ORGANIZATIONS
|
||||
</div>
|
||||
<div className="flex flex-col items-start px-1 mt-3 mb-2">
|
||||
{orgs
|
||||
.filter(
|
||||
(org) => org._id != localStorage.getItem("orgData.id")
|
||||
)
|
||||
.map((org) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
onClick={() => {
|
||||
localStorage.setItem("orgData.id", org._id);
|
||||
router.reload();
|
||||
}}
|
||||
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
|
||||
>
|
||||
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
||||
{org.name.charAt(0)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="text-gray-300 px-2 text-sm">
|
||||
{org.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={closeApp}
|
||||
className={`${
|
||||
active
|
||||
? "bg-red font-semibold text-white"
|
||||
: "text-gray-400"
|
||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||
>
|
||||
<div className="relative flex justify-start items-center cursor-pointer select-none">
|
||||
<FontAwesomeIcon
|
||||
className="text-lg ml-1.5 mr-3"
|
||||
icon={faRightFromBracket}
|
||||
/>
|
||||
Log Out
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faAngleRight,
|
||||
faQuestionCircle,
|
||||
faAngleRight,
|
||||
faQuestionCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -12,46 +12,46 @@ import getOrganization from "~/pages/api/organization/GetOrg";
|
||||
import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo";
|
||||
|
||||
export default function NavHeader({ pageName, isProjectRelated }) {
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const router = useRouter();
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setOrgName(org.name);
|
||||
let workspace = await getWorkspaceInfo({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setWorkspaceName(workspace.name);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setOrgName(org.name);
|
||||
let workspace = await getWorkspaceInfo({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setWorkspaceName(workspace.name);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="pt-20 ml-6 flex flex-row items-center">
|
||||
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2">
|
||||
{orgName?.charAt(0)}
|
||||
</div>
|
||||
<div className="text-primary text-sm font-semibold">{orgName}</div>
|
||||
{isProjectRelated && (
|
||||
<>
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-3 text-sm text-gray-400 mr-3"
|
||||
/>
|
||||
<div className="font-semibold text-primary text-sm">
|
||||
{workspaceName}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-3 text-sm text-gray-400 mr-3"
|
||||
/>
|
||||
<div className="text-gray-400 text-sm">{pageName}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="pt-20 ml-6 flex flex-row items-center">
|
||||
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2">
|
||||
{orgName?.charAt(0)}
|
||||
</div>
|
||||
<div className="text-primary text-sm font-semibold">{orgName}</div>
|
||||
{isProjectRelated && (
|
||||
<>
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-3 text-sm text-gray-400 mr-3"
|
||||
/>
|
||||
<div className="font-semibold text-primary text-sm">
|
||||
{workspaceName}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-3 text-sm text-gray-400 mr-3"
|
||||
/>
|
||||
<div className="text-gray-400 text-sm">{pageName}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import token from "~/pages/api/auth/Token";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import token from "~/pages/api/auth/Token";
|
||||
|
||||
export default class SecurityClient {
|
||||
static authOrigins = [PATH];
|
||||
|
||||
@@ -5,8 +5,8 @@ import getOrganizations from "~/pages/api/organization/getOrgs";
|
||||
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
|
||||
|
||||
import { initPostHog } from "../analytics/posthog";
|
||||
import { ENV } from "./config";
|
||||
import pushKeys from "./secrets/pushKeys";
|
||||
import { ENV } from "./config";
|
||||
import SecurityClient from "./SecurityClient";
|
||||
|
||||
const nacl = require("tweetnacl");
|
||||
@@ -24,184 +24,158 @@ const client = new jsrp.client();
|
||||
* @returns
|
||||
*/
|
||||
const attemptLogin = async (
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
isSignUp,
|
||||
isLogin
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
isSignUp,
|
||||
isLogin
|
||||
) => {
|
||||
try {
|
||||
let userWorkspace, userOrg;
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
try {
|
||||
let userWorkspace, userOrg;
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await login1(email, clientPublicKey);
|
||||
res = await res.json();
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.log("Wrong password", err);
|
||||
}
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await login1(email, clientPublicKey);
|
||||
res = await res.json();
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.log("Wrong password", err);
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
client.setSalt(salt);
|
||||
client.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = client.getProof(); // called M1
|
||||
response = await login2(email, clientProof);
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.log("Password verification failed");
|
||||
}
|
||||
let response;
|
||||
try {
|
||||
client.setSalt(salt);
|
||||
client.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = client.getProof(); // called M1
|
||||
response = await login2(email, clientProof);
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.log("Password verification failed");
|
||||
}
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
try {
|
||||
if (response.status == "200") {
|
||||
response = await response.json();
|
||||
SecurityClient.setToken(response["token"]);
|
||||
const publicKey = response["publicKey"];
|
||||
const encryptedPrivateKey =
|
||||
response["encryptedPrivateKey"];
|
||||
const iv = response["iv"];
|
||||
const tag = response["tag"];
|
||||
// if everything works, go the main dashboard page.
|
||||
try {
|
||||
if (response.status == "200") {
|
||||
response = await response.json();
|
||||
SecurityClient.setToken(response["token"]);
|
||||
const publicKey = response["publicKey"];
|
||||
const encryptedPrivateKey = response["encryptedPrivateKey"];
|
||||
const iv = response["iv"];
|
||||
const tag = response["tag"];
|
||||
|
||||
const PRIVATE_KEY = Aes256Gcm.decrypt(
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 +
|
||||
(password.slice(0, 32).length -
|
||||
new Blob([password]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
const PRIVATE_KEY = Aes256Gcm.decrypt(
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 +
|
||||
(password.slice(0, 32).length - new Blob([password]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
localStorage.setItem("publicKey", publicKey);
|
||||
localStorage.setItem(
|
||||
"encryptedPrivateKey",
|
||||
encryptedPrivateKey
|
||||
);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.error(
|
||||
"Unable to send the tokens in local storage:" +
|
||||
err.message
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setErrorLogin(true);
|
||||
}
|
||||
try {
|
||||
localStorage.setItem("publicKey", publicKey);
|
||||
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.error(
|
||||
"Unable to send the tokens in local storage:" + err.message
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setErrorLogin(true);
|
||||
}
|
||||
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrgsData = userOrgs.map((org) => org._id);
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrgsData = userOrgs.map((org) => org._id);
|
||||
|
||||
let orgToLogin;
|
||||
if (
|
||||
userOrgsData.includes(
|
||||
localStorage.getItem("orgData.id")
|
||||
)
|
||||
) {
|
||||
orgToLogin = localStorage.getItem("orgData.id");
|
||||
} else {
|
||||
orgToLogin = userOrgsData[0];
|
||||
localStorage.setItem("orgData.id", orgToLogin);
|
||||
}
|
||||
let orgToLogin;
|
||||
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) {
|
||||
orgToLogin = localStorage.getItem("orgData.id");
|
||||
} else {
|
||||
orgToLogin = userOrgsData[0];
|
||||
localStorage.setItem("orgData.id", orgToLogin);
|
||||
}
|
||||
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: orgToLogin,
|
||||
});
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: orgToLogin,
|
||||
});
|
||||
|
||||
orgUserProjects = orgUserProjects?.map(
|
||||
(project) => project._id
|
||||
);
|
||||
let projectToLogin;
|
||||
if (
|
||||
orgUserProjects.includes(
|
||||
localStorage.getItem("projectData.id")
|
||||
)
|
||||
) {
|
||||
projectToLogin = localStorage.getItem("projectData.id");
|
||||
} else {
|
||||
try {
|
||||
projectToLogin = orgUserProjects[0];
|
||||
localStorage.setItem(
|
||||
"projectData.id",
|
||||
projectToLogin
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"ERROR: User likely has no projects. ",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
orgUserProjects = orgUserProjects?.map((project) => project._id);
|
||||
let projectToLogin;
|
||||
if (
|
||||
orgUserProjects.includes(localStorage.getItem("projectData.id"))
|
||||
) {
|
||||
projectToLogin = localStorage.getItem("projectData.id");
|
||||
} else {
|
||||
try {
|
||||
projectToLogin = orgUserProjects[0];
|
||||
localStorage.setItem("projectData.id", projectToLogin);
|
||||
} catch (error) {
|
||||
console.log("ERROR: User likely has no projects. ", error);
|
||||
}
|
||||
}
|
||||
|
||||
// If user is logging in for the first time, add the example keys
|
||||
if (isSignUp) {
|
||||
await pushKeys(
|
||||
{
|
||||
DATABASE_URL: [
|
||||
"mongodb+srv://this_is:an_example@mongodb.net",
|
||||
"personal",
|
||||
],
|
||||
TWILIO_AUTH_TOKEN: [
|
||||
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
|
||||
"shared",
|
||||
],
|
||||
WEBSITE_URL: [
|
||||
"http://localhost:3000",
|
||||
"shared",
|
||||
],
|
||||
STRIPE_SECRET_KEY: [
|
||||
"sk_test_7348oyho4hfq398HIUOH78",
|
||||
"shared",
|
||||
],
|
||||
},
|
||||
projectToLogin,
|
||||
"Development"
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (email) {
|
||||
if (ENV == "production") {
|
||||
const posthog = initPostHog();
|
||||
posthog.identify(email);
|
||||
posthog.capture("User Logged In");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("posthog", error);
|
||||
}
|
||||
// If user is logging in for the first time, add the example keys
|
||||
if (isSignUp) {
|
||||
await pushKeys(
|
||||
{
|
||||
DATABASE_URL: [
|
||||
"mongodb+srv://this_is:an_example@mongodb.net",
|
||||
"personal",
|
||||
],
|
||||
TWILIO_AUTH_TOKEN: [
|
||||
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
|
||||
"shared",
|
||||
],
|
||||
WEBSITE_URL: ["http://localhost:3000", "shared"],
|
||||
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
|
||||
},
|
||||
projectToLogin,
|
||||
"Development"
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (email) {
|
||||
if (ENV == "production") {
|
||||
const posthog = initPostHog();
|
||||
posthog.identify(email);
|
||||
posthog.capture("User Logged In");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("posthog", error);
|
||||
}
|
||||
|
||||
if (isLogin) {
|
||||
router.push("/dashboard/");
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorLogin(true);
|
||||
console.log("Login response not available");
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Something went wrong during authentication");
|
||||
}
|
||||
return true;
|
||||
if (isLogin) {
|
||||
router.push("/dashboard/");
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorLogin(true);
|
||||
console.log("Login response not available");
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Something went wrong during authentication");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export default attemptLogin;
|
||||
|
||||
@@ -7,59 +7,59 @@
|
||||
* @returns
|
||||
*/
|
||||
const passwordCheck = (
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck
|
||||
) => {
|
||||
let errorCheck = currentErrorCheck;
|
||||
if (!password || password.length < 14) {
|
||||
setPasswordErrorLength(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setPasswordErrorLength(false);
|
||||
}
|
||||
|
||||
if (!/\d/.test(password)) {
|
||||
setPasswordErrorNumber(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setPasswordErrorNumber(false);
|
||||
}
|
||||
|
||||
if (!/[a-z]/.test(password)) {
|
||||
setPasswordErrorLowerCase(true);
|
||||
errorCheck = true;
|
||||
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
|
||||
// console.log(111)
|
||||
// setPasswordError(true);
|
||||
// setPasswordErrorMessage("Password should not contain repeating characters.");
|
||||
// errorCheck = true;
|
||||
// } else if (RegExp(`[${email}]`).test(password)) {
|
||||
// console.log(222)
|
||||
// setPasswordError(true);
|
||||
// setPasswordErrorMessage("Password should not contain your email.");
|
||||
// errorCheck = true;
|
||||
} else {
|
||||
setPasswordErrorLowerCase(false);
|
||||
}
|
||||
|
||||
// if (!/[A-Z]/.test(password)) {
|
||||
// setPasswordErrorUpperCase(true);
|
||||
// errorCheck = true;
|
||||
// } else {
|
||||
// setPasswordErrorUpperCase(false);
|
||||
// }
|
||||
|
||||
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
|
||||
// setPasswordErrorSpecialChar(true);
|
||||
// // "Please add at least 1 special character (*, !, #, %)."
|
||||
// errorCheck = true;
|
||||
// } else {
|
||||
// setPasswordErrorSpecialChar(false);
|
||||
// }
|
||||
return errorCheck;
|
||||
let errorCheck = currentErrorCheck;
|
||||
if (!password || password.length < 14) {
|
||||
setPasswordErrorLength(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setPasswordErrorLength(false);
|
||||
}
|
||||
|
||||
if (!/\d/.test(password)) {
|
||||
setPasswordErrorNumber(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setPasswordErrorNumber(false);
|
||||
}
|
||||
|
||||
if (!/[a-z]/.test(password)) {
|
||||
setPasswordErrorLowerCase(true);
|
||||
errorCheck = true;
|
||||
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
|
||||
// console.log(111)
|
||||
// setPasswordError(true);
|
||||
// setPasswordErrorMessage("Password should not contain repeating characters.");
|
||||
// errorCheck = true;
|
||||
// } else if (RegExp(`[${email}]`).test(password)) {
|
||||
// console.log(222)
|
||||
// setPasswordError(true);
|
||||
// setPasswordErrorMessage("Password should not contain your email.");
|
||||
// errorCheck = true;
|
||||
} else {
|
||||
setPasswordErrorLowerCase(false);
|
||||
}
|
||||
|
||||
// if (!/[A-Z]/.test(password)) {
|
||||
// setPasswordErrorUpperCase(true);
|
||||
// errorCheck = true;
|
||||
// } else {
|
||||
// setPasswordErrorUpperCase(false);
|
||||
// }
|
||||
|
||||
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
|
||||
// setPasswordErrorSpecialChar(true);
|
||||
// // "Please add at least 1 special character (*, !, #, %)."
|
||||
// errorCheck = true;
|
||||
// } else {
|
||||
// setPasswordErrorSpecialChar(false);
|
||||
// }
|
||||
return errorCheck;
|
||||
};
|
||||
|
||||
export default passwordCheck;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
const ENV = process.env.NEXT_PUBLIC_ENV! || 'development'; // investigate
|
||||
const ENV = process.env.NEXT_PUBLIC_ENV! || "development"; // investigate
|
||||
const POSTHOG_API_KEY = process.env.NEXT_PUBLIC_POSTHOG_API_KEY!;
|
||||
const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST! || 'https://app.posthog.com';
|
||||
const POSTHOG_HOST =
|
||||
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
|
||||
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
|
||||
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
|
||||
const TELEMETRY_ENABLED = (process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== 'false');
|
||||
const TELEMETRY_ENABLED =
|
||||
process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== "false";
|
||||
|
||||
export {
|
||||
ENV,
|
||||
POSTHOG_API_KEY,
|
||||
POSTHOG_HOST,
|
||||
STRIPE_PRODUCT_PRO,
|
||||
STRIPE_PRODUCT_STARTER,
|
||||
TELEMETRY_ENABLED
|
||||
ENV,
|
||||
POSTHOG_API_KEY,
|
||||
POSTHOG_HOST,
|
||||
STRIPE_PRODUCT_PRO,
|
||||
STRIPE_PRODUCT_STARTER,
|
||||
TELEMETRY_ENABLED,
|
||||
};
|
||||
|
||||
@@ -13,51 +13,51 @@ const BLOCK_SIZE_BYTES = 16; // 128 bit
|
||||
* Provides easy encryption/decryption methods using AES 256 GCM.
|
||||
*/
|
||||
class Aes256Gcm {
|
||||
/**
|
||||
* No need to run the constructor. The class only has static methods.
|
||||
*/
|
||||
constructor() {}
|
||||
/**
|
||||
* No need to run the constructor. The class only has static methods.
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Encrypts text with AES 256 GCM.
|
||||
* @param {string} text - Cleartext to encode.
|
||||
* @param {string} secret - Shared secret key, must be 32 bytes.
|
||||
* @returns {object}
|
||||
*/
|
||||
static encrypt(text, secret) {
|
||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
||||
/**
|
||||
* Encrypts text with AES 256 GCM.
|
||||
* @param {string} text - Cleartext to encode.
|
||||
* @param {string} secret - Shared secret key, must be 32 bytes.
|
||||
* @returns {object}
|
||||
*/
|
||||
static encrypt(text, secret) {
|
||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
||||
|
||||
let ciphertext = cipher.update(text, "utf8", "base64");
|
||||
ciphertext += cipher.final("base64");
|
||||
return {
|
||||
ciphertext,
|
||||
iv: iv.toString("base64"),
|
||||
tag: cipher.getAuthTag().toString("base64"),
|
||||
};
|
||||
}
|
||||
let ciphertext = cipher.update(text, "utf8", "base64");
|
||||
ciphertext += cipher.final("base64");
|
||||
return {
|
||||
ciphertext,
|
||||
iv: iv.toString("base64"),
|
||||
tag: cipher.getAuthTag().toString("base64"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts AES 256 CGM encrypted text.
|
||||
* @param {string} ciphertext - Base64-encoded ciphertext.
|
||||
* @param {string} iv - The base64-encoded initialization vector.
|
||||
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
|
||||
* @param {string} secret - Shared secret key, must be 32 bytes.
|
||||
* @returns {string}
|
||||
*/
|
||||
static decrypt(ciphertext, iv, tag, secret) {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
ALGORITHM,
|
||||
secret,
|
||||
Buffer.from(iv, "base64")
|
||||
);
|
||||
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
||||
/**
|
||||
* Decrypts AES 256 CGM encrypted text.
|
||||
* @param {string} ciphertext - Base64-encoded ciphertext.
|
||||
* @param {string} iv - The base64-encoded initialization vector.
|
||||
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
|
||||
* @param {string} secret - Shared secret key, must be 32 bytes.
|
||||
* @returns {string}
|
||||
*/
|
||||
static decrypt(ciphertext, iv, tag, secret) {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
ALGORITHM,
|
||||
secret,
|
||||
Buffer.from(iv, "base64")
|
||||
);
|
||||
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
||||
|
||||
let cleartext = decipher.update(ciphertext, "base64", "utf8");
|
||||
cleartext += decipher.final("utf8");
|
||||
let cleartext = decipher.update(ciphertext, "base64", "utf8");
|
||||
cleartext += decipher.final("utf8");
|
||||
|
||||
return cleartext;
|
||||
}
|
||||
return cleartext;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Aes256Gcm;
|
||||
|
||||
@@ -19,109 +19,102 @@ const clientNewPassword = new jsrp.client();
|
||||
* @returns
|
||||
*/
|
||||
const changePassword = async (
|
||||
email,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
email,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
) => {
|
||||
try {
|
||||
setPasswordChanged(false);
|
||||
setCurrentPasswordError(false);
|
||||
try {
|
||||
setPasswordChanged(false);
|
||||
setCurrentPasswordError(false);
|
||||
|
||||
clientOldPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: currentPassword,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
clientOldPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: currentPassword,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await SRP1({
|
||||
clientPublicKey: clientPublicKey,
|
||||
});
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log("Wrong current password", err, 1);
|
||||
}
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await SRP1({
|
||||
clientPublicKey: clientPublicKey,
|
||||
});
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log("Wrong current password", err, 1);
|
||||
}
|
||||
|
||||
clientOldPassword.setSalt(salt);
|
||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = clientOldPassword.getProof(); // called M1
|
||||
clientOldPassword.setSalt(salt);
|
||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = clientOldPassword.getProof(); // called M1
|
||||
|
||||
clientNewPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: newPassword,
|
||||
},
|
||||
async () => {
|
||||
clientNewPassword.createVerifier(
|
||||
async (err, result) => {
|
||||
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
|
||||
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||
localStorage.getItem("PRIVATE_KEY"),
|
||||
newPassword
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 +
|
||||
(newPassword.slice(0, 32)
|
||||
.length -
|
||||
new Blob([newPassword])
|
||||
.size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
clientNewPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: newPassword,
|
||||
},
|
||||
async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
|
||||
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||
localStorage.getItem("PRIVATE_KEY"),
|
||||
newPassword
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 +
|
||||
(newPassword.slice(0, 32).length -
|
||||
new Blob([newPassword]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
|
||||
if (ciphertext) {
|
||||
localStorage.setItem(
|
||||
"encryptedPrivateKey",
|
||||
ciphertext
|
||||
);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
if (ciphertext) {
|
||||
localStorage.setItem("encryptedPrivateKey", ciphertext);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await changePassword2({
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
clientProof,
|
||||
});
|
||||
if (res.status == 400) {
|
||||
setCurrentPasswordError(true);
|
||||
} else if (res.status == 200) {
|
||||
setPasswordChanged(true);
|
||||
setCurrentPassword("");
|
||||
setNewPassword("");
|
||||
}
|
||||
} catch (err) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Something went wrong during changing the password",
|
||||
slat,
|
||||
serverPublicKey
|
||||
);
|
||||
}
|
||||
return true;
|
||||
let res;
|
||||
try {
|
||||
res = await changePassword2({
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
clientProof,
|
||||
});
|
||||
if (res.status == 400) {
|
||||
setCurrentPasswordError(true);
|
||||
} else if (res.status == 200) {
|
||||
setPasswordChanged(true);
|
||||
setCurrentPassword("");
|
||||
setNewPassword("");
|
||||
}
|
||||
} catch (err) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Something went wrong during changing the password",
|
||||
slat,
|
||||
serverPublicKey
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export default changePassword;
|
||||
|
||||
@@ -14,18 +14,18 @@ const aes = require("./aes-256-gcm");
|
||||
* @returns {String} nonce - base64-encoded nonce
|
||||
*/
|
||||
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
|
||||
const nonce = nacl.randomBytes(24);
|
||||
const ciphertext = nacl.box(
|
||||
nacl.util.decodeUTF8(plaintext),
|
||||
nonce,
|
||||
nacl.util.decodeBase64(publicKey),
|
||||
nacl.util.decodeBase64(privateKey)
|
||||
);
|
||||
const nonce = nacl.randomBytes(24);
|
||||
const ciphertext = nacl.box(
|
||||
nacl.util.decodeUTF8(plaintext),
|
||||
nonce,
|
||||
nacl.util.decodeBase64(publicKey),
|
||||
nacl.util.decodeBase64(privateKey)
|
||||
);
|
||||
|
||||
return {
|
||||
ciphertext: nacl.util.encodeBase64(ciphertext),
|
||||
nonce: nacl.util.encodeBase64(nonce),
|
||||
};
|
||||
return {
|
||||
ciphertext: nacl.util.encodeBase64(ciphertext),
|
||||
nonce: nacl.util.encodeBase64(nonce),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -39,14 +39,14 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
|
||||
* @param {String} plaintext - UTF8 plaintext
|
||||
*/
|
||||
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
|
||||
const plaintext = nacl.box.open(
|
||||
nacl.util.decodeBase64(ciphertext),
|
||||
nacl.util.decodeBase64(nonce),
|
||||
nacl.util.decodeBase64(publicKey),
|
||||
nacl.util.decodeBase64(privateKey)
|
||||
);
|
||||
const plaintext = nacl.box.open(
|
||||
nacl.util.decodeBase64(ciphertext),
|
||||
nacl.util.decodeBase64(nonce),
|
||||
nacl.util.decodeBase64(publicKey),
|
||||
nacl.util.decodeBase64(privateKey)
|
||||
);
|
||||
|
||||
return nacl.util.encodeUTF8(plaintext);
|
||||
return nacl.util.encodeUTF8(plaintext);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -56,23 +56,23 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
|
||||
* @param {String} obj.key - 16-byte hex key
|
||||
*/
|
||||
const encryptSymmetric = ({ plaintext, key }) => {
|
||||
let ciphertext, iv, tag;
|
||||
try {
|
||||
const obj = aes.encrypt(plaintext, key);
|
||||
ciphertext = obj.ciphertext;
|
||||
iv = obj.iv;
|
||||
tag = obj.tag;
|
||||
} catch (err) {
|
||||
console.log("Failed to perform encryption");
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
let ciphertext, iv, tag;
|
||||
try {
|
||||
const obj = aes.encrypt(plaintext, key);
|
||||
ciphertext = obj.ciphertext;
|
||||
iv = obj.iv;
|
||||
tag = obj.tag;
|
||||
} catch (err) {
|
||||
console.log("Failed to perform encryption");
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
};
|
||||
return {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -86,20 +86,20 @@ const encryptSymmetric = ({ plaintext, key }) => {
|
||||
*
|
||||
*/
|
||||
const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
|
||||
let plaintext;
|
||||
try {
|
||||
plaintext = aes.decrypt(ciphertext, iv, tag, key);
|
||||
} catch (err) {
|
||||
console.log("Failed to perform decryption");
|
||||
process.exit(1);
|
||||
}
|
||||
let plaintext;
|
||||
try {
|
||||
plaintext = aes.decrypt(ciphertext, iv, tag, key);
|
||||
} catch (err) {
|
||||
console.log("Failed to perform decryption");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
encryptAssymmetric,
|
||||
decryptAssymmetric,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric,
|
||||
encryptAssymmetric,
|
||||
decryptAssymmetric,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey";
|
||||
import SRP1 from "~/pages/api/auth/SRP1";
|
||||
|
||||
import Aes256Gcm from "./aes-256-gcm";
|
||||
import generateBackupPDF from "../generateBackupPDF";
|
||||
import Aes256Gcm from "./aes-256-gcm";
|
||||
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
@@ -21,82 +21,78 @@ const crypto = require("crypto");
|
||||
* @returns
|
||||
*/
|
||||
const issueBackupKey = async ({
|
||||
email,
|
||||
password,
|
||||
personalName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued,
|
||||
email,
|
||||
password,
|
||||
personalName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued,
|
||||
}) => {
|
||||
try {
|
||||
setBackupKeyError(false);
|
||||
setBackupKeyIssued(false);
|
||||
clientPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = clientPassword.getPublicKey();
|
||||
try {
|
||||
setBackupKeyError(false);
|
||||
setBackupKeyIssued(false);
|
||||
clientPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = clientPassword.getPublicKey();
|
||||
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await SRP1({
|
||||
clientPublicKey: clientPublicKey,
|
||||
});
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setBackupKeyError(true);
|
||||
console.log("Wrong current password", err, 1);
|
||||
}
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await SRP1({
|
||||
clientPublicKey: clientPublicKey,
|
||||
});
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setBackupKeyError(true);
|
||||
console.log("Wrong current password", err, 1);
|
||||
}
|
||||
|
||||
clientPassword.setSalt(salt);
|
||||
clientPassword.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = clientPassword.getProof(); // called M1
|
||||
clientPassword.setSalt(salt);
|
||||
clientPassword.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = clientPassword.getProof(); // called M1
|
||||
|
||||
const generatedKey = crypto.randomBytes(16).toString("hex");
|
||||
const generatedKey = crypto.randomBytes(16).toString("hex");
|
||||
|
||||
clientKey.init(
|
||||
{
|
||||
username: email,
|
||||
password: generatedKey,
|
||||
},
|
||||
async () => {
|
||||
clientKey.createVerifier(async (err, result) => {
|
||||
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||
localStorage.getItem("PRIVATE_KEY"),
|
||||
generatedKey
|
||||
);
|
||||
clientKey.init(
|
||||
{
|
||||
username: email,
|
||||
password: generatedKey,
|
||||
},
|
||||
async () => {
|
||||
clientKey.createVerifier(async (err, result) => {
|
||||
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||
localStorage.getItem("PRIVATE_KEY"),
|
||||
generatedKey
|
||||
);
|
||||
|
||||
const res = await issueBackupPrivateKey({
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
clientProof,
|
||||
});
|
||||
const res = await issueBackupPrivateKey({
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
clientProof,
|
||||
});
|
||||
|
||||
if (res.status == 400) {
|
||||
setBackupKeyError(true);
|
||||
} else if (res.status == 200) {
|
||||
generateBackupPDF(
|
||||
personalName,
|
||||
email,
|
||||
generatedKey
|
||||
);
|
||||
setBackupKeyIssued(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
setBackupKeyError(true);
|
||||
console.log("Failed to issue a backup key");
|
||||
}
|
||||
return true;
|
||||
if (res.status == 400) {
|
||||
setBackupKeyError(true);
|
||||
} else if (res.status == 200) {
|
||||
generateBackupPDF(personalName, email, generatedKey);
|
||||
setBackupKeyIssued(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
setBackupKeyError(true);
|
||||
console.log("Failed to issue a backup key");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export default issueBackupKey;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const LINE =
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
|
||||
/**
|
||||
* Return text that is the buffer parsed
|
||||
@@ -7,41 +7,41 @@ const LINE =
|
||||
* @returns {String} text - text of buffer
|
||||
*/
|
||||
function parse(src) {
|
||||
const obj = {};
|
||||
const obj = {};
|
||||
|
||||
// Convert buffer to string
|
||||
let lines = src.toString();
|
||||
// Convert buffer to string
|
||||
let lines = src.toString();
|
||||
|
||||
// Convert line breaks to same format
|
||||
lines = lines.replace(/\r\n?/gm, "\n");
|
||||
// Convert line breaks to same format
|
||||
lines = lines.replace(/\r\n?/gm, "\n");
|
||||
|
||||
let match;
|
||||
while ((match = LINE.exec(lines)) != null) {
|
||||
const key = match[1];
|
||||
let match;
|
||||
while ((match = LINE.exec(lines)) != null) {
|
||||
const key = match[1];
|
||||
|
||||
// Default undefined or null to empty string
|
||||
let value = match[2] || "";
|
||||
// Default undefined or null to empty string
|
||||
let value = match[2] || "";
|
||||
|
||||
// Remove whitespace
|
||||
value = value.trim();
|
||||
// Remove whitespace
|
||||
value = value.trim();
|
||||
|
||||
// Check if double quoted
|
||||
const maybeQuote = value[0];
|
||||
// Check if double quoted
|
||||
const maybeQuote = value[0];
|
||||
|
||||
// Remove surrounding quotes
|
||||
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
||||
// Remove surrounding quotes
|
||||
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
||||
|
||||
// Expand newlines if double quoted
|
||||
if (maybeQuote === '"') {
|
||||
value = value.replace(/\\n/g, "\n");
|
||||
value = value.replace(/\\r/g, "\r");
|
||||
}
|
||||
// Expand newlines if double quoted
|
||||
if (maybeQuote === '"') {
|
||||
value = value.replace(/\\n/g, "\n");
|
||||
value = value.replace(/\\r/g, "\r");
|
||||
}
|
||||
|
||||
// Add to object
|
||||
obj[key] = value;
|
||||
}
|
||||
// Add to object
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
export default parse;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,23 +3,23 @@
|
||||
* @returns
|
||||
*/
|
||||
const guidGenerator = () => {
|
||||
var S4 = function () {
|
||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
};
|
||||
return (
|
||||
S4() +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
S4() +
|
||||
S4()
|
||||
);
|
||||
var S4 = function () {
|
||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
};
|
||||
return (
|
||||
S4() +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
"-" +
|
||||
S4() +
|
||||
S4() +
|
||||
S4()
|
||||
);
|
||||
};
|
||||
|
||||
export default guidGenerator;
|
||||
|
||||
@@ -3,103 +3,101 @@ import getSecrets from "~/pages/api/files/GetSecrets";
|
||||
import guidGenerator from "../randomId";
|
||||
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric,
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric,
|
||||
} = require("../cryptography/crypto");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
|
||||
const envMapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
};
|
||||
|
||||
const getSecretsForProject = async ({
|
||||
env,
|
||||
setFileState,
|
||||
setIsKeyAvailable,
|
||||
setData,
|
||||
workspaceId,
|
||||
env,
|
||||
setFileState,
|
||||
setIsKeyAvailable,
|
||||
setData,
|
||||
workspaceId,
|
||||
}) => {
|
||||
try {
|
||||
let file;
|
||||
try {
|
||||
file = await getSecrets(workspaceId, envMapping[env]);
|
||||
try {
|
||||
let file;
|
||||
try {
|
||||
file = await getSecrets(workspaceId, envMapping[env]);
|
||||
|
||||
setFileState(file);
|
||||
} catch (error) {
|
||||
console.log("ERROR: Not able to access the latest file");
|
||||
}
|
||||
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
|
||||
setIsKeyAvailable(
|
||||
!file.key ? (file.secrets.length == 0 ? true : false) : true
|
||||
);
|
||||
setFileState(file);
|
||||
} catch (error) {
|
||||
console.log("ERROR: Not able to access the latest file");
|
||||
}
|
||||
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
|
||||
setIsKeyAvailable(
|
||||
!file.key ? (file.secrets.length == 0 ? true : false) : true
|
||||
);
|
||||
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
let tempFileState = [];
|
||||
if (file.key) {
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: file.key.encryptedKey,
|
||||
nonce: file.key.nonce,
|
||||
publicKey: file.key.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
let tempFileState = [];
|
||||
if (file.key) {
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: file.key.encryptedKey,
|
||||
nonce: file.key.nonce,
|
||||
publicKey: file.key.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
file.secrets.map((secretPair) => {
|
||||
// decrypt .env file with symmetric key
|
||||
const plainTextKey = decryptSymmetric({
|
||||
ciphertext: secretPair.secretKey.ciphertext,
|
||||
iv: secretPair.secretKey.iv,
|
||||
tag: secretPair.secretKey.tag,
|
||||
key,
|
||||
});
|
||||
file.secrets.map((secretPair) => {
|
||||
// decrypt .env file with symmetric key
|
||||
const plainTextKey = decryptSymmetric({
|
||||
ciphertext: secretPair.secretKey.ciphertext,
|
||||
iv: secretPair.secretKey.iv,
|
||||
tag: secretPair.secretKey.tag,
|
||||
key,
|
||||
});
|
||||
|
||||
const plainTextValue = decryptSymmetric({
|
||||
ciphertext: secretPair.secretValue.ciphertext,
|
||||
iv: secretPair.secretValue.iv,
|
||||
tag: secretPair.secretValue.tag,
|
||||
key,
|
||||
});
|
||||
tempFileState.push({
|
||||
key: plainTextKey,
|
||||
value: plainTextValue,
|
||||
type: secretPair.type,
|
||||
});
|
||||
});
|
||||
}
|
||||
setFileState(tempFileState);
|
||||
const plainTextValue = decryptSymmetric({
|
||||
ciphertext: secretPair.secretValue.ciphertext,
|
||||
iv: secretPair.secretValue.iv,
|
||||
tag: secretPair.secretValue.tag,
|
||||
key,
|
||||
});
|
||||
tempFileState.push({
|
||||
key: plainTextKey,
|
||||
value: plainTextValue,
|
||||
type: secretPair.type,
|
||||
});
|
||||
});
|
||||
}
|
||||
setFileState(tempFileState);
|
||||
|
||||
setData(
|
||||
tempFileState.map((line, index) => [
|
||||
guidGenerator(),
|
||||
index,
|
||||
line["key"],
|
||||
line["value"],
|
||||
line["type"],
|
||||
])
|
||||
// .sort((a, b) =>
|
||||
// sortMethod == "alphabetical"
|
||||
// ? a[2].localeCompare(b[2])
|
||||
// : b[2].localeCompare(a[2])
|
||||
// )
|
||||
);
|
||||
return tempFileState.map((line, index) => [
|
||||
guidGenerator(),
|
||||
index,
|
||||
line["key"],
|
||||
line["value"],
|
||||
line["type"],
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Something went wrong during accessing or decripting secrets."
|
||||
);
|
||||
}
|
||||
return true;
|
||||
setData(
|
||||
tempFileState.map((line, index) => [
|
||||
guidGenerator(),
|
||||
index,
|
||||
line["key"],
|
||||
line["value"],
|
||||
line["type"],
|
||||
])
|
||||
// .sort((a, b) =>
|
||||
// sortMethod == "alphabetical"
|
||||
// ? a[2].localeCompare(b[2])
|
||||
// : b[2].localeCompare(a[2])
|
||||
// )
|
||||
);
|
||||
return tempFileState.map((line, index) => [
|
||||
guidGenerator(),
|
||||
index,
|
||||
line["key"],
|
||||
line["value"],
|
||||
line["type"],
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log("Something went wrong during accessing or decripting secrets.");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export default getSecretsForProject;
|
||||
|
||||
@@ -4,106 +4,103 @@ import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys";
|
||||
|
||||
const crypto = require("crypto");
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric,
|
||||
encryptSymmetric,
|
||||
encryptAssymmetric,
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric,
|
||||
encryptSymmetric,
|
||||
encryptAssymmetric,
|
||||
} = require("../cryptography/crypto");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
|
||||
const envMapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
};
|
||||
|
||||
const pushKeys = async (obj, workspaceId, env) => {
|
||||
let sharedKey = await getLatestFileKey(workspaceId);
|
||||
let sharedKey = await getLatestFileKey(workspaceId);
|
||||
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
let randomBytes;
|
||||
if (Object.keys(sharedKey).length > 0) {
|
||||
// case: a (shared) key exists for the workspace
|
||||
randomBytes = decryptAssymmetric({
|
||||
ciphertext: sharedKey.latestKey.encryptedKey,
|
||||
nonce: sharedKey.latestKey.nonce,
|
||||
publicKey: sharedKey.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
} else {
|
||||
// case: a (shared) key does not exist for the workspace
|
||||
randomBytes = crypto.randomBytes(16).toString("hex");
|
||||
}
|
||||
let randomBytes;
|
||||
if (Object.keys(sharedKey).length > 0) {
|
||||
// case: a (shared) key exists for the workspace
|
||||
randomBytes = decryptAssymmetric({
|
||||
ciphertext: sharedKey.latestKey.encryptedKey,
|
||||
nonce: sharedKey.latestKey.nonce,
|
||||
publicKey: sharedKey.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
} else {
|
||||
// case: a (shared) key does not exist for the workspace
|
||||
randomBytes = crypto.randomBytes(16).toString("hex");
|
||||
}
|
||||
|
||||
const secrets = Object.keys(obj).map((key) => {
|
||||
// encrypt key
|
||||
const {
|
||||
ciphertext: ciphertextKey,
|
||||
iv: ivKey,
|
||||
tag: tagKey,
|
||||
} = encryptSymmetric({
|
||||
plaintext: key,
|
||||
key: randomBytes,
|
||||
});
|
||||
const secrets = Object.keys(obj).map((key) => {
|
||||
// encrypt key
|
||||
const {
|
||||
ciphertext: ciphertextKey,
|
||||
iv: ivKey,
|
||||
tag: tagKey,
|
||||
} = encryptSymmetric({
|
||||
plaintext: key,
|
||||
key: randomBytes,
|
||||
});
|
||||
|
||||
// encrypt value
|
||||
const {
|
||||
ciphertext: ciphertextValue,
|
||||
iv: ivValue,
|
||||
tag: tagValue,
|
||||
} = encryptSymmetric({
|
||||
plaintext: obj[key][0],
|
||||
key: randomBytes,
|
||||
});
|
||||
// encrypt value
|
||||
const {
|
||||
ciphertext: ciphertextValue,
|
||||
iv: ivValue,
|
||||
tag: tagValue,
|
||||
} = encryptSymmetric({
|
||||
plaintext: obj[key][0],
|
||||
key: randomBytes,
|
||||
});
|
||||
|
||||
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
|
||||
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
|
||||
|
||||
return {
|
||||
ciphertextKey,
|
||||
ivKey,
|
||||
tagKey,
|
||||
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
||||
ciphertextValue,
|
||||
ivValue,
|
||||
tagValue,
|
||||
hashValue: crypto
|
||||
.createHash("sha256")
|
||||
.update(obj[key][0])
|
||||
.digest("hex"),
|
||||
type: visibility,
|
||||
};
|
||||
});
|
||||
return {
|
||||
ciphertextKey,
|
||||
ivKey,
|
||||
tagKey,
|
||||
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
||||
ciphertextValue,
|
||||
ivValue,
|
||||
tagValue,
|
||||
hashValue: crypto.createHash("sha256").update(obj[key][0]).digest("hex"),
|
||||
type: visibility,
|
||||
};
|
||||
});
|
||||
|
||||
// obtain public keys of all receivers (i.e. members in workspace)
|
||||
const publicKeys = await getWorkspaceKeys({
|
||||
workspaceId,
|
||||
});
|
||||
// obtain public keys of all receivers (i.e. members in workspace)
|
||||
const publicKeys = await getWorkspaceKeys({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
// assymmetrically encrypt key with each receiver public keys
|
||||
const keys = publicKeys.map((k) => {
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: k.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
// assymmetrically encrypt key with each receiver public keys
|
||||
const keys = publicKeys.map((k) => {
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: k.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
return {
|
||||
encryptedKey: ciphertext,
|
||||
nonce,
|
||||
userId: k.userId,
|
||||
};
|
||||
});
|
||||
return {
|
||||
encryptedKey: ciphertext,
|
||||
nonce,
|
||||
userId: k.userId,
|
||||
};
|
||||
});
|
||||
|
||||
// send payload
|
||||
await uploadSecrets({
|
||||
workspaceId,
|
||||
secrets,
|
||||
keys,
|
||||
environment: envMapping[env],
|
||||
});
|
||||
// send payload
|
||||
await uploadSecrets({
|
||||
workspaceId,
|
||||
secrets,
|
||||
keys,
|
||||
environment: envMapping[env],
|
||||
});
|
||||
};
|
||||
|
||||
export default pushKeys;
|
||||
|
||||
@@ -2,73 +2,73 @@ import publicKeyInfical from "~/pages/api/auth/publicKeyInfisical";
|
||||
import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars";
|
||||
|
||||
const crypto = require("crypto");
|
||||
const { encryptSymmetric, encryptAssymmetric } = require("../cryptography/crypto");
|
||||
const {
|
||||
encryptSymmetric,
|
||||
encryptAssymmetric,
|
||||
} = require("../cryptography/crypto");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
|
||||
const pushKeysIntegration = async ({ obj, integrationId }) => {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
let randomBytes = crypto.randomBytes(16).toString("hex");
|
||||
let randomBytes = crypto.randomBytes(16).toString("hex");
|
||||
|
||||
const secrets = Object.keys(obj).map((key) => {
|
||||
// encrypt key
|
||||
const {
|
||||
ciphertext: ciphertextKey,
|
||||
iv: ivKey,
|
||||
tag: tagKey,
|
||||
} = encryptSymmetric({
|
||||
plaintext: key,
|
||||
key: randomBytes,
|
||||
});
|
||||
const secrets = Object.keys(obj).map((key) => {
|
||||
// encrypt key
|
||||
const {
|
||||
ciphertext: ciphertextKey,
|
||||
iv: ivKey,
|
||||
tag: tagKey,
|
||||
} = encryptSymmetric({
|
||||
plaintext: key,
|
||||
key: randomBytes,
|
||||
});
|
||||
|
||||
// encrypt value
|
||||
const {
|
||||
ciphertext: ciphertextValue,
|
||||
iv: ivValue,
|
||||
tag: tagValue,
|
||||
} = encryptSymmetric({
|
||||
plaintext: obj[key],
|
||||
key: randomBytes,
|
||||
});
|
||||
// encrypt value
|
||||
const {
|
||||
ciphertext: ciphertextValue,
|
||||
iv: ivValue,
|
||||
tag: tagValue,
|
||||
} = encryptSymmetric({
|
||||
plaintext: obj[key],
|
||||
key: randomBytes,
|
||||
});
|
||||
|
||||
const visibility = "shared";
|
||||
const visibility = "shared";
|
||||
|
||||
return {
|
||||
ciphertextKey,
|
||||
ivKey,
|
||||
tagKey,
|
||||
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
||||
ciphertextValue,
|
||||
ivValue,
|
||||
tagValue,
|
||||
hashValue: crypto
|
||||
.createHash("sha256")
|
||||
.update(obj[key])
|
||||
.digest("hex"),
|
||||
type: visibility,
|
||||
};
|
||||
});
|
||||
return {
|
||||
ciphertextKey,
|
||||
ivKey,
|
||||
tagKey,
|
||||
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
||||
ciphertextValue,
|
||||
ivValue,
|
||||
tagValue,
|
||||
hashValue: crypto.createHash("sha256").update(obj[key]).digest("hex"),
|
||||
type: visibility,
|
||||
};
|
||||
});
|
||||
|
||||
// obtain public keys of all receivers (i.e. members in workspace)
|
||||
let publicKeyInfisical = await publicKeyInfical();
|
||||
// obtain public keys of all receivers (i.e. members in workspace)
|
||||
let publicKeyInfisical = await publicKeyInfical();
|
||||
|
||||
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
|
||||
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
|
||||
|
||||
// assymmetrically encrypt key with each receiver public keys
|
||||
// assymmetrically encrypt key with each receiver public keys
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: publicKeyInfisical,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: publicKeyInfisical,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const key = {
|
||||
encryptedKey: ciphertext,
|
||||
nonce,
|
||||
};
|
||||
const key = {
|
||||
encryptedKey: ciphertext,
|
||||
nonce,
|
||||
};
|
||||
|
||||
changeHerokuConfigVars({ integrationId, key, secrets });
|
||||
changeHerokuConfigVars({ integrationId, key, secrets });
|
||||
};
|
||||
|
||||
export default pushKeysIntegration;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
export const PATH = process.env.NEXT_PUBLIC_WEBSITE_URL;
|
||||
|
||||
export const publicPaths = [
|
||||
`/`,
|
||||
// `/integrations`,
|
||||
`/signupinvite`,
|
||||
`/pricing`,
|
||||
`/signup`,
|
||||
`/login`,
|
||||
`/blog`,
|
||||
`/docs`,
|
||||
`/changelog`,
|
||||
`/security`,
|
||||
`/scheduledemo`,
|
||||
`/blog/[slug]`,
|
||||
`/faq`,
|
||||
`/privacy`,
|
||||
`/terms`,
|
||||
`/subprocessors`,
|
||||
`/`,
|
||||
// `/integrations`,
|
||||
`/signupinvite`,
|
||||
`/pricing`,
|
||||
`/signup`,
|
||||
`/login`,
|
||||
`/blog`,
|
||||
`/docs`,
|
||||
`/changelog`,
|
||||
`/security`,
|
||||
`/scheduledemo`,
|
||||
`/blog/[slug]`,
|
||||
`/faq`,
|
||||
`/privacy`,
|
||||
`/terms`,
|
||||
`/subprocessors`,
|
||||
];
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
const ContentSecurityPolicy = `
|
||||
default-src ${process.env.NEXT_PUBLIC_WEBSITE_URL};
|
||||
script-src ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
|
||||
script-src ${
|
||||
process.env.NEXT_PUBLIC_WEBSITE_URL
|
||||
} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
|
||||
style-src 'self' https://rsms.me 'unsafe-inline';
|
||||
child-src https://infisical.com https://api.stripe.com;
|
||||
frame-src https://js.stripe.com/ https://api.stripe.com;
|
||||
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split('//')[1]} ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
|
||||
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split("//")[1]} ${
|
||||
process.env.NEXT_PUBLIC_WEBSITE_URL
|
||||
} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
|
||||
img-src 'self' https://*.stripe.com https://i.ytimg.com/ data:;
|
||||
media-src;
|
||||
font-src 'self' https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
|
||||
@@ -15,49 +19,49 @@ const ContentSecurityPolicy = `
|
||||
// You can choose which headers to add to the list
|
||||
// after learning more below.
|
||||
const securityHeaders = [
|
||||
{
|
||||
key: "X-DNS-Prefetch-Control",
|
||||
value: "on",
|
||||
},
|
||||
{
|
||||
key: "Strict-Transport-Security",
|
||||
value: "max-age=63072000; includeSubDomains; preload",
|
||||
},
|
||||
{
|
||||
key: "X-XSS-Protection",
|
||||
value: "1; mode=block",
|
||||
},
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "SAMEORIGIN",
|
||||
},
|
||||
{
|
||||
key: "Permissions-Policy",
|
||||
value: "camera=(), microphone=()",
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin-when-cross-origin",
|
||||
},
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
|
||||
},
|
||||
{
|
||||
key: "X-DNS-Prefetch-Control",
|
||||
value: "on",
|
||||
},
|
||||
{
|
||||
key: "Strict-Transport-Security",
|
||||
value: "max-age=63072000; includeSubDomains; preload",
|
||||
},
|
||||
{
|
||||
key: "X-XSS-Protection",
|
||||
value: "1; mode=block",
|
||||
},
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "SAMEORIGIN",
|
||||
},
|
||||
{
|
||||
key: "Permissions-Policy",
|
||||
value: "camera=(), microphone=()",
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin-when-cross-origin",
|
||||
},
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
output: 'standalone',
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
// Apply these headers to all routes in your application.
|
||||
source: "/:path*",
|
||||
headers: securityHeaders,
|
||||
},
|
||||
];
|
||||
},
|
||||
output: "standalone",
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
// Apply these headers to all routes in your application.
|
||||
source: "/:path*",
|
||||
headers: securityHeaders,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start:docker": "next build && next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/server": "^11.10.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
||||
"@fortawesome/react-fontawesome": "^0.1.19",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.35.0",
|
||||
"add": "^2.0.6",
|
||||
"axios": "^0.27.2",
|
||||
"axios-auth-refresh": "^3.3.3",
|
||||
"classnames": "^2.3.1",
|
||||
"cookies": "^0.8.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"gray-matter": "^4.0.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jsrp": "^0.2.4",
|
||||
"markdown-it": "^13.0.1",
|
||||
"next": "^12.2.5",
|
||||
"posthog-js": "^1.34.0",
|
||||
"query-string": "^7.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-code-input": "^3.10.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-mailchimp-subscribe": "^2.1.3",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-table": "^7.8.0",
|
||||
"set-cookie-parser": "^2.5.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"uuid": "^8.3.2",
|
||||
"uuidv4": "^6.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-next": "^13.0.5",
|
||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "2.7.1",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"typescript": "^4.9.3"
|
||||
}
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start:docker": "next build && next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/server": "^11.10.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
||||
"@fortawesome/react-fontawesome": "^0.1.19",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.35.0",
|
||||
"add": "^2.0.6",
|
||||
"axios": "^0.27.2",
|
||||
"axios-auth-refresh": "^3.3.3",
|
||||
"classnames": "^2.3.1",
|
||||
"cookies": "^0.8.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"gray-matter": "^4.0.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jsrp": "^0.2.4",
|
||||
"markdown-it": "^13.0.1",
|
||||
"next": "^12.2.5",
|
||||
"posthog-js": "^1.34.0",
|
||||
"query-string": "^7.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-code-input": "^3.10.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-mailchimp-subscribe": "^2.1.3",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-table": "^7.8.0",
|
||||
"set-cookie-parser": "^2.5.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"uuid": "^8.3.2",
|
||||
"uuidv4": "^6.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-next": "^13.0.5",
|
||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "2.7.1",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"typescript": "^4.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
import { initPostHog } from "~/components/analytics/posthog";
|
||||
import Layout from "~/components/basic/layout";
|
||||
import RouteGuard from "~/components/RouteGuard";
|
||||
import { ENV } from "~/utilities/config";
|
||||
|
||||
import { publicPaths } from "~/const";
|
||||
import { ENV } from "~/utilities/config";
|
||||
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
import "../styles/globals.css";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the second step of the change password process (pake)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||
|
||||
/**
|
||||
* This function is used to check if the user is authenticated.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the route that issues a backup private key that will afterwards be added into a pdf
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route logs the user out. Note: the user should authorized to do this.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the first step of the change password process (pake)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||
|
||||
/**
|
||||
* This function fetches the encrypted secrets from the .env file
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function uploads the encrypted .env file
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
|
||||
return SecurityClient.fetchCall(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes an integration from a certain project
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes an integration authorization from a certain project
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
const getIntegrationApps = ({ integrationAuthId }) => {
|
||||
return SecurityClient.fetchCall(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
const getIntegrations = () => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route starts the integration after teh default one if gonna set up.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the first step of the change password process (pake)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets authorizations of a certain project (Heroku, etc.)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets integrations of a certain project (Heroku, etc.)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get info about a certain org
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the users in an org.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the current subscription of an org.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the projects of a certain user in an org.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the users in an org.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route redirects the user to the right stripe billing page.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route add an incident contact email to a certain organization
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function sends an email invite to a user to join an org
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes an incident Contact from a certain organization
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function removes a certain member from a certain organization
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This routes gets all the incident contacts of a certain organization
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the all the orgs of a certain user.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us rename a certain org.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets service tokens for a specific user in a project
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets service tokens for a specific user in a project
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets the information about a specific user.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route registers a certain action for a user
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route registers a certain action for a user
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function adds a user to a project
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function change the access of a user in a certain workspace
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route creates a new workspace for a user.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function removes a certain member from a certain workspace
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes a specified workspace.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* Get the latest key pairs from a certain workspace
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the information of a certain project.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the public keys of everyone in your workspace.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the users in the workspace.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the public keys of everyone in your workspace.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us rename a certain workspace.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route uplods the keys in an encrypted format.
|
||||
|
||||
@@ -5,31 +5,29 @@ import { useRouter } from "next/router";
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function DashboardRedirect() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push(
|
||||
"/dashboard/" + localStorage.getItem("projectData.id")
|
||||
);
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
DashboardRedirect.requireAuth = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,34 +5,32 @@ const queryString = require("query-string");
|
||||
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||
|
||||
export default function Heroku() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state == localStorage.getItem("latestCSRFToken")) {
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
code,
|
||||
integration: "heroku",
|
||||
});
|
||||
router.push(
|
||||
"/integrations/" + localStorage.getItem("projectData.id")
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state == localStorage.getItem("latestCSRFToken")) {
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
code,
|
||||
integration: "heroku",
|
||||
});
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Heroku.requireAuth = true;
|
||||
|
||||
@@ -2,12 +2,12 @@ import React, { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
router.push("/login");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
router.push("/login");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div className="bg-bunker-800 w-screen"></div>;
|
||||
return <div className="bg-bunker-800 w-screen"></div>;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useEffect,useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCheck,
|
||||
faRotate,
|
||||
faX,
|
||||
faArrowRight,
|
||||
faCheck,
|
||||
faRotate,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
@@ -28,408 +28,345 @@ import startIntegration from "../api/integrations/StartIntegration";
|
||||
const crypto = require("crypto");
|
||||
|
||||
const envMapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
};
|
||||
|
||||
const reverseEnvMapping = {
|
||||
dev: "Development",
|
||||
staging: "Staging",
|
||||
prod: "Production",
|
||||
test: "Testing",
|
||||
dev: "Development",
|
||||
staging: "Staging",
|
||||
prod: "Production",
|
||||
test: "Testing",
|
||||
};
|
||||
|
||||
const Integration = ({ projectIntegration }) => {
|
||||
const [integrationEnvironment, setIntegrationEnvironment] = useState(
|
||||
reverseEnvMapping[projectIntegration.environment]
|
||||
);
|
||||
const [fileState, setFileState] = useState([]);
|
||||
const [data, setData] = useState();
|
||||
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
||||
const router = useRouter();
|
||||
const [apps, setApps] = useState([]);
|
||||
const [integrationApp, setIntegrationApp] = useState(
|
||||
projectIntegration.app ? projectIntegration.app : apps[0]
|
||||
);
|
||||
const [integrationEnvironment, setIntegrationEnvironment] = useState(
|
||||
reverseEnvMapping[projectIntegration.environment]
|
||||
);
|
||||
const [fileState, setFileState] = useState([]);
|
||||
const [data, setData] = useState();
|
||||
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
||||
const router = useRouter();
|
||||
const [apps, setApps] = useState([]);
|
||||
const [integrationApp, setIntegrationApp] = useState(
|
||||
projectIntegration.app ? projectIntegration.app : apps[0]
|
||||
);
|
||||
|
||||
useEffect(async () => {
|
||||
const tempHerokuApps = await getIntegrationApps({
|
||||
integrationAuthId: projectIntegration.integrationAuth,
|
||||
});
|
||||
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
|
||||
setApps(tempHerokuAppNames);
|
||||
setIntegrationApp(
|
||||
projectIntegration.app
|
||||
? projectIntegration.app
|
||||
: tempHerokuAppNames[0]
|
||||
);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
const tempHerokuApps = await getIntegrationApps({
|
||||
integrationAuthId: projectIntegration.integrationAuth,
|
||||
});
|
||||
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
|
||||
setApps(tempHerokuAppNames);
|
||||
setIntegrationApp(
|
||||
projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0]
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
|
||||
<div className="flex flex-row">
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
ENVIRONMENT
|
||||
</div>
|
||||
<ListBox
|
||||
data={
|
||||
!projectIntegration.isActive && [
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
]
|
||||
}
|
||||
selected={integrationEnvironment}
|
||||
onChange={setIntegrationEnvironment}
|
||||
/>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="mx-4 text-gray-400 mt-8"
|
||||
/>
|
||||
<div className="mr-2">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
INTEGRATION
|
||||
</div>
|
||||
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
|
||||
{projectIntegration.integration
|
||||
.charAt(0)
|
||||
.toUpperCase() +
|
||||
projectIntegration.integration.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
HEROKU APP
|
||||
</div>
|
||||
<ListBox
|
||||
data={!projectIntegration.isActive && apps}
|
||||
selected={integrationApp}
|
||||
onChange={setIntegrationApp}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row mt-6">
|
||||
{projectIntegration.isActive ? (
|
||||
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faRotate}
|
||||
className="text-lg mr-2.5 text-primary animate-spin"
|
||||
/>
|
||||
<div className="text-gray-300 font-semibold">
|
||||
In Sync
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
text="Start Integration"
|
||||
onButtonPressed={async () => {
|
||||
const result = await startIntegration({
|
||||
integrationId: projectIntegration._id,
|
||||
environment:
|
||||
envMapping[integrationEnvironment],
|
||||
appName: integrationApp,
|
||||
});
|
||||
if (result?.status == 200) {
|
||||
let currentSecrets =
|
||||
await getSecretsForProject({
|
||||
env: integrationEnvironment,
|
||||
setFileState,
|
||||
setIsKeyAvailable,
|
||||
setData,
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
return (
|
||||
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
|
||||
<div className="flex flex-row">
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
ENVIRONMENT
|
||||
</div>
|
||||
<ListBox
|
||||
data={
|
||||
!projectIntegration.isActive && [
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
]
|
||||
}
|
||||
selected={integrationEnvironment}
|
||||
onChange={setIntegrationEnvironment}
|
||||
/>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="mx-4 text-gray-400 mt-8"
|
||||
/>
|
||||
<div className="mr-2">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
INTEGRATION
|
||||
</div>
|
||||
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
|
||||
{projectIntegration.integration.charAt(0).toUpperCase() +
|
||||
projectIntegration.integration.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
HEROKU APP
|
||||
</div>
|
||||
<ListBox
|
||||
data={!projectIntegration.isActive && apps}
|
||||
selected={integrationApp}
|
||||
onChange={setIntegrationApp}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row mt-6">
|
||||
{projectIntegration.isActive ? (
|
||||
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faRotate}
|
||||
className="text-lg mr-2.5 text-primary animate-spin"
|
||||
/>
|
||||
<div className="text-gray-300 font-semibold">In Sync</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
text="Start Integration"
|
||||
onButtonPressed={async () => {
|
||||
const result = await startIntegration({
|
||||
integrationId: projectIntegration._id,
|
||||
environment: envMapping[integrationEnvironment],
|
||||
appName: integrationApp,
|
||||
});
|
||||
if (result?.status == 200) {
|
||||
let currentSecrets = await getSecretsForProject({
|
||||
env: integrationEnvironment,
|
||||
setFileState,
|
||||
setIsKeyAvailable,
|
||||
setData,
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
|
||||
let obj = Object.assign(
|
||||
{},
|
||||
...currentSecrets.map((row) => ({
|
||||
[row[2]]: row[3],
|
||||
}))
|
||||
);
|
||||
await pushKeysIntegration({
|
||||
obj,
|
||||
integrationId: projectIntegration._id,
|
||||
});
|
||||
router.reload();
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
|
||||
<Button
|
||||
onButtonPressed={async () => {
|
||||
await deleteIntegration({
|
||||
integrationId: projectIntegration._id,
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
color="red"
|
||||
size="icon-md"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
let obj = Object.assign(
|
||||
{},
|
||||
...currentSecrets.map((row) => ({
|
||||
[row[2]]: row[3],
|
||||
}))
|
||||
);
|
||||
await pushKeysIntegration({
|
||||
obj,
|
||||
integrationId: projectIntegration._id,
|
||||
});
|
||||
router.reload();
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
|
||||
<Button
|
||||
onButtonPressed={async () => {
|
||||
await deleteIntegration({
|
||||
integrationId: projectIntegration._id,
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
color="red"
|
||||
size="icon-md"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Integrations() {
|
||||
const [integrations, setIntegrations] = useState();
|
||||
const [projectIntegrations, setProjectIntegrations] = useState();
|
||||
const [authorizations, setAuthorizations] = useState();
|
||||
const router = useRouter();
|
||||
const [csrfToken, setCsrfToken] = useState("");
|
||||
const [integrations, setIntegrations] = useState();
|
||||
const [projectIntegrations, setProjectIntegrations] = useState();
|
||||
const [authorizations, setAuthorizations] = useState();
|
||||
const router = useRouter();
|
||||
const [csrfToken, setCsrfToken] = useState("");
|
||||
|
||||
useEffect(async () => {
|
||||
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
|
||||
setCsrfToken(tempCSRFToken);
|
||||
localStorage.setItem("latestCSRFToken", tempCSRFToken);
|
||||
useEffect(async () => {
|
||||
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
|
||||
setCsrfToken(tempCSRFToken);
|
||||
localStorage.setItem("latestCSRFToken", tempCSRFToken);
|
||||
|
||||
let projectAuthorizations = await getWorkspaceAuthorizations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setAuthorizations(projectAuthorizations);
|
||||
let projectAuthorizations = await getWorkspaceAuthorizations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setAuthorizations(projectAuthorizations);
|
||||
|
||||
const projectIntegrations = await getWorkspaceIntegrations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setProjectIntegrations(projectIntegrations);
|
||||
const projectIntegrations = await getWorkspaceIntegrations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setProjectIntegrations(projectIntegrations);
|
||||
|
||||
try {
|
||||
const integrationsData = await getIntegrations();
|
||||
setIntegrations(integrationsData);
|
||||
} catch (error) {
|
||||
console.log("Error", error);
|
||||
}
|
||||
}, []);
|
||||
try {
|
||||
const integrationsData = await getIntegrations();
|
||||
setIntegrations(integrationsData);
|
||||
} catch (error) {
|
||||
console.log("Error", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return integrations ? (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Dashboard</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Manage your .env files in seconds"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
/>
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<NavHeader
|
||||
pageName="Project Integrations"
|
||||
isProjectRelated={true}
|
||||
/>
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">
|
||||
Current Project Integrations
|
||||
</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Manage your integrations of Infisical with
|
||||
third-party services.
|
||||
</p>
|
||||
</div>
|
||||
{projectIntegrations.length > 0 ? (
|
||||
projectIntegrations.map((projectIntegration) => (
|
||||
<Integration
|
||||
key={guidGenerator()}
|
||||
projectIntegration={projectIntegration}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
|
||||
<div className="mb-1">
|
||||
You {"don't"} have any integrations set up
|
||||
yet. When you do, they will appear here.
|
||||
</div>
|
||||
<div className="">
|
||||
To start, click on any of the options below.
|
||||
It takes 5 clicks to set up.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">
|
||||
Available Integrations
|
||||
</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Click on the itegration you want to connect. This
|
||||
will let your environment variables flow
|
||||
automatically into selected third-party services.
|
||||
</p>
|
||||
<p className="mr-4 text-xs text-gray-600 mt-1">
|
||||
Note: during an integration with Heroku, for
|
||||
security reasons, it is impossible to maintain
|
||||
end-to-end encryption. In theory, this lets
|
||||
Infisical decrypt yor environment variables. In
|
||||
practice, we can assure you that this will never be
|
||||
done, and it allows us to protect your secrets from
|
||||
bad actors online. The core Infisical service will
|
||||
always stay end-to-end encrypted. With any
|
||||
questions, reach out support@infisical.com.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
|
||||
{Object.keys(integrations).map((integration) => (
|
||||
<div
|
||||
className={`relative ${
|
||||
["Heroku"].includes(
|
||||
integrations[integration].name
|
||||
)
|
||||
? ""
|
||||
: "opacity-50"
|
||||
}`}
|
||||
key={integrations[integration].name}
|
||||
>
|
||||
<a
|
||||
href={`${
|
||||
["Heroku"].includes(
|
||||
integrations[integration].name
|
||||
)
|
||||
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
|
||||
: "#"
|
||||
}`}
|
||||
rel="noopener"
|
||||
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
|
||||
["Heroku"].includes(
|
||||
integrations[integration].name
|
||||
)
|
||||
? "hover:bg-white/10 duration-200 cursor-pointer"
|
||||
: "cursor-default grayscale"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={`/images/integrations/${integrations[integration].name}.png`}
|
||||
height={100}
|
||||
width={100}
|
||||
alt="integration logo"
|
||||
></Image>
|
||||
{integrations[integration].name.split(" ")
|
||||
.length > 2 ? (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
|
||||
<div>
|
||||
{
|
||||
integrations[
|
||||
integration
|
||||
].name.split(" ")[0]
|
||||
}
|
||||
</div>
|
||||
<div className="text-base">
|
||||
{
|
||||
integrations[
|
||||
integration
|
||||
].name.split(" ")[1]
|
||||
}{" "}
|
||||
{
|
||||
integrations[
|
||||
integration
|
||||
].name.split(" ")[2]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
|
||||
{integrations[integration].name}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
{["Heroku"].includes(
|
||||
integrations[integration].name
|
||||
) &&
|
||||
authorizations
|
||||
.map(
|
||||
(authorization) =>
|
||||
authorization.integration
|
||||
)
|
||||
.includes(
|
||||
integrations[
|
||||
integration
|
||||
].name.toLowerCase()
|
||||
) && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div
|
||||
onClick={() => {
|
||||
deleteIntegrationAuth({
|
||||
integrationAuthId:
|
||||
authorizations
|
||||
.filter(
|
||||
(
|
||||
authorization
|
||||
) =>
|
||||
authorization.integration ==
|
||||
integrations[
|
||||
integration
|
||||
].name.toLowerCase()
|
||||
)
|
||||
.map(
|
||||
(
|
||||
authorization
|
||||
) =>
|
||||
authorization._id
|
||||
)[0],
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-xs mr-2 py-px"
|
||||
/>
|
||||
Revoke
|
||||
</div>
|
||||
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-xs mr-2"
|
||||
/>
|
||||
Authorized
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!["Heroku"].includes(
|
||||
integrations[integration].name
|
||||
) && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
|
||||
Coming Soon
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
return integrations ? (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Dashboard</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
/>
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<NavHeader pageName="Project Integrations" isProjectRelated={true} />
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">Current Project Integrations</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Manage your integrations of Infisical with third-party services.
|
||||
</p>
|
||||
</div>
|
||||
{projectIntegrations.length > 0 ? (
|
||||
projectIntegrations.map((projectIntegration) => (
|
||||
<Integration
|
||||
key={guidGenerator()}
|
||||
projectIntegration={projectIntegration}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
|
||||
<div className="mb-1">
|
||||
You {"don't"} have any integrations set up yet. When you do,
|
||||
they will appear here.
|
||||
</div>
|
||||
<div className="">
|
||||
To start, click on any of the options below. It takes 5 clicks
|
||||
to set up.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">Available Integrations</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Click on the itegration you want to connect. This will let your
|
||||
environment variables flow automatically into selected third-party
|
||||
services.
|
||||
</p>
|
||||
<p className="mr-4 text-xs text-gray-600 mt-1">
|
||||
Note: during an integration with Heroku, for security reasons, it
|
||||
is impossible to maintain end-to-end encryption. In theory, this
|
||||
lets Infisical decrypt yor environment variables. In practice, we
|
||||
can assure you that this will never be done, and it allows us to
|
||||
protect your secrets from bad actors online. The core Infisical
|
||||
service will always stay end-to-end encrypted. With any questions,
|
||||
reach out support@infisical.com.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
|
||||
{Object.keys(integrations).map((integration) => (
|
||||
<div
|
||||
className={`relative ${
|
||||
["Heroku"].includes(integrations[integration].name)
|
||||
? ""
|
||||
: "opacity-50"
|
||||
}`}
|
||||
key={integrations[integration].name}
|
||||
>
|
||||
<a
|
||||
href={`${
|
||||
["Heroku"].includes(integrations[integration].name)
|
||||
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
|
||||
: "#"
|
||||
}`}
|
||||
rel="noopener"
|
||||
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
|
||||
["Heroku"].includes(integrations[integration].name)
|
||||
? "hover:bg-white/10 duration-200 cursor-pointer"
|
||||
: "cursor-default grayscale"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={`/images/integrations/${integrations[integration].name}.png`}
|
||||
height={100}
|
||||
width={100}
|
||||
alt="integration logo"
|
||||
></Image>
|
||||
{integrations[integration].name.split(" ").length > 2 ? (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
|
||||
<div>{integrations[integration].name.split(" ")[0]}</div>
|
||||
<div className="text-base">
|
||||
{integrations[integration].name.split(" ")[1]}{" "}
|
||||
{integrations[integration].name.split(" ")[2]}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
|
||||
{integrations[integration].name}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
{["Heroku"].includes(integrations[integration].name) &&
|
||||
authorizations
|
||||
.map((authorization) => authorization.integration)
|
||||
.includes(integrations[integration].name.toLowerCase()) && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div
|
||||
onClick={() => {
|
||||
deleteIntegrationAuth({
|
||||
integrationAuthId: authorizations
|
||||
.filter(
|
||||
(authorization) =>
|
||||
authorization.integration ==
|
||||
integrations[integration].name.toLowerCase()
|
||||
)
|
||||
.map((authorization) => authorization._id)[0],
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-xs mr-2 py-px"
|
||||
/>
|
||||
Revoke
|
||||
</div>
|
||||
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-xs mr-2"
|
||||
/>
|
||||
Authorized
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!["Heroku"].includes(integrations[integration].name) && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
|
||||
Coming Soon
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Integrations.requireAuth = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect,useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
@@ -14,130 +14,124 @@ import attemptLogin from "~/utilities/attemptLogin";
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* This function check if the user entered the correct credentials and should be allowed to log in.
|
||||
*/
|
||||
const loginCheck = async () => {
|
||||
setIsLoading(true);
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
false,
|
||||
true
|
||||
).then(() => {
|
||||
setTimeout(function () {
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* This function check if the user entered the correct credentials and should be allowed to log in.
|
||||
*/
|
||||
const loginCheck = async () => {
|
||||
setIsLoading(true);
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
false,
|
||||
true
|
||||
).then(() => {
|
||||
setTimeout(function () {
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
|
||||
<Head>
|
||||
<title>Login</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content="Log In to Infisical" />
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
/>
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="long logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Log In
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<p className="text-md flex justify-center mt-2 text-gray-400">
|
||||
Need an Infisical account?
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
|
||||
<Link href="/signup">
|
||||
<button className="w-full pb-3 hover:opacity-90 duration-200">
|
||||
<u className="font-normal text-md text-sky-500">
|
||||
Create an account
|
||||
</u>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label="Password"
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{errorLogin && (
|
||||
<Error text="Your email and/or password are wrong." />
|
||||
)}
|
||||
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
|
||||
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
|
||||
<Button
|
||||
text="Log In"
|
||||
onButtonPressed={loginCheck}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
|
||||
<Head>
|
||||
<title>Login</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content="Log In to Infisical" />
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
/>
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="long logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Log In
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<p className="text-md flex justify-center mt-2 text-gray-400">
|
||||
Need an Infisical account?
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
|
||||
<Link href="/signup">
|
||||
<button className="w-full pb-3 hover:opacity-90 duration-200">
|
||||
<u className="font-normal text-md text-sky-500">
|
||||
Create an account
|
||||
</u>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label="Password"
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{errorLogin && <Error text="Your email and/or password are wrong." />}
|
||||
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
|
||||
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
|
||||
<Button
|
||||
text="Log In"
|
||||
onButtonPressed={loginCheck}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
<p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p>
|
||||
</div> */}
|
||||
</div>
|
||||
{false && (
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faWarning}
|
||||
className="ml-2 mr-6 text-6xl"
|
||||
/>
|
||||
We are experiencing minor technical difficulties. We are
|
||||
working on solving it right now. Please come back in a few
|
||||
minutes.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
{false && (
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
|
||||
We are experiencing minor technical difficulties. We are working on
|
||||
solving it right now. Please come back in a few minutes.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,22 @@ import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function NoProjects() {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
||||
<FontAwesomeIcon icon={faFolderOpen} className="text-7xl mb-8 w-full px-auto" />
|
||||
<div className="max-w-md">
|
||||
You are not part of any projects in this organization yet. When you do, they will appear here.
|
||||
</div>
|
||||
<div className="max-w-md mt-4">
|
||||
Create a new project, or ask
|
||||
other organization members to give you neccessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
||||
<FontAwesomeIcon
|
||||
icon={faFolderOpen}
|
||||
className="text-7xl mb-8 w-full px-auto"
|
||||
/>
|
||||
<div className="max-w-md">
|
||||
You are not part of any projects in this organization yet. When you do,
|
||||
they will appear here.
|
||||
</div>
|
||||
<div className="max-w-md mt-4">
|
||||
Create a new project, or ask other organization members to give you
|
||||
neccessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NoProjects.requireAuth = true;
|
||||
|
||||
@@ -2,21 +2,21 @@ import React from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
export default function Activity() {
|
||||
return (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
|
||||
<Head>
|
||||
<title>Request a New Invite</title>
|
||||
<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-6xl">Oops.</p>
|
||||
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
|
||||
<p className="text-xl">Ask the administrator for a new one.</p>
|
||||
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
|
||||
Note: If it still {"doesn't work"}, please reach out to us
|
||||
at support@infisical.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
|
||||
<Head>
|
||||
<title>Request a New Invite</title>
|
||||
<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-6xl">Oops.</p>
|
||||
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
|
||||
<p className="text-xl">Ask the administrator for a new one.</p>
|
||||
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
|
||||
Note: If it still {"doesn't work"}, please reach out to us at
|
||||
support@infisical.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect,useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
import Plan from "~/components/billing/Plan";
|
||||
@@ -9,102 +9,101 @@ import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscript
|
||||
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
|
||||
|
||||
export default function SettingsBilling() {
|
||||
let [currentPlan, setCurrentPlan] = useState("");
|
||||
let [numUsers, setNumUsers] = useState("");
|
||||
let [currentPlan, setCurrentPlan] = useState("");
|
||||
let [numUsers, setNumUsers] = useState("");
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 1,
|
||||
name: "Starter",
|
||||
price: "Free",
|
||||
priceExplanation: "up to 5 team members",
|
||||
text: "Manage any project with 5 members for free!",
|
||||
subtext: "$5 per member/month afterwards.",
|
||||
buttonTextMain: "Downgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_STARTER,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: "Professional",
|
||||
price: "$9",
|
||||
priceExplanation: "/member/month",
|
||||
subtext: "Includes unlimited projects & members.",
|
||||
text: "Keep up with key management as you grow.",
|
||||
buttonTextMain: "Upgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_PRO,
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: "Enterprise",
|
||||
price: "Custom Pricing",
|
||||
text: "More control for advanced organizations.",
|
||||
buttonTextMain: "Schedule a Demo",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
const plans = [
|
||||
{
|
||||
key: 1,
|
||||
name: "Starter",
|
||||
price: "Free",
|
||||
priceExplanation: "up to 5 team members",
|
||||
text: "Manage any project with 5 members for free!",
|
||||
subtext: "$5 per member/month afterwards.",
|
||||
buttonTextMain: "Downgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_STARTER,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: "Professional",
|
||||
price: "$9",
|
||||
priceExplanation: "/member/month",
|
||||
subtext: "Includes unlimited projects & members.",
|
||||
text: "Keep up with key management as you grow.",
|
||||
buttonTextMain: "Upgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_PRO,
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: "Enterprise",
|
||||
price: "Custom Pricing",
|
||||
text: "More control for advanced organizations.",
|
||||
buttonTextMain: "Schedule a Demo",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(async () => {
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setNumUsers(orgUsers.length);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings - Billing</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Usage & Billing" />
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Usage & Billing
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your {"organization's"}{" "}
|
||||
subscription here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<p className="text-xl font-semibold">Subscription</p>
|
||||
<div className="flex flex-row mt-4 overflow-x-auto">
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xl font-bold mt-12">Current Usage</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">{numUsers}</p>
|
||||
<p className="text-gray-300">
|
||||
{numUsers > 1
|
||||
? "Organization members"
|
||||
: "Organization member"}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setNumUsers(orgUsers.length);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings - Billing</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Usage & Billing" />
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Usage & Billing
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your {"organization's"} subscription here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<p className="text-xl font-semibold">Subscription</p>
|
||||
<div className="flex flex-row mt-4 overflow-x-auto">
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xl font-bold mt-12">Current Usage</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">{numUsers}</p>
|
||||
<p className="text-gray-300">
|
||||
{numUsers > 1
|
||||
? "Organization members"
|
||||
: "Organization member"}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">1 </p>
|
||||
<p className="text-gray-300">Organization projects</p>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsBilling.requireAuth = true;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect,useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faX,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -29,340 +29,313 @@ import deleteWorkspace from "../../api/workspace/deleteWorkspace";
|
||||
import getWorkspaces from "../../api/workspace/getWorkspaces";
|
||||
|
||||
export default function SettingsOrg() {
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [emailUser, setEmailUser] = useState("");
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
|
||||
useState("");
|
||||
const [searchUsers, setSearchUsers] = useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
|
||||
useState(false);
|
||||
const [isAddUserOpen, setIsAddUserOpen] = useState(
|
||||
router.asPath.split("?")[1] == "invite"
|
||||
);
|
||||
const [incidentContacts, setIncidentContacts] = useState([]);
|
||||
const [searchIncidentContact, setSearchIncidentContact] = useState("");
|
||||
const [userList, setUserList] = useState();
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
let workspaceIdTemp;
|
||||
const [email, setEmail] = useState("");
|
||||
const [currentPlan, setCurrentPlan] = useState("");
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [emailUser, setEmailUser] = useState("");
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
|
||||
const [searchUsers, setSearchUsers] = useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
|
||||
useState(false);
|
||||
const [isAddUserOpen, setIsAddUserOpen] = useState(
|
||||
router.asPath.split("?")[1] == "invite"
|
||||
);
|
||||
const [incidentContacts, setIncidentContacts] = useState([]);
|
||||
const [searchIncidentContact, setSearchIncidentContact] = useState("");
|
||||
const [userList, setUserList] = useState();
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
let workspaceIdTemp;
|
||||
const [email, setEmail] = useState("");
|
||||
const [currentPlan, setCurrentPlan] = useState("");
|
||||
|
||||
useEffect(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let orgData = org;
|
||||
setOrgName(orgData.name);
|
||||
let incidentContactsData = await getIncidentContacts(
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
useEffect(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let orgData = org;
|
||||
setOrgName(orgData.name);
|
||||
let incidentContactsData = await getIncidentContacts(
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
|
||||
setIncidentContacts(
|
||||
incidentContactsData?.map((contact) => contact.email)
|
||||
);
|
||||
setIncidentContacts(incidentContactsData?.map((contact) => contact.email));
|
||||
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
|
||||
workspaceIdTemp = router.query.id;
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setUserList(
|
||||
orgUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email:
|
||||
user.user?.email == null
|
||||
? user.inviteEmail
|
||||
: user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}))
|
||||
);
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
}, []);
|
||||
workspaceIdTemp = router.query.id;
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setUserList(
|
||||
orgUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}))
|
||||
);
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
}, []);
|
||||
|
||||
const modifyOrgName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setOrgName(newName);
|
||||
};
|
||||
const modifyOrgName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setOrgName(newName);
|
||||
};
|
||||
|
||||
const submitChanges = (newOrgName) => {
|
||||
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
const submitChanges = (newOrgName) => {
|
||||
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
|
||||
function closeAddUserModal() {
|
||||
setIsAddUserOpen(false);
|
||||
}
|
||||
function closeAddUserModal() {
|
||||
setIsAddUserOpen(false);
|
||||
}
|
||||
|
||||
function closeAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(false);
|
||||
}
|
||||
function closeAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(false);
|
||||
}
|
||||
|
||||
function openAddUserModal() {
|
||||
setIsAddUserOpen(true);
|
||||
}
|
||||
function openAddUserModal() {
|
||||
setIsAddUserOpen(true);
|
||||
}
|
||||
|
||||
function openAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(true);
|
||||
}
|
||||
function openAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(true);
|
||||
}
|
||||
|
||||
async function submitAddUserModal(email) {
|
||||
await addUserToOrg(email, localStorage.getItem("orgData.id"));
|
||||
setEmail("");
|
||||
setIsAddUserOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
async function submitAddUserModal(email) {
|
||||
await addUserToOrg(email, localStorage.getItem("orgData.id"));
|
||||
setEmail("");
|
||||
setIsAddUserOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
|
||||
const deleteIncidentContactFully = (incidentContact) => {
|
||||
setIncidentContacts(
|
||||
incidentContacts.filter((contact) => contact != incidentContact)
|
||||
);
|
||||
deleteIncidentContact(
|
||||
localStorage.getItem("orgData.id"),
|
||||
incidentContact
|
||||
);
|
||||
};
|
||||
const deleteIncidentContactFully = (incidentContact) => {
|
||||
setIncidentContacts(
|
||||
incidentContacts.filter((contact) => contact != incidentContact)
|
||||
);
|
||||
deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Organization Settings" />
|
||||
<AddIncidentContactDialog
|
||||
isOpen={isAddIncidentContactOpen}
|
||||
closeModal={closeAddIncidentContactModal}
|
||||
workspaceId={workspaceId}
|
||||
incidentContacts={incidentContacts}
|
||||
setIncidentContacts={setIncidentContacts}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Organization Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your organization here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
|
||||
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
Display Name
|
||||
</p>
|
||||
<InputField
|
||||
// label="Organization Name"
|
||||
onChangeHandler={modifyOrgName}
|
||||
type="varName"
|
||||
value={orgName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div
|
||||
className={`flex justify-start max-w-sm mt-4 mb-2`}
|
||||
>
|
||||
<Button
|
||||
text="Save Changes"
|
||||
onButtonPressed={() =>
|
||||
submitChanges(orgName)
|
||||
}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<p className="font-semibold mr-4 text-white text-xl">
|
||||
Organization Members
|
||||
</p>
|
||||
<p className="mr-4 text-gray-400 mt-2 mb-2">
|
||||
Manage members of your organization. These users
|
||||
could afterwards be formed into projects.
|
||||
</p>
|
||||
<AddUserDialog
|
||||
isOpen={isAddUserOpen}
|
||||
closeModal={closeAddUserModal}
|
||||
submitModal={submitAddUserModal}
|
||||
email={emailUser}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmailUser}
|
||||
currentPlan={currentPlan}
|
||||
orgName={orgName}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchUsers}
|
||||
onChange={(e) =>
|
||||
setSearchUsers(e.target.value)
|
||||
}
|
||||
placeholder={"Search members..."}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
|
||||
<Button
|
||||
text="Add Member"
|
||||
onButtonPressed={openAddUserModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{userList && (
|
||||
<div className="overflow-y-auto max-w-6xl w-full">
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
myUser={personalEmail}
|
||||
filter={searchUsers.toLowerCase()}
|
||||
resendInvite={submitAddUserModal}
|
||||
isOrg={true}
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Organization Settings" />
|
||||
<AddIncidentContactDialog
|
||||
isOpen={isAddIncidentContactOpen}
|
||||
closeModal={closeAddIncidentContactModal}
|
||||
workspaceId={workspaceId}
|
||||
incidentContacts={incidentContacts}
|
||||
setIncidentContacts={setIncidentContacts}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Organization Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your organization here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
|
||||
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
Display Name
|
||||
</p>
|
||||
<InputField
|
||||
// label="Organization Name"
|
||||
onChangeHandler={modifyOrgName}
|
||||
type="varName"
|
||||
value={orgName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
|
||||
<Button
|
||||
text="Save Changes"
|
||||
onButtonPressed={() => submitChanges(orgName)}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<p className="font-semibold mr-4 text-white text-xl">
|
||||
Organization Members
|
||||
</p>
|
||||
<p className="mr-4 text-gray-400 mt-2 mb-2">
|
||||
Manage members of your organization. These users could
|
||||
afterwards be formed into projects.
|
||||
</p>
|
||||
<AddUserDialog
|
||||
isOpen={isAddUserOpen}
|
||||
closeModal={closeAddUserModal}
|
||||
submitModal={submitAddUserModal}
|
||||
email={emailUser}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmailUser}
|
||||
currentPlan={currentPlan}
|
||||
orgName={orgName}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchUsers}
|
||||
onChange={(e) => setSearchUsers(e.target.value)}
|
||||
placeholder={"Search members..."}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
|
||||
<Button
|
||||
text="Add Member"
|
||||
onButtonPressed={openAddUserModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{userList && (
|
||||
<div className="overflow-y-auto max-w-6xl w-full">
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
myUser={personalEmail}
|
||||
filter={searchUsers.toLowerCase()}
|
||||
resendInvite={submitAddUserModal}
|
||||
isOrg={true}
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Incident Contacts
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mb-2 min-w-max">
|
||||
These contacts will be notified in the
|
||||
unlikely event of a severe incident.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
|
||||
<Button
|
||||
text="Add Contact"
|
||||
onButtonPressed={
|
||||
openAddIncidentContactModal
|
||||
}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchIncidentContact}
|
||||
onChange={(e) =>
|
||||
setSearchIncidentContact(e.target.value)
|
||||
}
|
||||
placeholder={"Search..."}
|
||||
/>
|
||||
</div>
|
||||
{incidentContacts?.filter((email) =>
|
||||
email.includes(searchIncidentContact)
|
||||
).length > 0 ? (
|
||||
incidentContacts
|
||||
.filter((email) =>
|
||||
email.includes(searchIncidentContact)
|
||||
)
|
||||
.map((contact) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
|
||||
>
|
||||
<p className="text-gray-300">
|
||||
{contact}
|
||||
</p>
|
||||
<div className="opacity-50 hover:opacity-100 duration-200">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteIncidentContactFully(
|
||||
contact
|
||||
)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
|
||||
<p className="text-gray-400">
|
||||
No incident contacts found.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Incident Contacts
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mb-2 min-w-max">
|
||||
These contacts will be notified in the unlikely event of a
|
||||
severe incident.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
|
||||
<Button
|
||||
text="Add Contact"
|
||||
onButtonPressed={openAddIncidentContactModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchIncidentContact}
|
||||
onChange={(e) => setSearchIncidentContact(e.target.value)}
|
||||
placeholder={"Search..."}
|
||||
/>
|
||||
</div>
|
||||
{incidentContacts?.filter((email) =>
|
||||
email.includes(searchIncidentContact)
|
||||
).length > 0 ? (
|
||||
incidentContacts
|
||||
.filter((email) => email.includes(searchIncidentContact))
|
||||
.map((contact) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
|
||||
>
|
||||
<p className="text-gray-300">{contact}</p>
|
||||
<div className="opacity-50 hover:opacity-100 duration-200">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteIncidentContactFully(contact)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
|
||||
<p className="text-gray-400">No incident contacts found.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
|
||||
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
|
||||
<p className="text-xl font-bold text-red">
|
||||
Danger Zone
|
||||
</p>
|
||||
@@ -397,11 +370,11 @@ export default function SettingsOrg() {
|
||||
have more than one.
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsOrg.requireAuth = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect,useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -7,57 +7,54 @@ import Button from "~/components/basic/buttons/Button";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import changePassword from "~/components/utilities/cryptography/changePassword";
|
||||
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
||||
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
|
||||
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
||||
|
||||
import getUser from "../../api/user/getUser";
|
||||
|
||||
export default function PersonalSettings() {
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [personalName, setPersonalName] = useState("");
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [backupPassword, setBackupPassword] = useState("");
|
||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [personalName, setPersonalName] = useState("");
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [backupPassword, setBackupPassword] = useState("");
|
||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
|
||||
useEffect(async () => {
|
||||
let user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
setPersonalName(user.firstName + " " + user.lastName);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
let user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
setPersonalName(user.firstName + " " + user.lastName);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Personal Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader
|
||||
pageName="Personal Settings"
|
||||
isProjectRelated={false}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Personal Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your personal information here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md flex flex-col items-end pb-4">
|
||||
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Personal Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Personal Settings" isProjectRelated={false} />
|
||||
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Personal Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your personal information here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md flex flex-col items-end pb-4">
|
||||
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
Display Name
|
||||
@@ -101,235 +98,223 @@ export default function PersonalSettings() {
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Change password
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-xl w-full">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={(password) => {
|
||||
setCurrentPassword(password);
|
||||
}}
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
isRequired
|
||||
error={currentPasswordError}
|
||||
errorText="The current password may be wrong"
|
||||
/>
|
||||
<div className="py-2"></div>
|
||||
<InputField
|
||||
label="New Password"
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
}}
|
||||
type="password"
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength &&
|
||||
passwordErrorLowerCase &&
|
||||
passwordErrorNumber
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
|
||||
<div
|
||||
className={`text-gray-400 text-sm mb-1`}
|
||||
>
|
||||
Password should contain at least:
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength
|
||||
? "text-gray-400"
|
||||
: "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase
|
||||
? "text-gray-400"
|
||||
: "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber
|
||||
? "text-gray-400"
|
||||
: "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 number
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
)}
|
||||
<div className="flex flex-row items-center mt-3 w-52 pr-3">
|
||||
<Button
|
||||
text="Change Password"
|
||||
onButtonPressed={() => {
|
||||
if (
|
||||
!passwordErrorLength &&
|
||||
!passwordErrorLowerCase &&
|
||||
!passwordErrorNumber
|
||||
) {
|
||||
changePassword(
|
||||
personalEmail,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
);
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={
|
||||
newPassword != "" &&
|
||||
currentPassword != "" &&
|
||||
!(
|
||||
passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber
|
||||
)
|
||||
}
|
||||
textDisabled="Change Password"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
passwordChanged
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Change password
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-xl w-full">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={(password) => {
|
||||
setCurrentPassword(password);
|
||||
}}
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
isRequired
|
||||
error={currentPasswordError}
|
||||
errorText="The current password may be wrong"
|
||||
/>
|
||||
<div className="py-2"></div>
|
||||
<InputField
|
||||
label="New Password"
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
}}
|
||||
type="password"
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength &&
|
||||
passwordErrorLowerCase &&
|
||||
passwordErrorNumber
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
Password should contain at least:
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase
|
||||
? "text-gray-400"
|
||||
: "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 number
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
)}
|
||||
<div className="flex flex-row items-center mt-3 w-52 pr-3">
|
||||
<Button
|
||||
text="Change Password"
|
||||
onButtonPressed={() => {
|
||||
if (
|
||||
!passwordErrorLength &&
|
||||
!passwordErrorLowerCase &&
|
||||
!passwordErrorNumber
|
||||
) {
|
||||
changePassword(
|
||||
personalEmail,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
);
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={
|
||||
newPassword != "" &&
|
||||
currentPassword != "" &&
|
||||
!(
|
||||
passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber
|
||||
)
|
||||
}
|
||||
textDisabled="Change Password"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
passwordChanged ? "opacity-100" : "opacity-0"
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Emergency Kit
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 min-w-max">
|
||||
Your Emergency Kit contains the
|
||||
information you’ll need to sign in to
|
||||
your Infisical account.
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
|
||||
Only the latest issued Emergency Kit
|
||||
remains valid. To get a new Emergency
|
||||
Kit, verify your password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-xl mb-4">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={setBackupPassword}
|
||||
type="password"
|
||||
value={backupPassword}
|
||||
isRequired
|
||||
error={backupKeyError}
|
||||
errorText="The current password is wrong"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center mt-3 w-full w-60">
|
||||
<Button
|
||||
text="Download Emergency Kit"
|
||||
onButtonPressed={() => {
|
||||
issueBackupKey({
|
||||
email: personalEmail,
|
||||
password: backupPassword,
|
||||
personalName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={backupPassword != ""}
|
||||
textDisabled="Download Emergency Kit"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
backupKeyIssued
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Emergency Kit
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 min-w-max">
|
||||
Your Emergency Kit contains the information you’ll need to
|
||||
sign in to your Infisical account.
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
|
||||
Only the latest issued Emergency Kit remains valid. To get a
|
||||
new Emergency Kit, verify your password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-xl mb-4">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={setBackupPassword}
|
||||
type="password"
|
||||
value={backupPassword}
|
||||
isRequired
|
||||
error={backupKeyError}
|
||||
errorText="The current password is wrong"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center mt-3 w-full w-60">
|
||||
<Button
|
||||
text="Download Emergency Kit"
|
||||
onButtonPressed={() => {
|
||||
issueBackupKey({
|
||||
email: personalEmail,
|
||||
password: backupPassword,
|
||||
personalName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={backupPassword != ""}
|
||||
textDisabled="Download Emergency Kit"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
backupKeyIssued ? "opacity-100" : "opacity-0"
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PersonalSettings.requireAuth = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect,useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -15,212 +15,191 @@ import getWorkspaces from "../../api/workspace/getWorkspaces";
|
||||
import renameWorkspace from "../../api/workspace/renameWorkspace";
|
||||
|
||||
export default function SettingsBasic() {
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const [serviceTokens, setServiceTokens] = useState([]);
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
|
||||
useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
|
||||
useState(false);
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const [serviceTokens, setServiceTokens] = useState([]);
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
|
||||
useState(false);
|
||||
|
||||
useEffect(async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
userWorkspaces.map((userWorkspace) => {
|
||||
if (userWorkspace._id == router.query.id) {
|
||||
setWorkspaceName(userWorkspace.name);
|
||||
}
|
||||
});
|
||||
let tempServiceTokens = await getServiceTokens({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setServiceTokens(tempServiceTokens);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
userWorkspaces.map((userWorkspace) => {
|
||||
if (userWorkspace._id == router.query.id) {
|
||||
setWorkspaceName(userWorkspace.name);
|
||||
}
|
||||
});
|
||||
let tempServiceTokens = await getServiceTokens({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setServiceTokens(tempServiceTokens);
|
||||
}, []);
|
||||
|
||||
const modifyWorkspaceName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setWorkspaceName(newName);
|
||||
};
|
||||
const modifyWorkspaceName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setWorkspaceName(newName);
|
||||
};
|
||||
|
||||
const submitChanges = (newWorkspaceName) => {
|
||||
renameWorkspace(router.query.id, newWorkspaceName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
const submitChanges = (newWorkspaceName) => {
|
||||
renameWorkspace(router.query.id, newWorkspaceName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
|
||||
function closeAddModal() {
|
||||
setIsAddOpen(false);
|
||||
}
|
||||
function closeAddModal() {
|
||||
setIsAddOpen(false);
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
setIsAddOpen(true);
|
||||
}
|
||||
function openAddModal() {
|
||||
setIsAddOpen(true);
|
||||
}
|
||||
|
||||
const closeAddServiceTokenModal = () => {
|
||||
setIsAddServiceTokenDialogOpen(false);
|
||||
};
|
||||
const closeAddServiceTokenModal = () => {
|
||||
setIsAddServiceTokenDialogOpen(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<AddServiceTokenDialog
|
||||
isOpen={isAddServiceTokenDialogOpen}
|
||||
workspaceId={router.query.id}
|
||||
closeModal={closeAddServiceTokenModal}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
<div className="flex flex-row mr-6 max-w-5xl">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader
|
||||
pageName="Project Settings"
|
||||
isProjectRelated={true}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Project Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
These settings only apply to the currently
|
||||
selected Project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-start">
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
|
||||
<p className="text-xl font-semibold mb-4">
|
||||
Display Name
|
||||
</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<InputField
|
||||
onChangeHandler={
|
||||
modifyWorkspaceName
|
||||
}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div
|
||||
className={`flex justify-start max-w-sm mt-4 mb-2`}
|
||||
>
|
||||
<Button
|
||||
text="Save Changes"
|
||||
onButtonPressed={() =>
|
||||
submitChanges(workspaceName)
|
||||
}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<p className="text-xl font-semibold self-start">
|
||||
Project ID
|
||||
</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start mt-4">
|
||||
To integrate Infisical into your code
|
||||
base and get automatic injection of
|
||||
environmental variables, you should use
|
||||
the following Project ID.
|
||||
</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start">
|
||||
For more guidance, including code
|
||||
snipets for various languages and
|
||||
frameworks, see{" "}
|
||||
{/* eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
href="https://infisical.com/docs/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="text-primary hover:opacity-80 duration-200"
|
||||
>
|
||||
Infisical Docs.
|
||||
</a>
|
||||
</p>
|
||||
<div className="max-h-28 w-ful">
|
||||
<InputField
|
||||
type="varName"
|
||||
value={router.query.id}
|
||||
placeholder=""
|
||||
isRequired
|
||||
static
|
||||
text="This is your project's auto-generated unique identifier. It can't be changed."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<p className="text-xl font-semibold mb-3">
|
||||
Service Tokens
|
||||
</p>
|
||||
<p className="text-base text-gray-400 mb-4">
|
||||
Every service token is specific
|
||||
to you, a certain project and a
|
||||
certain environment within this
|
||||
project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-48">
|
||||
<Button
|
||||
text="Add New Token"
|
||||
onButtonPressed={() => {
|
||||
setIsAddServiceTokenDialogOpen(
|
||||
true
|
||||
);
|
||||
}}
|
||||
color="mineshaft"
|
||||
icon={faPlus}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceTokenTable
|
||||
data={serviceTokens}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<AddServiceTokenDialog
|
||||
isOpen={isAddServiceTokenDialogOpen}
|
||||
workspaceId={router.query.id}
|
||||
closeModal={closeAddServiceTokenModal}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
<div className="flex flex-row mr-6 max-w-5xl">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Project Settings" isProjectRelated={true} />
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Project Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
These settings only apply to the currently selected Project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-start">
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
|
||||
<p className="text-xl font-semibold mb-4">Display Name</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<InputField
|
||||
onChangeHandler={modifyWorkspaceName}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
|
||||
<Button
|
||||
text="Save Changes"
|
||||
onButtonPressed={() => submitChanges(workspaceName)}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<p className="text-xl font-semibold self-start">Project ID</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start mt-4">
|
||||
To integrate Infisical into your code base and get automatic
|
||||
injection of environmental variables, you should use the
|
||||
following Project ID.
|
||||
</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start">
|
||||
For more guidance, including code snipets for various
|
||||
languages and frameworks, see{" "}
|
||||
{/* eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
href="https://infisical.com/docs/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="text-primary hover:opacity-80 duration-200"
|
||||
>
|
||||
Infisical Docs.
|
||||
</a>
|
||||
</p>
|
||||
<div className="max-h-28 w-ful">
|
||||
<InputField
|
||||
type="varName"
|
||||
value={router.query.id}
|
||||
placeholder=""
|
||||
isRequired
|
||||
static
|
||||
text="This is your project's auto-generated unique identifier. It can't be changed."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<p className="text-xl font-semibold mb-3">
|
||||
Service Tokens
|
||||
</p>
|
||||
<p className="text-base text-gray-400 mb-4">
|
||||
Every service token is specific to you, a certain
|
||||
project and a certain environment within this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-48">
|
||||
<Button
|
||||
text="Add New Token"
|
||||
onButtonPressed={() => {
|
||||
setIsAddServiceTokenDialogOpen(true);
|
||||
}}
|
||||
color="mineshaft"
|
||||
icon={faPlus}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceTokenTable
|
||||
data={serviceTokens}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
|
||||
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
|
||||
<p className="text-xl font-semibold self-start">
|
||||
Project Environments
|
||||
</p>
|
||||
@@ -255,47 +234,42 @@ export default function SettingsBasic() {
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
|
||||
<p className="text-xl font-bold text-red">
|
||||
Danger Zone
|
||||
</p>
|
||||
<p className="mt-2 text-md text-gray-400">
|
||||
As soon as you delete this project, you will not
|
||||
be able to undo it. This will immediately remove
|
||||
all the keys. If you still want to do that,
|
||||
please enter the name of the project below.
|
||||
</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
|
||||
<InputField
|
||||
label="Project to be Deleted"
|
||||
onChangeHandler={
|
||||
setWorkspaceToBeDeletedName
|
||||
}
|
||||
type="varName"
|
||||
value={workspaceToBeDeletedName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={executeDeletingWorkspace}
|
||||
>
|
||||
Delete Project
|
||||
</button>
|
||||
<p className="mt-0.5 ml-1 text-xs text-gray-500">
|
||||
Note: You can only delete a project in case you
|
||||
have more than one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
|
||||
<p className="text-xl font-bold text-red">Danger Zone</p>
|
||||
<p className="mt-2 text-md text-gray-400">
|
||||
As soon as you delete this project, you will not be able to undo
|
||||
it. This will immediately remove all the keys. If you still want
|
||||
to do that, please enter the name of the project below.
|
||||
</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
|
||||
<InputField
|
||||
label="Project to be Deleted"
|
||||
onChangeHandler={setWorkspaceToBeDeletedName}
|
||||
type="varName"
|
||||
value={workspaceToBeDeletedName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={executeDeletingWorkspace}
|
||||
>
|
||||
Delete Project
|
||||
</button>
|
||||
<p className="mt-0.5 ml-1 text-xs text-gray-500">
|
||||
Note: You can only delete a project in case you have more than
|
||||
one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsBasic.requireAuth = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user