diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index 38f03f8bfb..527a847304 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -28,6 +28,7 @@ interface FeatureSet { customRateLimits: boolean; customAlerts: boolean; auditLogs: boolean; + envLimit?: number | null; } /** @@ -55,7 +56,8 @@ class EELicenseService { rbac: true, customRateLimits: true, customAlerts: true, - auditLogs: false + auditLogs: false, + envLimit: null } public localFeatureSet: NodeCache; diff --git a/frontend/src/components/basic/dialog/AddProjectMemberDialog.tsx b/frontend/src/components/basic/dialog/AddProjectMemberDialog.tsx index ec3f1a9f52..aceeb9db9f 100644 --- a/frontend/src/components/basic/dialog/AddProjectMemberDialog.tsx +++ b/frontend/src/components/basic/dialog/AddProjectMemberDialog.tsx @@ -53,7 +53,7 @@ const AddProjectMemberDialog = ({ leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - + {data?.length > 0 ? ( {t('section.members.add-dialog.already-all-invited')} diff --git a/frontend/src/components/basic/table/ProjectUsersTable.tsx b/frontend/src/components/basic/table/ProjectUsersTable.tsx index c78a6f6c29..0f6df98d3b 100644 --- a/frontend/src/components/basic/table/ProjectUsersTable.tsx +++ b/frontend/src/components/basic/table/ProjectUsersTable.tsx @@ -25,6 +25,7 @@ type Props = { changeData: (users: any[]) => void; myUser: string; filter: string; + isUserListLoading: boolean; }; type EnvironmentProps = { @@ -38,7 +39,7 @@ type EnvironmentProps = { * @param {*} props * @returns */ -const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { +const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => { const [roleSelected, setRoleSelected] = useState( Array(userData?.length).fill(userData.map((user) => user.role)) ); @@ -205,7 +206,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { }; return ( -
+
{ text="You can change user permissions if you switch to Infisical's Professional plan." /> - + @@ -231,7 +232,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { - {userData?.filter( + {!isUserListLoading && userData?.filter( (user) => user.firstName?.toLowerCase().includes(filter) || user.lastName?.toLowerCase().includes(filter) || @@ -245,14 +246,14 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { user.email?.toLowerCase().includes(filter) ) .map((row, index) => ( - - + - - ))} + {isUserListLoading && <> + + + }
NAME EMAIL
+
{row.firstName} {row.lastName} + {row.email} +
diff --git a/frontend/src/components/v2/EmptyState/EmptyState.tsx b/frontend/src/components/v2/EmptyState/EmptyState.tsx index 70bf234d3d..b962bd25c0 100644 --- a/frontend/src/components/v2/EmptyState/EmptyState.tsx +++ b/frontend/src/components/v2/EmptyState/EmptyState.tsx @@ -21,7 +21,7 @@ export const EmptyState = ({ }: Props) => (
diff --git a/frontend/src/components/v2/Table/Table.tsx b/frontend/src/components/v2/Table/Table.tsx index dd4bb4e7be..89a5e2ccdf 100644 --- a/frontend/src/components/v2/Table/Table.tsx +++ b/frontend/src/components/v2/Table/Table.tsx @@ -16,8 +16,8 @@ export const TableContainer = ({ }: TableContainerProps): JSX.Element => (
@@ -34,7 +34,7 @@ export type TableProps = { export const Table = ({ children, className }: TableProps): JSX.Element => ( @@ -49,7 +49,7 @@ export type THeadProps = { }; export const THead = ({ children, className }: THeadProps): JSX.Element => ( - + {children} ); @@ -62,7 +62,7 @@ export type TrProps = { export const Tr = ({ children, className, ...props }: TrProps): JSX.Element => ( {children} @@ -76,7 +76,7 @@ export type ThProps = { }; export const Th = ({ children, className }: ThProps): JSX.Element => ( - + ); // table body diff --git a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx index 694d6d628b..a16f78b994 100644 --- a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx +++ b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx @@ -18,7 +18,7 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele href={`/settings/billing/${localStorage.getItem('projectData.id') as string}`} key="upgrade-plan" > - + , - - - - - - + + {payloadOpened && ( - - - - +
+
+
+
{String(t('common.timestamp'))}
+
{row.createdAt}
+
+
)} {payloadOpened && row.payload?.map( (action) => action.secretVersions.length > 0 && ( -
- - - +
+
+
{t(`activity.event.${action.name}`)}
+ +
+
) )} {payloadOpened && ( - - - - +
+
+
+
{String(t('activity.ip-address'))}
+
{row.ipAddress}
+
+
)} ); @@ -147,47 +152,48 @@ const ActivityTable = ({ return (
-
-
-
{children}{children}
null} +
+
+ +
{row.payload ?.map( (action) => `${String(action.secretVersions.length)} ${t(`activity.event.${action.name}`)}` ) .join(' and ')} -
{renderUser()}{row.channel} + +
{renderUser()}
+
{row.channel}
+
{timeSince(new Date(row.createdAt))} -
- {String(t('common.timestamp'))}{row.createdAt}
- {t(`activity.event.${action.name}`)} null} - className="cursor-pointer text-primary-300 duration-200 hover:text-primary" - onClick={() => toggleSidebar(action._id)} - > - {action.secretVersions.length + - (action.secretVersions.length !== 1 ? ' secrets' : ' secret')} - -
- {String(t('activity.ip-address'))}{row.ipAddress}
- - - - - - - - - - {data?.map((row, index) => ( - - ))} - -
- - {String(t('common.event')).toUpperCase()} - - {String(t('common.user')).toUpperCase()} - - {String(t('common.source')).toUpperCase()} - - {String(t('common.time')).toUpperCase()} - -
+
+ {/*
*/} +
+
+
+ +
+
+ {String(t('common.event')).toUpperCase()} +
+
+ {String(t('common.user')).toUpperCase()} +
+
+ {String(t('common.source')).toUpperCase()} +
+
+ {String(t('common.time')).toUpperCase()} +
+
+
+
+ {data?.map((row, index) => ( + + ))} +
{isLoading && ( -
- loading animation -
+
)}
); diff --git a/frontend/src/hooks/api/subscriptions/queries.tsx b/frontend/src/hooks/api/subscriptions/queries.tsx index 4a65912efa..7d2b2d7293 100644 --- a/frontend/src/hooks/api/subscriptions/queries.tsx +++ b/frontend/src/hooks/api/subscriptions/queries.tsx @@ -2,20 +2,20 @@ import { useQuery } from '@tanstack/react-query'; import { apiRequest } from '@app/config/request'; -import { GetSubscriptionPlan } from './types'; +import { SubscriptionPlan } from './types'; // import { Workspace } from './types'; const subscriptionKeys = { - getOrgSubsription: (orgID: string) => ['subscription', { orgID }] as const + getOrgSubsription: (orgID: string) => ['plan', { orgID }] as const }; const fetchOrgSubscription = async (orgID: string) => { - const { data } = await apiRequest.get<{ subscriptions: GetSubscriptionPlan }>( - `/api/v1/organization/${orgID}/subscriptions` + const { data } = await apiRequest.get<{ plan: SubscriptionPlan }>( + `/api/v1/organizations/${orgID}/plan` ); - return data.subscriptions; + return data.plan; }; type UseGetOrgSubscriptionProps = { diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts index f9dfcbf0b5..5d01611bd0 100644 --- a/frontend/src/hooks/api/subscriptions/types.ts +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -1,25 +1,16 @@ -export type GetSubscriptionPlan = { - data: { plan: SubscriptionPlan }[]; -}; - export type SubscriptionPlan = { - id: string; - object: string; - active: boolean; - aggregate_usage: unknown; - amount: 1400; - amount_decimal: 1400; - billing_scheme: string; - created: 1674833546; - currency: string; - interval: string; - interval_count: 1; - livemode: false; - metadata: {}; - nickname: null; - product: string; - tiers_mode: unknown; - transform_usage: unknown; - trial_period_days: unknown; - usage_type: string; + _id: string; + membersUsed: number; + membersLimit: number; + auditLogs: boolean; + customAlerts: boolean; + customRateLimits: boolean; + pitRecovery: boolean; + rbac: boolean; + secretVersioning: boolean; + slug: string; + tier: number; + workspaceLimit: number; + workspacesUsed: number; + envLimit: number; }; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 938d9e4285..27cb014cbb 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -31,7 +31,6 @@ import { SelectItem, UpgradePlanModal } from '@app/components/v2'; -import { plans } from '@app/const'; import { useOrganization, useSubscription, useUser, useWorkspace } from '@app/context'; import { usePopUp } from '@app/hooks'; import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useUploadWsKey } from '@app/hooks/api'; @@ -59,10 +58,10 @@ export const AppLayout = ({ children }: LayoutProps) => { const { workspaces, currentWorkspace } = useWorkspace(); const { currentOrg } = useOrganization(); const { user } = useUser(); - const { subscriptionPlan } = useSubscription(); + const { subscription } = useSubscription(); + const host = window.location.origin; - const isAddingProjectsAllowed = - subscriptionPlan !== plans.starter || (subscriptionPlan === plans.starter && workspaces.length < 3) || host !== 'https://app.infisical.com'; + const isAddingProjectsAllowed = ((subscription?.workspacesUsed || 0) < (subscription?.workspaceLimit || 1)) || host !== 'https://app.infisical.com'; const createWs = useCreateWorkspace(); const uploadWsKey = useUploadWsKey(); @@ -104,7 +103,7 @@ export const AppLayout = ({ children }: LayoutProps) => { }); const userWorkspaces = orgUserProjects; if ( - (userWorkspaces.length === 0 && + (userWorkspaces?.length === 0 && router.asPath !== '/noprojects' && !router.asPath.includes('home') && !router.asPath.includes('settings')) || diff --git a/frontend/src/pages/activity/[id].tsx b/frontend/src/pages/activity/[id].tsx index 8f2694713b..ec18939585 100644 --- a/frontend/src/pages/activity/[id].tsx +++ b/frontend/src/pages/activity/[id].tsx @@ -6,7 +6,10 @@ import { useRouter } from 'next/router'; import Button from '@app/components/basic/buttons/Button'; import EventFilter from '@app/components/basic/EventFilter'; import NavHeader from '@app/components/navigation/NavHeader'; +import { UpgradePlanModal } from '@app/components/v2'; +import { useSubscription } from '@app/context'; import ActivitySideBar from '@app/ee/components/ActivitySideBar'; +import { usePopUp } from '@app/hooks/usePopUp'; import getProjectLogs from '../../ee/api/secrets/GetProjectLogs'; import ActivityTable from '../../ee/components/ActivityTable'; @@ -67,6 +70,10 @@ export default function Activity() { const currentLimit = 10; const [currentSidebarAction, toggleSidebar] = useState(); const { t } = useTranslation(); + const { subscription } = useSubscription(); + const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + 'upgradePlan' + ] as const); // this use effect updates the data in case of a new filter being added useEffect(() => { @@ -137,11 +144,15 @@ export default function Activity() { }, [currentLimit, currentOffset]); const loadMoreLogs = () => { - setCurrentOffset(currentOffset + currentLimit); + if (subscription?.auditLogs === false) { + handlePopUpOpen('upgradePlan'); + } else { + setCurrentOffset(currentOffset + currentLimit); + } }; return ( -
+
Audit Logs @@ -173,6 +184,11 @@ export default function Activity() { />
+ handlePopUpClose('upgradePlan')} + text="You can see more logs if you switch to Infisical's Business/Professional Plan." + />
); } diff --git a/frontend/src/pages/users/[id].tsx b/frontend/src/pages/users/[id].tsx index 3153f9935b..bfdbf83053 100644 --- a/frontend/src/pages/users/[id].tsx +++ b/frontend/src/pages/users/[id].tsx @@ -11,6 +11,7 @@ import AddProjectMemberDialog from '@app/components/basic/dialog/AddProjectMembe import ProjectUsersTable from '@app/components/basic/table/ProjectUsersTable'; import NavHeader from '@app/components/navigation/NavHeader'; import guidGenerator from '@app/components/utilities/randomId'; +import { Input } from '@app/components/v2'; import { decryptAssymmetric, @@ -56,6 +57,7 @@ export default function Users() { const workspaceId = router.query.id as string; const [userList, setUserList] = useState([]); + const [isUserListLoading, setIsUserListLoading] = useState(true); const [orgUserList, setOrgUserList] = useState([]); useEffect(() => { @@ -81,6 +83,8 @@ export default function Users() { })); setUserList(tempUserList); + setIsUserListLoading(false); + // This is needed to know wha users from an org (if any), we are able to add to a certain project const orgUsers = await getOrganizationUsers({ orgId: String(localStorage.getItem('orgData.id')) @@ -151,9 +155,8 @@ export default function Users() { -
+

{t('settings.members.title')}

-

{t('settings.members.description')}

{/* */} -
-
- - +
+ setSearchUsers(e.target.value)} - placeholder={String(t('section.members.search-members'))} + leftIcon={} />
-
+