Add frontend, backend and CLI

This commit is contained in:
Maidul Islam
2022-11-17 17:54:35 -05:00
parent 8d00c5cdb7
commit bea0ff6e05
276 changed files with 50230 additions and 72 deletions
+102
View File
@@ -0,0 +1,102 @@
import React, { useState, useEffect } from "react";
import Head from "next/head";
import Plan from "../../../components/billing/Plan";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription"
import getOrganizationUsers from "../../api/organization/GetOrgUsers"
import NavHeader from "../../../components/navigation/NavHeader";
export default function SettingsBilling() {
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 == process.env.NEXT_PUBLIC_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 == process.env.NEXT_PUBLIC_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)
}, []);
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>
);
}
SettingsBilling.requireAuth = true;
+390
View File
@@ -0,0 +1,390 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMagnifyingGlass, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getWorkspaces from "../../api/workspace/getWorkspaces";
import AddIncidentContactDialog from "../../../components/basic/dialog/AddIncidentContactDialog";
import getIncidentContacts from "../../api/organization/getIncidentContacts";
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import AddUserDialog from "../../../components/basic/dialog/AddUserDialog";
import UserTable from "../../../components/basic/table/UserTable";
import getUser from "../../api/user/getUser";
import guidGenerator from "../../../components/utilities/randomId";
import addUserToOrg from "../../api/organization/addUserToOrg";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
import renameOrg from "../../api/organization/renameOrg";
import getOrganization from "../../api/organization/GetOrg";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import NavHeader from "../../../components/navigation/NavHeader";
import Button from "../../../components/basic/buttons/Button";
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("");
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
)
);
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)
}, []);
const modifyOrgName = (newName) => {
setButtonReady(true);
setOrgName(newName);
};
const submitChanges = (newOrgName) => {
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
setButtonReady(false);
};
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddUserModal() {
setIsAddUserOpen(false);
}
function closeAddIncidentContactModal() {
setIsAddIncidentContactOpen(false);
}
function openAddUserModal() {
setIsAddUserOpen(true);
}
function openAddIncidentContactModal() {
setIsAddIncidentContactOpen(true);
}
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);
};
/**
* 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);
}
}
};
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="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>
<p className="mt-4 text-md text-gray-400">
As soon as you delete an organization, you will
not be able to undo it. This will immediately
remove all organization members and cancel your
subscription. If you still want to do that,
please enter the name of the organization below.
</p>
<div className="max-h-28 w-full max-w-xl mr-auto mt-8 max-w-xl">
<InputField
label="Organization to be Deleted"
onChangeHandler={
setWorkspaceToBeDeletedName
}
type="varName"
value={workspaceToBeDeletedName}
placeholder=""
isRequired
/>
</div>
<button
type="button"
className="mt-6 w-full max-w-xl 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-bold 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>
);
}
SettingsOrg.requireAuth = true;
+241
View File
@@ -0,0 +1,241 @@
import React, { useState, useEffect } from "react";
import Head from "next/head";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getUser from "../../api/user/getUser";
import NavHeader from "../../../components/navigation/NavHeader";
import passwordCheck from "../../../components/utilities/checks/PasswordCheck";
import changePassword from "../../../components/utilities/changePassword";
import issueBackupKey from "../../../components/utilities/issueBackupKey";
import Button from "../../../components/basic/buttons/Button";
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);
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">
<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
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 rounded-md bg-gray-800 text-sm ${
buttonReady &&
"hover:bg-primary hover:text-black hover:text-semibold duration-200 cursor-pointer"
} text-gray-400 px-4 py-2.5`}
>
{buttonReady ? (
<button
type="button"
className="flex flex-start justify-center font-medium px-2"
onClick={() =>
submitChanges(orgName)
}
>
Save Changes
</button>
) : (
<div className="flex flex-row items-center jutify-center px-4">
<FontAwesomeIcon
className="text-lg mr-3 text-gray-400"
icon={faCheck}
/>
<p className="font-base">
Saved
</p>
</div>
)}
</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 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;
+281
View File
@@ -0,0 +1,281 @@
import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getWorkspaces from "../../api/workspace/getWorkspaces";
import renameWorkspace from "../../api/workspace/renameWorkspace";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import NavHeader from "../../../components/navigation/NavHeader";
import Button from "../../../components/basic/buttons/Button";
import ServiceTokenTable from "../../../components/basic/table/ServiceTokenTable";
import getServiceTokens from "../../api/serviceToken/getServiceTokens"
import AddServiceTokenDialog from "../../../components/basic/dialog/AddServiceTokenDialog";
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);
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 submitChanges = (newWorkspaceName) => {
renameWorkspace(router.query.id, newWorkspaceName);
setButtonReady(false);
};
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddModal() {
setIsAddOpen(false);
}
function openAddModal() {
setIsAddOpen(true);
}
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();
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{" "}
<a
href="https://infisical.com/docs/gettingStarted"
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">
<p className="text-xl font-semibold self-start">
Project Environments
</p>
<p className="text-md mr-1 text-gray-400 mt-2 self-start">
Choose which environments will show up
in your Dashboard. Some common ones
include Development, Staging, and
Production. Often, teams choose to add
Testing.
</p>
<p className="text-sm mr-1 text-gray-500 self-start">
Note: the text in brackets shows how
these environmant should be accessed in
CLI.
</p>
<div className="rounded-md h-10 w-full mr-auto mt-4 flex flex-row">
{envOptions.map((env) => (
<div className="bg-white/5 hover:bg-white/10 duration-200 h-full w-max px-3 flex flex-row items-center justify-between rounded-md mr-1 text-sm">
{env}
<XIcon
className="h-5 w-5 ml-2 mt-0.5 text-white cursor-pointer"
aria-hidden="true"
/>
</div>
))}
<div className="group bg-white/5 hover:bg-primary hover:text-black duration-200 h-full w-max py-1 px-3 flex flex-row items-center justify-between rounded-md mr-1 cursor-pointer text-sm font-semibold">
<PlusIcon
className="h-5 w-5 text-white mr-2 group-hover:text-black"
aria-hidden="true"
/>
Add
</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;