Compare commits

...

1 Commits

Author SHA1 Message Date
Tuan Dang
f8ad8de4c5 Make progress on restyling 2024-03-15 12:31:59 -07:00
29 changed files with 685 additions and 562 deletions

View File

@@ -17,7 +17,7 @@ export const getDefaultOnPremFeatures = () => {
customAlerts: false, customAlerts: false,
auditLogs: false, auditLogs: false,
auditLogsRetentionDays: 0, auditLogsRetentionDays: 0,
samlSSO: false, samlSSO: true,
scim: false, scim: false,
ldap: false, ldap: false,
status: null, status: null,

View File

@@ -23,7 +23,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
customAlerts: false, customAlerts: false,
auditLogs: false, auditLogs: false,
auditLogsRetentionDays: 0, auditLogsRetentionDays: 0,
samlSSO: false, samlSSO: true,
scim: false, scim: false,
ldap: false, ldap: false,
status: null, status: null,

View File

@@ -39,7 +39,7 @@ export type TFeatureSet = {
customAlerts: false; customAlerts: false;
auditLogs: false; auditLogs: false;
auditLogsRetentionDays: 0; auditLogsRetentionDays: 0;
samlSSO: false; samlSSO: true;
scim: false; scim: false;
ldap: false; ldap: false;
status: null; status: null;

View File

@@ -302,7 +302,7 @@
"auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.", "auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.",
"docs": "Infisical Docs", "docs": "Infisical Docs",
"auto-capitalization": "Auto Capitalization", "auto-capitalization": "Auto Capitalization",
"auto-capitalization-description": "According to standards, Infisical will automatically capitalize your keys. If you want to disable this feature, you can do so here." "auto-capitalization-description": "Infisical will automatically capitalize your keys. If you want to disable this feature, you can do so here."
} }
}, },
"signup": { "signup": {

View File

@@ -290,7 +290,7 @@
"auto-generated": "Este es el ID único y autogenerado de proyecto. No se puede modificar.", "auto-generated": "Este es el ID único y autogenerado de proyecto. No se puede modificar.",
"docs": "Documentación de Infisical", "docs": "Documentación de Infisical",
"auto-capitalization": "Mayúsculas automáticas", "auto-capitalization": "Mayúsculas automáticas",
"auto-capitalization-description": "De acuerdo con los estándares, Infisical pondrá en mayúsculas tus claves. Si quieres desactivar esta funcionalidad, lo puedes hacer aquí." "auto-capitalization-description": "Infisical pondrá en mayúsculas tus claves. Si quieres desactivar esta funcionalidad, lo puedes hacer aquí."
} }
}, },
"signup": { "signup": {

View File

@@ -267,7 +267,7 @@
"auto-generated": "Este é o identificador exclusivo - gerado automaticamente - do seu projeto. Não pode ser alterado.", "auto-generated": "Este é o identificador exclusivo - gerado automaticamente - do seu projeto. Não pode ser alterado.",
"docs": "Documentação do Infisical", "docs": "Documentação do Infisical",
"auto-capitalization": "Converter em caixa alta automaticamente", "auto-capitalization": "Converter em caixa alta automaticamente",
"auto-capitalization-description": "Por padrão, Infisical converte automaticamente as chaves em caixa alta. Se você quiser desativar essa funcionalidade, pode fazê-lo aqui." "auto-capitalization-description": "Infisical converte automaticamente as chaves em caixa alta. Se você quiser desativar essa funcionalidade, pode fazê-lo aqui."
} }
}, },
"signup": { "signup": {

View File

@@ -23,9 +23,6 @@ export const MembersPage = withPermission(
<Tab value={TabSections.Identities}> <Tab value={TabSections.Identities}>
<div className="flex items-center"> <div className="flex items-center">
<p>Machine Identities</p> <p>Machine Identities</p>
<div className="ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
New
</div>
</div> </div>
</Tab> </Tab>
<Tab value={TabSections.Roles}>Organization Roles</Tab> <Tab value={TabSections.Roles}>Organization Roles</Tab>

View File

@@ -1,10 +1,13 @@
import Link from "next/link"; // import Link from "next/link";
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons"; // import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions"; // import { OrgPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2"; import {
// Button,
DeleteActionModal
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { withPermission } from "@app/hoc"; import { withPermission } from "@app/hoc";
import { useDeleteIdentity } from "@app/hooks/api"; import { useDeleteIdentity } from "@app/hooks/api";
@@ -58,9 +61,10 @@ export const IdentitySection = withPermission(
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between"> <div className="py-4">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p> <p className="mb-2 text-md text-mineshaft-100">Machine Identities</p>
<div className="flex w-full justify-end pr-4"> <p className="text-sm text-mineshaft-300">Manage which apps/services have access to this organization</p>
{/* <div className="flex w-full justify-end pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview"> <Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<a <a
target="_blank" target="_blank"
@@ -74,8 +78,8 @@ export const IdentitySection = withPermission(
/> />
</a> </a>
</Link> </Link>
</div> </div> */}
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}> {/* <OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
colorSchema="primary" colorSchema="primary"
@@ -87,7 +91,7 @@ export const IdentitySection = withPermission(
Create identity Create identity
</Button> </Button>
)} )}
</OrgPermissionCan> </OrgPermissionCan> */}
</div> </div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} /> <IdentityTable handlePopUpOpen={handlePopUpOpen} />
<IdentityModal <IdentityModal

View File

@@ -1,11 +1,14 @@
import { faKey, faLock, faPencil, faServer, faXmark } from "@fortawesome/free-solid-svg-icons"; import { useState } from "react";
import { faKey, faLock, faMagnifyingGlass, faPencil, faPlus, faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { import {
Button,
EmptyState, EmptyState,
IconButton, IconButton,
Input,
Select, Select,
SelectItem, SelectItem,
Table, Table,
@@ -49,10 +52,12 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
const { data, isLoading } = useGetIdentityMembershipOrgs(orgId); const { data: identities, isLoading } = useGetIdentityMembershipOrgs(orgId);
const { data: roles } = useGetOrgRoles(orgId); const { data: roles } = useGetOrgRoles(orgId);
const [searchIdentity, setSearchIdentity] = useState("");
const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => { const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => {
try { try {
await updateMutateAsync({ await updateMutateAsync({
@@ -77,91 +82,139 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
} }
}; };
const filteredIdentities = identities ? identities.filter(({ identity: { name } }) => name.toLocaleLowerCase().includes(searchIdentity.toLocaleLowerCase())) : [];
return ( return (
<TableContainer> <div>
<Table> <div className="flex">
<THead> <Input
<Tr> value={searchIdentity}
<Th>Name</Th> onChange={(e) => setSearchIdentity(e.target.value)}
<Th>ID</Th> leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
<Th>Role</Th> placeholder="Search identities..."
<Th>Auth Method</Th> />
<Th className="w-5" /> <OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}>
</Tr> {(isAllowed) => (
</THead> <Button
<TBody> colorSchema="secondary"
{isLoading && <TableSkeleton columns={4} innerKey="org-identities" />} type="submit"
{!isLoading && leftIcon={<FontAwesomeIcon icon={faPlus} />}
data && onClick={() => handlePopUpOpen("identity")}
data.length > 0 && isDisabled={!isAllowed}
data.map(({ identity: { id, name, authMethod }, role, customRole }) => { className="ml-4"
return ( >
<Tr className="h-10" key={`identity-${id}`}> Create identity
<Td>{name}</Td> </Button>
<Td>{id}</Td> )}
<Td> </OrgPermissionCan>
<OrgPermissionCan </div>
I={OrgPermissionActions.Edit} <TableContainer className="mt-4">
a={OrgPermissionSubjects.Identity} <Table>
> <THead>
{(isAllowed) => { <Tr>
return ( <Th>Name</Th>
<Select <Th>ID</Th>
value={role === "custom" ? (customRole?.slug as string) : role} <Th>Role</Th>
isDisabled={!isAllowed} <Th>Auth Method</Th>
className="w-40 bg-mineshaft-600" <Th className="w-5" />
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800" </Tr>
onValueChange={(selectedRole) => </THead>
handleChangeRole({ <TBody>
identityId: id, {isLoading && <TableSkeleton columns={4} innerKey="org-identities" />}
role: selectedRole {!isLoading && filteredIdentities?.map(({ identity: { id, name, authMethod }, role, customRole }) => {
}) return (
} <Tr className="h-10" key={`identity-${id}`}>
> <Td>{name}</Td>
{(roles || []).map(({ slug, name: roleName }) => ( <Td>{id}</Td>
<SelectItem value={slug} key={`owner-option-${slug}`}> <Td>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>{authMethod ? identityAuthToNameMap[authMethod] : "Not configured"}</Td>
<Td>
<div className="flex items-center justify-end">
{authMethod === IdentityAuthMethod.UNIVERSAL_AUTH && (
<Tooltip content="Manage client ID/secrets">
<IconButton
onClick={async () => {
handlePopUpOpen("universalAuthClientSecret", {
identityId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
// isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faKey} />
</IconButton>
</Tooltip>
)}
<OrgPermissionCan <OrgPermissionCan
I={OrgPermissionActions.Edit} I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity} a={OrgPermissionSubjects.Identity}
> >
{(isAllowed) => ( {(isAllowed) => {
<Tooltip content="Manage auth method"> return (
<Select
value={role === "custom" ? (customRole?.slug as string) : role}
isDisabled={!isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
handleChangeRole({
identityId: id,
role: selectedRole
})
}
>
{(roles || []).map(({ slug, name: roleName }) => (
<SelectItem value={slug} key={`owner-option-${slug}`}>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>{authMethod ? identityAuthToNameMap[authMethod] : "Not configured"}</Td>
<Td>
<div className="flex items-center justify-end">
{authMethod === IdentityAuthMethod.UNIVERSAL_AUTH && (
<Tooltip content="Manage client ID/secrets">
<IconButton <IconButton
onClick={async () => { onClick={async () => {
handlePopUpOpen("identityAuthMethod", { handlePopUpOpen("universalAuthClientSecret", {
identityId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
// isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faKey} />
</IconButton>
</Tooltip>
)}
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Tooltip content="Manage auth method">
<IconButton
onClick={async () => {
handlePopUpOpen("identityAuthMethod", {
identityId: id,
name,
authMethod
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faLock} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<IconButton
onClick={async () => {
handlePopUpOpen("identity", {
identityId: id, identityId: id,
name, name,
authMethod role,
customRole
}); });
}} }}
size="lg" size="lg"
@@ -171,76 +224,47 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
className="ml-4" className="ml-4"
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faLock} /> <FontAwesomeIcon icon={faPencil} />
</IconButton> </IconButton>
</Tooltip> )}
)} </OrgPermissionCan>
</OrgPermissionCan> <OrgPermissionCan
<OrgPermissionCan I={OrgPermissionActions.Delete}
I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Identity}
a={OrgPermissionSubjects.Identity} >
> {(isAllowed) => (
{(isAllowed) => ( <IconButton
<IconButton onClick={() => {
onClick={async () => { handlePopUpOpen("deleteIdentity", {
handlePopUpOpen("identity", { identityId: id,
identityId: id, name
name, });
role, }}
customRole size="lg"
}); colorSchema="danger"
}} variant="plain"
size="lg" ariaLabel="update"
colorSchema="primary" className="ml-4"
variant="plain" isDisabled={!isAllowed}
ariaLabel="update" >
className="ml-4" <FontAwesomeIcon icon={faXmark} />
isDisabled={!isAllowed} </IconButton>
> )}
<FontAwesomeIcon icon={faPencil} /> </OrgPermissionCan>
</IconButton> </div>
)} </Td>
</OrgPermissionCan> </Tr>
<OrgPermissionCan );
I={OrgPermissionActions.Delete} })}
a={OrgPermissionSubjects.Identity} </TBody>
> </Table>
{(isAllowed) => ( {!isLoading && filteredIdentities?.length === 0 && (
<IconButton <EmptyState
onClick={() => { title={searchIdentity === "" ? "No identities have been created in this organization" : "No matching identities found"}
handlePopUpOpen("deleteIdentity", { icon={faServer}
identityId: id, />
name )}
}); </TableContainer>
}} </div>
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</OrgPermissionCan>
</div>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState
title="No identities have been created in this organization"
icon={faServer}
/>
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
); );
}; };

View File

@@ -1,21 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import { import {
Button,
DeleteActionModal, DeleteActionModal,
EmailServiceSetupModal, EmailServiceSetupModal,
UpgradePlanModal UpgradePlanModal
} from "@app/components/v2"; } from "@app/components/v2";
import { import { useOrganization } from "@app/context";
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
useSubscription
} from "@app/context";
import { useDeleteOrgMembership } from "@app/hooks/api"; import { useDeleteOrgMembership } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
@@ -24,7 +15,6 @@ import { OrgMembersTable } from "./OrgMembersTable";
export const OrgMembersSection = () => { export const OrgMembersSection = () => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { subscription } = useSubscription();
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id ?? ""; const orgId = currentOrg?.id ?? "";
@@ -39,28 +29,6 @@ export const OrgMembersSection = () => {
const { mutateAsync: deleteMutateAsync } = useDeleteOrgMembership(); const { mutateAsync: deleteMutateAsync } = useDeleteOrgMembership();
const isMoreUsersNotAllowed = subscription?.memberLimit
? subscription.membersUsed >= subscription.memberLimit
: false;
const handleAddMemberModal = () => {
if (currentOrg?.authEnforced) {
createNotification({
text: "You cannot manage users from Infisical when org-level auth is enforced for your organization",
type: "error"
});
return;
}
if (isMoreUsersNotAllowed) {
handlePopUpOpen("upgradePlan", {
description: "You can add more members if you upgrade your Infisical plan."
});
} else {
handlePopUpOpen("addMember");
}
};
const onRemoveMemberSubmit = async (orgMembershipId: string) => { const onRemoveMemberSubmit = async (orgMembershipId: string) => {
try { try {
await deleteMutateAsync({ await deleteMutateAsync({
@@ -85,21 +53,9 @@ export const OrgMembersSection = () => {
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between"> <div className="py-4">
<p className="text-xl font-semibold text-mineshaft-100">Members</p> <h2 className="mb-2 text-md text-mineshaft-100">Members</h2>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}> <p className="text-sm text-mineshaft-300">Manage who has access to this organization</p>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddMemberModal()}
isDisabled={!isAllowed}
>
Add Member
</Button>
)}
</OrgPermissionCan>
</div> </div>
<OrgMembersTable <OrgMembersTable
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faMagnifyingGlass, faPlus,faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@@ -38,7 +38,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
handlePopUpOpen: ( handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeMember", "upgradePlan"]>, popUpName: keyof UsePopUpState<["addMember", "removeMember", "upgradePlan"]>,
data?: { data?: {
orgMembershipId?: string; orgMembershipId?: string;
username?: string; username?: string;
@@ -66,6 +66,28 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const { mutateAsync: addUserMutateAsync } = useAddUserToOrg(); const { mutateAsync: addUserMutateAsync } = useAddUserToOrg();
const { mutateAsync: updateUserOrgRole } = useUpdateOrgUserRole(); const { mutateAsync: updateUserOrgRole } = useUpdateOrgUserRole();
const isMoreUsersNotAllowed = subscription?.memberLimit
? subscription.membersUsed >= subscription.memberLimit
: false;
const handleAddMemberModal = () => {
if (currentOrg?.authEnforced) {
createNotification({
text: "You cannot manage users from Infisical when org-level auth is enforced for your organization",
type: "error"
});
return;
}
if (isMoreUsersNotAllowed) {
handlePopUpOpen("upgradePlan", {
description: "You can add more members if you upgrade your Infisical plan."
});
} else {
handlePopUpOpen("addMember");
}
};
const onRoleChange = async (membershipId: string, role: string) => { const onRoleChange = async (membershipId: string, role: string) => {
if (!currentOrg?.id) return; if (!currentOrg?.id) return;
@@ -152,12 +174,28 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
return ( return (
<div> <div>
<Input <div className="flex">
value={searchMemberFilter} <Input
onChange={(e) => setSearchMemberFilter(e.target.value)} value={searchMemberFilter}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} onChange={(e) => setSearchMemberFilter(e.target.value)}
placeholder="Search members..." leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/> placeholder="Search members..."
/>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddMemberModal()}
isDisabled={!isAllowed}
className="ml-4"
>
Add member
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4"> <TableContainer className="mt-4">
<Table> <Table>
<THead> <THead>

View File

@@ -1,10 +1,13 @@
import Link from "next/link"; // import Link from "next/link";
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons"; // import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { ProjectPermissionCan } from "@app/components/permissions"; // import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2"; import {
// Button,
DeleteActionModal
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
import { useDeleteIdentityFromWorkspace } from "@app/hooks/api"; import { useDeleteIdentityFromWorkspace } from "@app/hooks/api";
@@ -55,9 +58,10 @@ export const IdentitySection = withProjectPermission(
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="py-4">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p> <p className="mb-2 text-md text-mineshaft-100">Machine Identities</p>
<div className="flex w-full justify-end pr-4"> <p className="text-sm text-mineshaft-300">Manage which apps/services have access to this project</p>
{/* <div className="flex w-full justify-end pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview"> <Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white"> <span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
Documentation{" "} Documentation{" "}
@@ -67,8 +71,8 @@ export const IdentitySection = withProjectPermission(
/> />
</span> </span>
</Link> </Link>
</div> </div> */}
<ProjectPermissionCan {/* <ProjectPermissionCan
I={ProjectPermissionActions.Create} I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity} a={ProjectPermissionSub.Identity}
> >
@@ -83,7 +87,7 @@ export const IdentitySection = withProjectPermission(
Add identity Add identity
</Button> </Button>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan> */}
</div> </div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} /> <IdentityTable handlePopUpOpen={handlePopUpOpen} />
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />

View File

@@ -1,11 +1,14 @@
import { faServer, faXmark } from "@fortawesome/free-solid-svg-icons"; import { useState } from "react";
import { faMagnifyingGlass,faPlus, faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns"; import { format } from "date-fns";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
Button,
EmptyState, EmptyState,
IconButton, IconButton,
Input,
Table, Table,
TableContainer, TableContainer,
TableSkeleton, TableSkeleton,
@@ -33,76 +36,103 @@ type Props = {
export const IdentityTable = ({ handlePopUpOpen }: Props) => { export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || ""); const { data: identities, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
const [searchIdentity, setSearchIdentity] = useState("");
const filteredIdentities = identities ? identities.filter(({ identity: { name } }) => name.toLocaleLowerCase().includes(searchIdentity.toLocaleLowerCase())) : [];
return ( return (
<TableContainer> <div>
<Table> <div className="flex">
<THead> <Input
<Tr> value={searchIdentity}
<Th>Name</Th> onChange={(e) => setSearchIdentity(e.target.value)}
<Th>Role</Th> leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
<Th>Added on</Th> placeholder="Search identities..."
<Th className="w-5" /> />
</Tr> <ProjectPermissionCan
</THead> I={ProjectPermissionActions.Create}
<TBody> a={ProjectPermissionSub.Identity}
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />} >
{!isLoading && {(isAllowed) => (
data && <Button
data.length > 0 && colorSchema="secondary"
data.map(({ identity: { id, name }, roles, createdAt }) => { type="submit"
return ( leftIcon={<FontAwesomeIcon icon={faPlus} />}
<Tr className="h-10" key={`st-v3-${id}`}> onClick={() => handlePopUpOpen("identity")}
<Td>{name}</Td> isDisabled={!isAllowed}
<Td> className="ml-4"
<ProjectPermissionCan >
I={ProjectPermissionActions.Edit} Add identity
a={ProjectPermissionSub.Identity} </Button>
> )}
{(isAllowed) => ( </ProjectPermissionCan>
<IdentityRoles roles={roles} disableEdit={!isAllowed} identityId={id} /> </div>
)} <TableContainer className="mt-4">
</ProjectPermissionCan> <Table>
</Td> <THead>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr> <Tr>
<Td colSpan={7}> <Th>Name</Th>
<EmptyState title="No identities have been added to this project" icon={faServer} /> <Th>Role</Th>
</Td> <Th>Added on</Th>
<Th className="w-5" />
</Tr> </Tr>
)} </THead>
</TBody> <TBody>
</Table> {isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
</TableContainer> {!isLoading && filteredIdentities?.map(({ identity: { id, name }, roles, createdAt }) => {
return (
<Tr className="h-10" key={`st-v3-${id}`}>
<Td>{name}</Td>
<Td>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IdentityRoles roles={roles} disableEdit={!isAllowed} identityId={id} />
)}
</ProjectPermissionCan>
</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isLoading && filteredIdentities?.length === 0 && (
<EmptyState
title={searchIdentity === "" ? "No identities have been added to this project" : "No matching identities found"}
icon={faServer}
/>
)}
</TableContainer>
</div>
); );
}; };

View File

@@ -186,28 +186,32 @@ export const MemberListTab = () => {
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="py-4">
<p className="text-xl font-semibold text-mineshaft-100">Members</p> <h2 className="mb-2 text-md text-mineshaft-100">Members</h2>
<p className="text-sm text-mineshaft-300">Manage who has access to this project</p>
</div>
<div className="flex">
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}> <ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
colorSchema="primary" colorSchema="secondary"
type="submit" type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addMember")} onClick={() => handlePopUpOpen("addMember")}
isDisabled={!isAllowed} isDisabled={!isAllowed}
className="ml-4"
> >
Add Member Add Member
</Button> </Button>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<div className="mt-4"> <div className="mt-4">
<TableContainer> <TableContainer>
<Table> <Table>

View File

@@ -1,9 +1,6 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan, PermissionDeniedBanner } from "@app/components/permissions"; import { PermissionDeniedBanner } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
@@ -12,7 +9,7 @@ import { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
export const OrgIncidentContactsSection = () => { export const OrgIncidentContactsSection = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpClose } = usePopUp([
"addContact" "addContact"
] as const); ] as const);
const { permission } = useOrgPermission(); const { permission } = useOrgPermission();
@@ -20,22 +17,7 @@ export const OrgIncidentContactsSection = () => {
return ( return (
<> <>
<hr className="border-mineshaft-600" /> <hr className="border-mineshaft-600" />
<div className="flex items-center justify-between pt-4"> <p className="pt-4 text-md text-mineshaft-100">{t("section.incident.incident-contacts")}</p>
<p className="text-md text-mineshaft-100">{t("section.incident.incident-contacts")}</p>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addContact")}
>
Add contact
</Button>
)}
</OrgPermissionCan>
</div>
<div className="py-4"> <div className="py-4">
{permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount) ? ( {permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount) ? (
<OrgIncidentContactsTable /> <OrgIncidentContactsTable />

View File

@@ -1,10 +1,11 @@
import { useState } from "react"; import { useState } from "react";
import { faContactBook, faMagnifyingGlass, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faContactBook, faMagnifyingGlass, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { import {
Button,
DeleteActionModal, DeleteActionModal,
EmptyState, EmptyState,
IconButton, IconButton,
@@ -16,12 +17,13 @@ import {
Td, Td,
Th, Th,
THead, THead,
Tr Tr} from "@app/components/v2";
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api"; import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api";
import { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal";
export const OrgIncidentContactsTable = () => { export const OrgIncidentContactsTable = () => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
@@ -29,7 +31,8 @@ export const OrgIncidentContactsTable = () => {
const [searchContact, setSearchContact] = useState(""); const [searchContact, setSearchContact] = useState("");
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"removeContact", "removeContact",
"setUpEmail" "setUpEmail",
"addContact"
] as const); ] as const);
const { mutateAsync } = useDeleteIncidentContact(); const { mutateAsync } = useDeleteIncidentContact();
@@ -64,12 +67,28 @@ export const OrgIncidentContactsTable = () => {
return ( return (
<div> <div>
<Input <div className=" flex">
value={searchContact} <Input
onChange={(e) => setSearchContact(e.target.value)} value={searchContact}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} onChange={(e) => setSearchContact(e.target.value)}
placeholder="Search incident contact by email..." leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/> placeholder="Search incident contact by email..."
/>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addContact")}
className="ml-4"
>
Add contact
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4"> <TableContainer className="mt-4">
<Table> <Table>
<THead> <THead>
@@ -90,12 +109,14 @@ export const OrgIncidentContactsTable = () => {
> >
{(isAllowed) => ( {(isAllowed) => (
<IconButton <IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeContact", { email, id })} onClick={() => handlePopUpOpen("removeContact", { email, id })}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="delete"
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faTrash} /> <FontAwesomeIcon icon={faXmark} />
</IconButton> </IconButton>
)} )}
</OrgPermissionCan> </OrgPermissionCan>
@@ -115,6 +136,11 @@ export const OrgIncidentContactsTable = () => {
onChange={(isOpen) => handlePopUpToggle("removeContact", isOpen)} onChange={(isOpen) => handlePopUpToggle("removeContact", isOpen)}
onDeleteApproved={onRemoveIncidentContact} onDeleteApproved={onRemoveIncidentContact}
/> />
<AddOrgIncidentContactModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
</div> </div>
); );
}; };

View File

@@ -62,7 +62,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)} className="py-4"> <form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
<div className=""> <div>
<h2 className="mb-2 text-md text-mineshaft-100">Organization Name</h2> <h2 className="mb-2 text-md text-mineshaft-100">Organization Name</h2>
<Controller <Controller
defaultValue="" defaultValue=""

View File

@@ -1,49 +1,16 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tab } from "@headlessui/react";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab"; import { ProjectTabGroup } from "./components";
import { WebhooksTab } from "./components/WebhooksTab";
const tabs = [
{ name: "General", key: "tab-project-general" },
{ name: "Webhooks", key: "tab-project-webhooks" }
];
export const ProjectSettingsPage = () => { export const ProjectSettingsPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="flex justify-center w-full h-full bg-bunker-800 text-white"> <div className="flex justify-center w-full h-full bg-bunker-800 text-white">
<div className="max-w-7xl px-6 w-full"> <div className="max-w-4xl px-6 w-full">
<div className="my-6"> <div className="my-6">
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p> <p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div> </div>
<Tab.Group> <ProjectTabGroup />
<Tab.List className="mb-4 w-full border-b-2 border-mineshaft-800">
{tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}>
{({ selected }) => (
<button
type="button"
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
selected ? "border-b border-white text-white" : "text-mineshaft-400"
}`}
>
{tab.name}
</button>
)}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<ProjectGeneralTab />
</Tab.Panel>
<Tab.Panel>
<WebhooksTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</div> </div>
</div> </div>
); );

View File

@@ -36,25 +36,28 @@ export const AutoCapitalizationSection = () => {
}; };
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <>
<p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p> <hr className="border-mineshaft-600" />
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}> <div className="pt-4">
{(isAllowed) => ( <p className="text-md text-mineshaft-100">{t("settings.project.auto-capitalization")}</p>
<div className="w-max"> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
<Checkbox {(isAllowed) => (
className="data-[state=checked]:bg-primary" <div className="w-max py-4">
id="autoCapitalization" <Checkbox
isDisabled={!isAllowed} className="data-[state=checked]:bg-primary"
isChecked={currentWorkspace?.autoCapitalization ?? false} id="autoCapitalization"
onCheckedChange={(state) => { isDisabled={!isAllowed}
handleToggleCapitalizationToggle(state as boolean); isChecked={currentWorkspace?.autoCapitalization ?? false}
}} onCheckedChange={(state) => {
> handleToggleCapitalizationToggle(state as boolean);
{t("settings.project.auto-capitalization-description")} }}
</Checkbox> >
</div> {t("settings.project.auto-capitalization-description")}
)} </Checkbox>
</ProjectPermissionCan> </div>
</div> )}
</ProjectPermissionCan>
</div>
</>
); );
}; };

View File

@@ -54,22 +54,25 @@ export const DeleteProjectSection = () => {
}; };
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <>
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p> <hr className="border-mineshaft-600" />
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}> <div className="py-4">
{(isAllowed) => ( <p className="mb-4 text-md text-mineshaft-100">Danger Zone</p>
<Button <ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
isLoading={isDeleting} {(isAllowed) => (
isDisabled={!isAllowed || isDeleting} <Button
colorSchema="danger" isLoading={isDeleting}
variant="outline_bg" isDisabled={!isAllowed || isDeleting}
type="submit" colorSchema="danger"
onClick={() => handlePopUpOpen("deleteWorkspace")} variant="outline_bg"
> type="submit"
{`Delete ${currentWorkspace?.name}`} onClick={() => handlePopUpOpen("deleteWorkspace")}
</Button> >
)} {`Delete ${currentWorkspace?.name}`}
</ProjectPermissionCan> </Button>
)}
</ProjectPermissionCan>
</div>
<DeleteActionModal <DeleteActionModal
isOpen={popUp.deleteWorkspace.isOpen} isOpen={popUp.deleteWorkspace.isOpen}
title="Are you sure want to delete this project?" title="Are you sure want to delete this project?"
@@ -79,6 +82,6 @@ export const DeleteProjectSection = () => {
buttonText="Delete Project" buttonText="Delete Project"
onDeleteApproved={handleDeleteWorkspaceSubmit} onDeleteApproved={handleDeleteWorkspaceSubmit}
/> />
</div> </>
); );
}; };

View File

@@ -1,9 +1,10 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { useState } from "react";
import { faMagnifyingGlass,faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions"; import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal, UpgradePlanModal } from "@app/components/v2"; import { Button, DeleteActionModal, Input, UpgradePlanModal } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
@@ -18,11 +19,15 @@ import { AddEnvironmentModal } from "./AddEnvironmentModal";
import { EnvironmentTable } from "./EnvironmentTable"; import { EnvironmentTable } from "./EnvironmentTable";
import { UpdateEnvironmentModal } from "./UpdateEnvironmentModal"; import { UpdateEnvironmentModal } from "./UpdateEnvironmentModal";
// TODO: resolve strange subtitle spacing / design
// TODO: resolve filtering stuff
export const EnvironmentSection = () => { export const EnvironmentSection = () => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const [searchEnv, setSearchEnv] = useState("");
const deleteWsEnvironment = useDeleteWsEnvironment(); const deleteWsEnvironment = useDeleteWsEnvironment();
@@ -63,14 +68,21 @@ export const EnvironmentSection = () => {
}; };
return ( return (
<div <div id="environments">
id="environments" <hr className="border-mineshaft-600" />
className="mb-6 scroll-m-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4" <p className="pt-4 text-md text-mineshaft-100">Environments</p>
> <p className="pt-4 text-sm text-mineshaft-300">
<div className="mb-8 flex justify-between"> Choose which environments will show up in your dashboard like development, staging,
<p className="text-xl font-semibold text-mineshaft-100">Environments</p> production
<div> </p>
<ProjectPermissionCan <div className="flex pt-4">
<Input
value={searchEnv}
onChange={(e) => setSearchEnv(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search environments by name/slug..."
/>
<ProjectPermissionCan
I={ProjectPermissionActions.Create} I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Environments} a={ProjectPermissionSub.Environments}
> >
@@ -86,22 +98,20 @@ export const EnvironmentSection = () => {
} }
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
className="ml-4"
> >
Create environment Create
</Button> </Button>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div>
</div> </div>
<p className="mb-8 text-gray-400"> <div className="py-4">
Choose which environments will show up in your dashboard like development, staging, {permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? (
production <EnvironmentTable handlePopUpOpen={handlePopUpOpen} />
</p> ) : (
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? ( <PermissionDeniedBanner />
<EnvironmentTable handlePopUpOpen={handlePopUpOpen} /> )}
) : ( </div>
<PermissionDeniedBanner />
)}
<AddEnvironmentModal <AddEnvironmentModal
popUp={popUp} popUp={popUp}
handlePopUpClose={handlePopUpClose} handlePopUpClose={handlePopUpClose}

View File

@@ -7,7 +7,7 @@ import { SecretTagsSection } from "../SecretTagsSection";
export const ProjectGeneralTab = () => { export const ProjectGeneralTab = () => {
return ( return (
<div> <div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<ProjectNameChangeSection /> <ProjectNameChangeSection />
<EnvironmentSection /> <EnvironmentSection />
<SecretTagsSection /> <SecretTagsSection />

View File

@@ -9,10 +9,15 @@ import { Button, FormControl, Input } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useRenameWorkspace } from "@app/hooks/api"; import { useRenameWorkspace } from "@app/hooks/api";
import { CopyButton } from "./CopyButton"; // import { CopyButton } from "./CopyButton";
const formSchema = yup.object({ const formSchema = yup.object({
name: yup.string().required().label("Project Name").max(64, "Too long, maximum length is 64 characters"), name: yup.string().required().label("Project Name").max(64, "Too long, maximum length is 64 characters"),
slug: yup
.string()
.matches(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
.required()
.label("Project Slug")
}); });
type FormData = yup.InferType<typeof formSchema>; type FormData = yup.InferType<typeof formSchema>;
@@ -27,7 +32,8 @@ export const ProjectNameChangeSection = () => {
useEffect(() => { useEffect(() => {
if (currentWorkspace) { if (currentWorkspace) {
reset({ reset({
name: currentWorkspace.name name: currentWorkspace.name,
slug: currentWorkspace.slug
}); });
} }
}, [currentWorkspace]); }, [currentWorkspace]);
@@ -55,11 +61,47 @@ export const ProjectNameChangeSection = () => {
}; };
return ( return (
<form <form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
onSubmit={handleSubmit(onFormSubmit)} <div>
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4" <h2 className="mb-2 text-md text-mineshaft-100">Project Name</h2>
> <Controller
<div className="justify-betweens flex"> defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
<Input placeholder="Project Echo" {...field} />
</FormControl>
)}
control={control}
name="name"
/>
</div>
{/* <div className="py-4">
<h2 className="mb-2 text-md text-mineshaft-100">Project Slug</h2>
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
<Input placeholder="echo" {...field} />
</FormControl>
)}
control={control}
name="slug"
/>
</div> */}
{/* <div className="py-4">
<h2 className="mb-2 text-md text-mineshaft-100">Project ID</h2>
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
<Input placeholder="echo" {...field} />
</FormControl>
)}
control={control}
name="slug"
/>
</div> */}
{/* <div className="justify-betweens flex">
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Name</h2> <h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Name</h2>
<div className="space-x-2"> <div className="space-x-2">
<CopyButton <CopyButton
@@ -77,8 +119,8 @@ export const ProjectNameChangeSection = () => {
Copy Project ID Copy Project ID
</CopyButton> </CopyButton>
</div> </div>
</div> </div> */}
<div className="max-w-md"> {/* <div className="max-w-md">
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => ( {(isAllowed) => (
<Controller <Controller
@@ -98,7 +140,7 @@ export const ProjectNameChangeSection = () => {
/> />
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div> */}
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button

View File

@@ -0,0 +1,41 @@
import { Fragment } from "react";
import { Tab } from "@headlessui/react";
import { ProjectGeneralTab } from "../ProjectGeneralTab";
import { WebhooksTab } from "../WebhooksTab";
const tabs = [
{ name: "General", key: "tab-project-general" },
{ name: "Webhooks", key: "tab-project-webhooks" }
];
export const ProjectTabGroup = () => {
return (
<Tab.Group>
<Tab.List className="mb-4 w-full border-b-2 border-mineshaft-800">
{tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}>
{({ selected }) => (
<button
type="button"
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
selected ? "border-b border-white text-white" : "text-mineshaft-400"
}`}
>
{tab.name}
</button>
)}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<ProjectGeneralTab />
</Tab.Panel>
<Tab.Panel>
<WebhooksTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
}

View File

@@ -0,0 +1 @@
export { ProjectTabGroup } from "./ProjectTabGroup";

View File

@@ -1,9 +1,23 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { useState } from "react";
import { faMagnifyingGlass, faPlus, faTags,faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions"; import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2"; import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Input,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr} from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
@@ -11,10 +25,12 @@ import {
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteWsTag } from "@app/hooks/api"; import {
useDeleteWsTag,
useGetWsTags
} from "@app/hooks/api";
import { AddSecretTagModal } from "./AddSecretTagModal"; import { AddSecretTagModal } from "./AddSecretTagModal";
import { SecretTagsTable } from "./SecretTagsTable";
type DeleteModalData = { name: string; id: string }; type DeleteModalData = { name: string; id: string };
@@ -25,9 +41,10 @@ export const SecretTagsSection = (): JSX.Element => {
"deleteTagConfirmation" "deleteTagConfirmation"
] as const); ] as const);
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: tags, isLoading } = useGetWsTags(currentWorkspace?.id ?? "");
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const deleteWsTag = useDeleteWsTag(); const deleteWsTag = useDeleteWsTag();
const [searchTag, setSearchTag] = useState("");
const onDeleteApproved = async () => { const onDeleteApproved = async () => {
try { try {
@@ -51,10 +68,28 @@ export const SecretTagsSection = (): JSX.Element => {
} }
}; };
const filteredTags = tags
? tags.filter(({ name }) => name.toLocaleLowerCase().includes(searchTag.toLocaleLowerCase()))
: [];
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div>
<div className="mb-8 flex justify-between"> <hr className="border-mineshaft-600" />
<p className="mb-3 text-xl font-semibold">Secret Tags</p> <div className="flex items-center justify-between pt-4">
<p className="text-md text-mineshaft-100">Secret Tags</p>
</div>
<p className="pt-4 text-sm text-mineshaft-300">
Every secret can be assigned to one or more tags. Here you can add and remove tags for the
current project.
</p>
<div className="pt-4 flex">
<Input
value={searchTag}
onChange={(e) => setSearchTag(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search tags by name/slug..."
/>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}> <ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}>
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
@@ -64,21 +99,67 @@ export const SecretTagsSection = (): JSX.Element => {
handlePopUpOpen("CreateSecretTag"); handlePopUpOpen("CreateSecretTag");
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
className="ml-4"
> >
Create tag Create
</Button> </Button>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
<p className="mb-8 text-gray-400"> <div className="py-4">
Every secret can be assigned to one or more tags. Here you can add and remove tags for the {permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? (
current project. <TableContainer>
</p> <Table>
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? ( <THead>
<SecretTagsTable handlePopUpOpen={handlePopUpOpen} /> <Tr>
) : ( <Th>Name</Th>
<PermissionDeniedBanner /> <Th>Slug</Th>
<Th aria-label="button" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
{filteredTags?.map(({ id, name, slug }) => (
<Tr key={name}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td className="flex items-center justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Tags}
>
{(isAllowed) => (
<IconButton
onClick={() =>
handlePopUpOpen("deleteTagConfirmation", {
name,
id
})
}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
))}
</TBody>
</Table>
{!isLoading && filteredTags?.length === 0 && (
<EmptyState title="No secret tags found" icon={faTags} />
)} )}
</TableContainer>
) : (
<PermissionDeniedBanner />
)}
</div>
<AddSecretTagModal <AddSecretTagModal
popUp={popUp} popUp={popUp}
handlePopUpClose={handlePopUpClose} handlePopUpClose={handlePopUpClose}

View File

@@ -1,91 +0,0 @@
import { faTags, faTrashCan } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useGetWsTags } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteTagConfirmation"]>,
{
name,
id
}: {
name: string;
id: string;
}
) => void;
};
export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWsTags(currentWorkspace?.id ?? "");
return (
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Tag</Th>
<Th>Slug</Th>
<Th aria-label="button" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
{!isLoading &&
data &&
data.map(({ id, name, slug }) => (
<Tr key={name}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td className="flex items-center justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Tags}
>
{(isAllowed) => (
<IconButton
onClick={() =>
handlePopUpOpen("deleteTagConfirmation", {
name,
id
})
}
colorSchema="danger"
ariaLabel="update"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faTrashCan} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
))}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={3}>
<EmptyState title="No secret tags found" icon={faTags} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
);
};

View File

@@ -139,7 +139,7 @@ export const WebhooksTab = withProjectPermission(
}; };
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">{t("settings.webhooks.title")}</p> <p className="text-xl font-semibold text-mineshaft-100">{t("settings.webhooks.title")}</p>
<ProjectPermissionCan <ProjectPermissionCan

View File

@@ -3,4 +3,5 @@ export { DeleteProjectSection } from "./DeleteProjectSection";
export { E2EESection } from "./E2EESection"; export { E2EESection } from "./E2EESection";
export { EnvironmentSection } from "./EnvironmentSection"; export { EnvironmentSection } from "./EnvironmentSection";
export { ProjectNameChangeSection } from "./ProjectNameChangeSection"; export { ProjectNameChangeSection } from "./ProjectNameChangeSection";
export { ProjectTabGroup } from "./ProjectTabGroup";
export { SecretTagsSection } from "./SecretTagsSection"; export { SecretTagsSection } from "./SecretTagsSection";