mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +00:00
lots of frontend improvements
This commit is contained in:
@@ -28,6 +28,7 @@ interface FeatureSet {
|
|||||||
customRateLimits: boolean;
|
customRateLimits: boolean;
|
||||||
customAlerts: boolean;
|
customAlerts: boolean;
|
||||||
auditLogs: boolean;
|
auditLogs: boolean;
|
||||||
|
envLimit?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +56,8 @@ class EELicenseService {
|
|||||||
rbac: true,
|
rbac: true,
|
||||||
customRateLimits: true,
|
customRateLimits: true,
|
||||||
customAlerts: true,
|
customAlerts: true,
|
||||||
auditLogs: false
|
auditLogs: false,
|
||||||
|
envLimit: null
|
||||||
}
|
}
|
||||||
|
|
||||||
public localFeatureSet: NodeCache;
|
public localFeatureSet: NodeCache;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const AddProjectMemberDialog = ({
|
|||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
{data?.length > 0 ? (
|
{data?.length > 0 ? (
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
@@ -64,7 +64,7 @@ const AddProjectMemberDialog = ({
|
|||||||
) : (
|
) : (
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="z-50 text-lg font-medium leading-6 text-gray-400"
|
className="z-50 text-lg font-medium text-mineshaft-300 mb-4"
|
||||||
>
|
>
|
||||||
{t('section.members.add-dialog.already-all-invited')}
|
{t('section.members.add-dialog.already-all-invited')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type Props = {
|
|||||||
changeData: (users: any[]) => void;
|
changeData: (users: any[]) => void;
|
||||||
myUser: string;
|
myUser: string;
|
||||||
filter: string;
|
filter: string;
|
||||||
|
isUserListLoading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EnvironmentProps = {
|
type EnvironmentProps = {
|
||||||
@@ -38,7 +39,7 @@ type EnvironmentProps = {
|
|||||||
* @param {*} props
|
* @param {*} props
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
|
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
|
||||||
const [roleSelected, setRoleSelected] = useState(
|
const [roleSelected, setRoleSelected] = useState(
|
||||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
Array(userData?.length).fill(userData.map((user) => user.role))
|
||||||
);
|
);
|
||||||
@@ -205,7 +206,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-container relative mb-6 mt-1 min-w-max rounded-md border border-mineshaft-700 bg-bunker">
|
<div className="table-container relative mb-6 mt-1 min-w-max rounded-md border border-mineshaft-600 bg-bunker">
|
||||||
<div className="absolute h-[3.1rem] w-full rounded-t-md bg-white/5" />
|
<div className="absolute h-[3.1rem] w-full rounded-t-md bg-white/5" />
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={isUpgradeModalOpen}
|
isOpen={isUpgradeModalOpen}
|
||||||
@@ -213,7 +214,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
|
|||||||
text="You can change user permissions if you switch to Infisical's Professional plan."
|
text="You can change user permissions if you switch to Infisical's Professional plan."
|
||||||
/>
|
/>
|
||||||
<table className="my-0.5 w-full">
|
<table className="my-0.5 w-full">
|
||||||
<thead className="text-xs font-light text-gray-400">
|
<thead className="text-xs font-light text-gray-400 bg-mineshaft-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="py-3.5 pl-4 text-left">NAME</th>
|
<th className="py-3.5 pl-4 text-left">NAME</th>
|
||||||
<th className="py-3.5 pl-4 text-left">EMAIL</th>
|
<th className="py-3.5 pl-4 text-left">EMAIL</th>
|
||||||
@@ -231,7 +232,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{userData?.filter(
|
{!isUserListLoading && userData?.filter(
|
||||||
(user) =>
|
(user) =>
|
||||||
user.firstName?.toLowerCase().includes(filter) ||
|
user.firstName?.toLowerCase().includes(filter) ||
|
||||||
user.lastName?.toLowerCase().includes(filter) ||
|
user.lastName?.toLowerCase().includes(filter) ||
|
||||||
@@ -245,14 +246,14 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
|
|||||||
user.email?.toLowerCase().includes(filter)
|
user.email?.toLowerCase().includes(filter)
|
||||||
)
|
)
|
||||||
.map((row, index) => (
|
.map((row, index) => (
|
||||||
<tr key={guidGenerator()} className="bg-bunker-600 text-sm hover:bg-bunker-500">
|
<tr key={guidGenerator()} className="bg-mineshaft-800 text-sm">
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-4 text-gray-300">
|
<td className="border-t border-mineshaft-600 py-2 pl-4 text-gray-300">
|
||||||
{row.firstName} {row.lastName}
|
{row.firstName} {row.lastName}
|
||||||
</td>
|
</td>
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-4 text-gray-300">
|
<td className="border-t border-mineshaft-600 py-2 pl-4 text-gray-300">
|
||||||
{row.email}
|
{row.email}
|
||||||
</td>
|
</td>
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-6 pr-10 text-gray-300">
|
<td className="border-t border-mineshaft-600 py-2 pl-6 pr-10 text-gray-300">
|
||||||
<div className="flex h-full flex-row items-center justify-start">
|
<div className="flex h-full flex-row items-center justify-start">
|
||||||
<Select
|
<Select
|
||||||
className="w-36 bg-mineshaft-700"
|
className="w-36 bg-mineshaft-700"
|
||||||
@@ -391,6 +392,10 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
{isUserListLoading && <>
|
||||||
|
<tr key={guidGenerator()} className="bg-mineshaft-800 text-sm animate-pulse h-14 w-full"/>
|
||||||
|
<tr key={guidGenerator()} className="bg-mineshaft-800 text-sm animate-pulse h-14 w-full"/>
|
||||||
|
</>}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const EmptyState = ({
|
|||||||
}: Props) => (
|
}: Props) => (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'flex w-full flex-col items-center bg-bunker-700 px-2 pt-6 text-bunker-300',
|
'flex w-full flex-col items-center bg-mineshaft-800 px-2 pt-6 text-bunker-300',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const TableContainer = ({
|
|||||||
}: TableContainerProps): JSX.Element => (
|
}: TableContainerProps): JSX.Element => (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative w-full overflow-x-auto border border-solid border-mineshaft-700 bg-mineshaft-800 font-inter shadow-md',
|
'relative w-full overflow-x-auto border border-solid border-mineshaft-700 bg-mineshaft-800 font-inter',
|
||||||
isRounded && 'rounded-md',
|
isRounded && 'rounded-lg',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -34,7 +34,7 @@ export type TableProps = {
|
|||||||
export const Table = ({ children, className }: TableProps): JSX.Element => (
|
export const Table = ({ children, className }: TableProps): JSX.Element => (
|
||||||
<table
|
<table
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'w-full rounded-md bg-bunker-800 p-2 text-left text-sm text-gray-300',
|
'w-full bg-mineshaft-800 p-2 text-left text-sm text-gray-300',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -49,7 +49,7 @@ export type THeadProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const THead = ({ children, className }: THeadProps): JSX.Element => (
|
export const THead = ({ children, className }: THeadProps): JSX.Element => (
|
||||||
<thead className={twMerge('bg-bunker text-xs uppercase text-bunker-300', className)}>
|
<thead className={twMerge('bg-mineshaft-800 text-xs uppercase text-bunker-300', className)}>
|
||||||
{children}
|
{children}
|
||||||
</thead>
|
</thead>
|
||||||
);
|
);
|
||||||
@@ -62,7 +62,7 @@ export type TrProps = {
|
|||||||
|
|
||||||
export const Tr = ({ children, className, ...props }: TrProps): JSX.Element => (
|
export const Tr = ({ children, className, ...props }: TrProps): JSX.Element => (
|
||||||
<tr
|
<tr
|
||||||
className={twMerge('border border-solid border-mineshaft-700 hover:bg-bunker-700', className)}
|
className={twMerge('border border-solid border-mineshaft-700 cursor-default', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -76,7 +76,7 @@ export type ThProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Th = ({ children, className }: ThProps): JSX.Element => (
|
export const Th = ({ children, className }: ThProps): JSX.Element => (
|
||||||
<th className={twMerge('bg-bunker-500 px-5 pt-4 pb-3.5 font-semibold', className)}>{children}</th>
|
<th className={twMerge('bg-mineshaft-800 px-5 pt-4 pb-3.5 font-semibold border-b-2 border-mineshaft-600', className)}>{children}</th>
|
||||||
);
|
);
|
||||||
|
|
||||||
// table body
|
// table body
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
|
|||||||
href={`/settings/billing/${localStorage.getItem('projectData.id') as string}`}
|
href={`/settings/billing/${localStorage.getItem('projectData.id') as string}`}
|
||||||
key="upgrade-plan"
|
key="upgrade-plan"
|
||||||
>
|
>
|
||||||
<Button className="mr-4 ml-2">Upgrade Plan</Button>
|
<Button className="mr-4 ml-2 mb-2">Upgrade Plan</Button>
|
||||||
</Link>,
|
</Link>,
|
||||||
<ModalClose asChild key="upgrade-plan-cancel">
|
<ModalClose asChild key="upgrade-plan-cancel">
|
||||||
<Button colorSchema="secondary" variant="plain">
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { createContext, ReactNode, useContext, useMemo } from 'react';
|
import { createContext, ReactNode, useContext, useMemo } from 'react';
|
||||||
|
|
||||||
import { useGetOrgSubscription } from '@app/hooks/api';
|
import { useGetOrgSubscription } from '@app/hooks/api';
|
||||||
import { GetSubscriptionPlan } from '@app/hooks/api/types';
|
import { SubscriptionPlan } from '@app/hooks/api/types';
|
||||||
|
|
||||||
import { useWorkspace } from '../WorkspaceContext';
|
import { useWorkspace } from '../WorkspaceContext';
|
||||||
// import { Subscription } from '@app/hooks/api/workspace/types';
|
// import { Subscription } from '@app/hooks/api/workspace/types';
|
||||||
|
|
||||||
type TSubscriptionContext = {
|
type TSubscriptionContext = {
|
||||||
subscription?: GetSubscriptionPlan;
|
subscription?: SubscriptionPlan;
|
||||||
subscriptionPlan: string;
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ export const SubscriptionProvider = ({ children }: Props): JSX.Element => {
|
|||||||
const value = useMemo<TSubscriptionContext>(
|
const value = useMemo<TSubscriptionContext>(
|
||||||
() => ({
|
() => ({
|
||||||
subscription: data,
|
subscription: data,
|
||||||
subscriptionPlan: data?.data?.[0]?.plan?.product || '',
|
|
||||||
isLoading
|
isLoading
|
||||||
}),
|
}),
|
||||||
[data, isLoading]
|
[data, isLoading]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Image from 'next/image';
|
|
||||||
import { faAngleDown, faAngleRight, faUpRightFromSquare } from '@fortawesome/free-solid-svg-icons';
|
import { faAngleDown, faAngleRight, faUpRightFromSquare } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ const ActivityLogsRow = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderUser = () => {
|
const renderUser = () => {
|
||||||
if (row?.user) return `User: ${row.user}`;
|
if (row?.user) return `${row.user}`;
|
||||||
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
|
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
|
||||||
if (row?.serviceTokenData.name) return `Service Token: ${row.serviceTokenData.name}`;
|
if (row?.serviceTokenData.name) return `Service Token: ${row.serviceTokenData.name}`;
|
||||||
|
|
||||||
@@ -56,71 +55,77 @@ const ActivityLogsRow = ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr key={guidGenerator()} className="w-full bg-bunker-800 text-sm duration-100">
|
<div key={guidGenerator()} className="w-full bg-mineshaft-800 text-sm text-mineshaft-200 duration-100 flex flex-row items-center">
|
||||||
<td
|
<button
|
||||||
onKeyDown={() => null}
|
type="button"
|
||||||
onClick={() => setPayloadOpened(!payloadOpened)}
|
onClick={() => setPayloadOpened(!payloadOpened)}
|
||||||
className="flex cursor-pointer items-center border-t border-mineshaft-700 text-gray-300"
|
className="border-t border-mineshaft-700 pt-[0.58rem]"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={payloadOpened ? faAngleDown : faAngleRight}
|
icon={payloadOpened ? faAngleDown : faAngleRight}
|
||||||
className={`mt-2.5 ml-6 text-bunker-100 hover:bg-mineshaft-700 ${
|
className={`ml-6 mb-2 text-mineshaft-300 cursor-pointer ${
|
||||||
payloadOpened && 'bg-mineshaft-500'
|
payloadOpened ? 'bg-mineshaft-500 hover:bg-mineshaft-500' : 'hover:bg-mineshaft-700'
|
||||||
} h-4 w-4 rounded-md p-1 duration-100`}
|
} h-4 w-4 rounded-md p-1 duration-100`}
|
||||||
/>
|
/>
|
||||||
</td>
|
</button>
|
||||||
<td className="border-t border-mineshaft-700 py-3 text-gray-300">
|
<div className="border-t border-mineshaft-700 py-3 w-1/4 pl-6">
|
||||||
{row.payload
|
{row.payload
|
||||||
?.map(
|
?.map(
|
||||||
(action) =>
|
(action) =>
|
||||||
`${String(action.secretVersions.length)} ${t(`activity.event.${action.name}`)}`
|
`${String(action.secretVersions.length)} ${t(`activity.event.${action.name}`)}`
|
||||||
)
|
)
|
||||||
.join(' and ')}
|
.join(' and ')}
|
||||||
</td>
|
</div>
|
||||||
<td className="border-t border-mineshaft-700 py-3 pl-6 text-gray-300">{renderUser()}</td>
|
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">{renderUser()}</div>
|
||||||
<td className="border-t border-mineshaft-700 py-3 pl-6 text-gray-300">{row.channel}</td>
|
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">{row.channel}</div>
|
||||||
<td className="border-t border-mineshaft-700 py-3 pl-6 text-gray-300">
|
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">
|
||||||
{timeSince(new Date(row.createdAt))}
|
{timeSince(new Date(row.createdAt))}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
{payloadOpened && (
|
{payloadOpened && (
|
||||||
<tr className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200">
|
<div className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center">
|
||||||
<td />
|
<div className='max-w-xl w-full flex flex-row items-center'>
|
||||||
<td>{String(t('common.timestamp'))}</td>
|
<div className='w-24' />
|
||||||
<td>{row.createdAt}</td>
|
<div className='w-1/2'>{String(t('common.timestamp'))}</div>
|
||||||
</tr>
|
<div className='w-1/2'>{row.createdAt}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{payloadOpened &&
|
{payloadOpened &&
|
||||||
row.payload?.map(
|
row.payload?.map(
|
||||||
(action) =>
|
(action) =>
|
||||||
action.secretVersions.length > 0 && (
|
action.secretVersions.length > 0 && (
|
||||||
<tr
|
<div
|
||||||
key={action._id}
|
key={action.name}
|
||||||
className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200"
|
className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center"
|
||||||
>
|
>
|
||||||
<td />
|
<div className='max-w-xl w-full flex flex-row items-center'>
|
||||||
<td className="">{t(`activity.event.${action.name}`)}</td>
|
<div className='w-24' />
|
||||||
<td
|
<div className='w-1/2'>{t(`activity.event.${action.name}`)}</div>
|
||||||
onKeyDown={() => null}
|
<button
|
||||||
className="cursor-pointer text-primary-300 duration-200 hover:text-primary"
|
type="button"
|
||||||
onClick={() => toggleSidebar(action._id)}
|
onClick={() => toggleSidebar(action._id)}
|
||||||
>
|
className='w-1/2 text-primary-300 hover:text-primary-500 flex flex-row justify-left items-center duration-100'
|
||||||
{action.secretVersions.length +
|
>
|
||||||
(action.secretVersions.length !== 1 ? ' secrets' : ' secret')}
|
{action.secretVersions.length +
|
||||||
<FontAwesomeIcon
|
(action.secretVersions.length !== 1 ? ' secrets' : ' secret')}
|
||||||
icon={faUpRightFromSquare}
|
<FontAwesomeIcon
|
||||||
className="ml-2 mb-0.5 h-3 w-3 font-light"
|
icon={faUpRightFromSquare}
|
||||||
/>
|
className="ml-2 mb-0.5 h-3 w-3 font-light"
|
||||||
</td>
|
/>
|
||||||
</tr>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{payloadOpened && (
|
{payloadOpened && (
|
||||||
<tr className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200">
|
<div className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center">
|
||||||
<td />
|
<div className='max-w-xl w-full flex flex-row items-center'>
|
||||||
<td>{String(t('activity.ip-address'))}</td>
|
<div className='w-24' />
|
||||||
<td>{row.ipAddress}</td>
|
<div className='w-1/2'>{String(t('activity.ip-address'))}</div>
|
||||||
</tr>
|
<div className='w-1/2'>{row.ipAddress}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -147,47 +152,48 @@ const ActivityTable = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-8 w-full px-6">
|
<div className="mt-8 w-full px-6">
|
||||||
<div className="table-container relative mb-6 w-full rounded-md border border-mineshaft-700 bg-bunker">
|
<div className="table-container relative mb-6 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800">
|
||||||
<div className="absolute h-[3rem] w-full rounded-t-md bg-white/5" />
|
{/* <div className="absolute h-[3rem] w-full rounded-t-md bg-white/5" /> */}
|
||||||
<table className="my-1 w-full">
|
<div className="my-1 w-full">
|
||||||
<thead className="text-bunker-300">
|
<div className="text-bunker-300 border-b border-mineshaft-600">
|
||||||
<tr className="text-sm">
|
<div className="text-sm flex flex-row w-full">
|
||||||
<th aria-label="actions" className="pl-6 pt-2.5 pb-3 text-left" />
|
<button
|
||||||
<th className="pt-2.5 pb-3 text-left font-semibold">
|
type="button"
|
||||||
{String(t('common.event')).toUpperCase()}
|
onClick={() => {}}
|
||||||
</th>
|
className="opacity-0"
|
||||||
<th className="pl-6 pt-2.5 pb-3 text-left font-semibold">
|
>
|
||||||
{String(t('common.user')).toUpperCase()}
|
<FontAwesomeIcon
|
||||||
</th>
|
icon={faAngleRight}
|
||||||
<th className="pl-6 pt-2.5 pb-3 text-left font-semibold">
|
className="ml-6 mb-2 text-bunker-100 hover:bg-mineshaft-700 cursor-pointer h-4 w-4 rounded-md p-1 duration-100"
|
||||||
{String(t('common.source')).toUpperCase()}
|
/>
|
||||||
</th>
|
</button>
|
||||||
<th className="pl-6 pt-2.5 pb-3 text-left font-semibold">
|
<div className="flex flex-row justify-between w-full">
|
||||||
{String(t('common.time')).toUpperCase()}
|
<div className="pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||||
</th>
|
{String(t('common.event')).toUpperCase()}
|
||||||
<th aria-label="action" />
|
</div>
|
||||||
</tr>
|
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||||
</thead>
|
{String(t('common.user')).toUpperCase()}
|
||||||
<tbody>
|
</div>
|
||||||
{data?.map((row, index) => (
|
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||||
<ActivityLogsRow
|
{String(t('common.source')).toUpperCase()}
|
||||||
key={`activity.${index + 1}.${row._id}`}
|
</div>
|
||||||
row={row}
|
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||||
toggleSidebar={toggleSidebar}
|
{String(t('common.time')).toUpperCase()}
|
||||||
/>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
{data?.map((row, index) => (
|
||||||
|
<ActivityLogsRow
|
||||||
|
key={`activity.${index + 1}.${row._id}`}
|
||||||
|
row={row}
|
||||||
|
toggleSidebar={toggleSidebar}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="mb-8 mt-4 flex w-full justify-center">
|
<div className="mb-8 mt-4 bg-mineshaft-800 rounded-md h-60 flex w-full justify-center animate-pulse" />
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="loading animation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { apiRequest } from '@app/config/request';
|
import { apiRequest } from '@app/config/request';
|
||||||
|
|
||||||
import { GetSubscriptionPlan } from './types';
|
import { SubscriptionPlan } from './types';
|
||||||
|
|
||||||
// import { Workspace } from './types';
|
// import { Workspace } from './types';
|
||||||
|
|
||||||
const subscriptionKeys = {
|
const subscriptionKeys = {
|
||||||
getOrgSubsription: (orgID: string) => ['subscription', { orgID }] as const
|
getOrgSubsription: (orgID: string) => ['plan', { orgID }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchOrgSubscription = async (orgID: string) => {
|
const fetchOrgSubscription = async (orgID: string) => {
|
||||||
const { data } = await apiRequest.get<{ subscriptions: GetSubscriptionPlan }>(
|
const { data } = await apiRequest.get<{ plan: SubscriptionPlan }>(
|
||||||
`/api/v1/organization/${orgID}/subscriptions`
|
`/api/v1/organizations/${orgID}/plan`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.subscriptions;
|
return data.plan;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseGetOrgSubscriptionProps = {
|
type UseGetOrgSubscriptionProps = {
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
export type GetSubscriptionPlan = {
|
|
||||||
data: { plan: SubscriptionPlan }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SubscriptionPlan = {
|
export type SubscriptionPlan = {
|
||||||
id: string;
|
_id: string;
|
||||||
object: string;
|
membersUsed: number;
|
||||||
active: boolean;
|
membersLimit: number;
|
||||||
aggregate_usage: unknown;
|
auditLogs: boolean;
|
||||||
amount: 1400;
|
customAlerts: boolean;
|
||||||
amount_decimal: 1400;
|
customRateLimits: boolean;
|
||||||
billing_scheme: string;
|
pitRecovery: boolean;
|
||||||
created: 1674833546;
|
rbac: boolean;
|
||||||
currency: string;
|
secretVersioning: boolean;
|
||||||
interval: string;
|
slug: string;
|
||||||
interval_count: 1;
|
tier: number;
|
||||||
livemode: false;
|
workspaceLimit: number;
|
||||||
metadata: {};
|
workspacesUsed: number;
|
||||||
nickname: null;
|
envLimit: number;
|
||||||
product: string;
|
|
||||||
tiers_mode: unknown;
|
|
||||||
transform_usage: unknown;
|
|
||||||
trial_period_days: unknown;
|
|
||||||
usage_type: string;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
SelectItem,
|
SelectItem,
|
||||||
UpgradePlanModal
|
UpgradePlanModal
|
||||||
} from '@app/components/v2';
|
} from '@app/components/v2';
|
||||||
import { plans } from '@app/const';
|
|
||||||
import { useOrganization, useSubscription, useUser, useWorkspace } from '@app/context';
|
import { useOrganization, useSubscription, useUser, useWorkspace } from '@app/context';
|
||||||
import { usePopUp } from '@app/hooks';
|
import { usePopUp } from '@app/hooks';
|
||||||
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useUploadWsKey } from '@app/hooks/api';
|
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useUploadWsKey } from '@app/hooks/api';
|
||||||
@@ -59,10 +58,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
const { workspaces, currentWorkspace } = useWorkspace();
|
const { workspaces, currentWorkspace } = useWorkspace();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { subscriptionPlan } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const host = window.location.origin;
|
const host = window.location.origin;
|
||||||
const isAddingProjectsAllowed =
|
const isAddingProjectsAllowed = ((subscription?.workspacesUsed || 0) < (subscription?.workspaceLimit || 1)) || host !== 'https://app.infisical.com';
|
||||||
subscriptionPlan !== plans.starter || (subscriptionPlan === plans.starter && workspaces.length < 3) || host !== 'https://app.infisical.com';
|
|
||||||
|
|
||||||
const createWs = useCreateWorkspace();
|
const createWs = useCreateWorkspace();
|
||||||
const uploadWsKey = useUploadWsKey();
|
const uploadWsKey = useUploadWsKey();
|
||||||
@@ -104,7 +103,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
});
|
});
|
||||||
const userWorkspaces = orgUserProjects;
|
const userWorkspaces = orgUserProjects;
|
||||||
if (
|
if (
|
||||||
(userWorkspaces.length === 0 &&
|
(userWorkspaces?.length === 0 &&
|
||||||
router.asPath !== '/noprojects' &&
|
router.asPath !== '/noprojects' &&
|
||||||
!router.asPath.includes('home') &&
|
!router.asPath.includes('home') &&
|
||||||
!router.asPath.includes('settings')) ||
|
!router.asPath.includes('settings')) ||
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { useRouter } from 'next/router';
|
|||||||
import Button from '@app/components/basic/buttons/Button';
|
import Button from '@app/components/basic/buttons/Button';
|
||||||
import EventFilter from '@app/components/basic/EventFilter';
|
import EventFilter from '@app/components/basic/EventFilter';
|
||||||
import NavHeader from '@app/components/navigation/NavHeader';
|
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 ActivitySideBar from '@app/ee/components/ActivitySideBar';
|
||||||
|
import { usePopUp } from '@app/hooks/usePopUp';
|
||||||
|
|
||||||
import getProjectLogs from '../../ee/api/secrets/GetProjectLogs';
|
import getProjectLogs from '../../ee/api/secrets/GetProjectLogs';
|
||||||
import ActivityTable from '../../ee/components/ActivityTable';
|
import ActivityTable from '../../ee/components/ActivityTable';
|
||||||
@@ -67,6 +70,10 @@ export default function Activity() {
|
|||||||
const currentLimit = 10;
|
const currentLimit = 10;
|
||||||
const [currentSidebarAction, toggleSidebar] = useState<string>();
|
const [currentSidebarAction, toggleSidebar] = useState<string>();
|
||||||
const { t } = useTranslation();
|
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
|
// this use effect updates the data in case of a new filter being added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -137,11 +144,15 @@ export default function Activity() {
|
|||||||
}, [currentLimit, currentOffset]);
|
}, [currentLimit, currentOffset]);
|
||||||
|
|
||||||
const loadMoreLogs = () => {
|
const loadMoreLogs = () => {
|
||||||
setCurrentOffset(currentOffset + currentLimit);
|
if (subscription?.auditLogs === false) {
|
||||||
|
handlePopUpOpen('upgradePlan');
|
||||||
|
} else {
|
||||||
|
setCurrentOffset(currentOffset + currentLimit);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-6 lg:mx-0 w-full h-screen">
|
<div className="mx-6 lg:mx-0 w-full h-full">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Audit Logs</title>
|
<title>Audit Logs</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
@@ -173,6 +184,11 @@ export default function Activity() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={() => handlePopUpClose('upgradePlan')}
|
||||||
|
text="You can see more logs if you switch to Infisical's Business/Professional Plan."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import AddProjectMemberDialog from '@app/components/basic/dialog/AddProjectMembe
|
|||||||
import ProjectUsersTable from '@app/components/basic/table/ProjectUsersTable';
|
import ProjectUsersTable from '@app/components/basic/table/ProjectUsersTable';
|
||||||
import NavHeader from '@app/components/navigation/NavHeader';
|
import NavHeader from '@app/components/navigation/NavHeader';
|
||||||
import guidGenerator from '@app/components/utilities/randomId';
|
import guidGenerator from '@app/components/utilities/randomId';
|
||||||
|
import { Input } from '@app/components/v2';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
@@ -56,6 +57,7 @@ export default function Users() {
|
|||||||
const workspaceId = router.query.id as string;
|
const workspaceId = router.query.id as string;
|
||||||
|
|
||||||
const [userList, setUserList] = useState<any[]>([]);
|
const [userList, setUserList] = useState<any[]>([]);
|
||||||
|
const [isUserListLoading, setIsUserListLoading] = useState(true);
|
||||||
const [orgUserList, setOrgUserList] = useState<any[]>([]);
|
const [orgUserList, setOrgUserList] = useState<any[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -81,6 +83,8 @@ export default function Users() {
|
|||||||
}));
|
}));
|
||||||
setUserList(tempUserList);
|
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
|
// 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({
|
const orgUsers = await getOrganizationUsers({
|
||||||
orgId: String(localStorage.getItem('orgData.id'))
|
orgId: String(localStorage.getItem('orgData.id'))
|
||||||
@@ -151,9 +155,8 @@ export default function Users() {
|
|||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<NavHeader pageName={t('settings.members.title')} isProjectRelated />
|
<NavHeader pageName={t('settings.members.title')} isProjectRelated />
|
||||||
<div className="flex flex-col items-start justify-start px-6 py-6 pb-4 text-3xl">
|
<div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4">
|
||||||
<p className="mr-4 font-semibold text-white">{t('settings.members.title')}</p>
|
<p className="mr-4 font-semibold text-white">{t('settings.members.title')}</p>
|
||||||
<p className="mr-4 text-base text-gray-400">{t('settings.members.description')}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<AddProjectMemberDialog
|
<AddProjectMemberDialog
|
||||||
isOpen={isAddOpen}
|
isOpen={isAddOpen}
|
||||||
@@ -169,20 +172,17 @@ export default function Users() {
|
|||||||
setEmail={setEmail}
|
setEmail={setEmail}
|
||||||
/>
|
/>
|
||||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||||
<div className="flex w-full flex-row items-start px-6 pb-1">
|
<div className="absolute right-4 top-36 flex w-full flex-row items-start px-6 pb-1">
|
||||||
<div className="mt-2 flex h-10 w-full flex-row items-center rounded-md bg-white/5">
|
<div className="flex w-full max-w-sm flex flex-row ml-auto">
|
||||||
<FontAwesomeIcon
|
<Input
|
||||||
className="rounded-l-md bg-white/5 py-3 pl-4 pr-2 text-gray-400"
|
className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||||
icon={faMagnifyingGlass}
|
placeholder="Search by secret name..."
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className="h-full w-full rounded-r-md bg-white/5 pl-2 text-gray-400 outline-none"
|
|
||||||
value={searchUsers}
|
value={searchUsers}
|
||||||
onChange={(e) => setSearchUsers(e.target.value)}
|
onChange={(e) => setSearchUsers(e.target.value)}
|
||||||
placeholder={String(t('section.members.search-members'))}
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 ml-2 flex min-w-max flex-row items-start justify-start">
|
<div className="ml-2 flex min-w-max flex-row items-start justify-start">
|
||||||
<Button
|
<Button
|
||||||
text={String(t('section.members.add-member'))}
|
text={String(t('section.members.add-member'))}
|
||||||
onButtonPressed={openAddModal}
|
onButtonPressed={openAddModal}
|
||||||
@@ -198,6 +198,7 @@ export default function Users() {
|
|||||||
changeData={setUserList}
|
changeData={setUserList}
|
||||||
myUser={personalEmail}
|
myUser={personalEmail}
|
||||||
filter={searchUsers}
|
filter={searchUsers}
|
||||||
|
isUserListLoading={isUserListLoading}
|
||||||
// onClick={openDeleteModal}
|
// onClick={openDeleteModal}
|
||||||
// deleteUser={deleteMembership}
|
// deleteUser={deleteMembership}
|
||||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { faKey, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
|
||||||
import NavHeader from '@app/components/navigation/NavHeader';
|
import NavHeader from '@app/components/navigation/NavHeader';
|
||||||
import { Button, TableContainer, Tooltip } from '@app/components/v2';
|
import { Button, Input, TableContainer, Tooltip } from '@app/components/v2';
|
||||||
import { useWorkspace } from '@app/context';
|
import { useWorkspace } from '@app/context';
|
||||||
import {
|
import {
|
||||||
useGetProjectSecretsByKey,
|
useGetProjectSecretsByKey,
|
||||||
@@ -27,6 +29,8 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
|||||||
const workspaceId = currentWorkspace?._id as string;
|
const workspaceId = currentWorkspace?._id as string;
|
||||||
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
||||||
|
|
||||||
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading && !workspaceId && router.isReady) {
|
if (!isLoading && !workspaceId && router.isReady) {
|
||||||
router.push('/noprojects');
|
router.push('/noprojects');
|
||||||
@@ -88,7 +92,7 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when secrets is not loading and secrets list is empty
|
// when secrets is not loading and secrets list is empty
|
||||||
const isDashboardSecretEmpty = !isSecretsLoading && !Object.keys(secrets?.secrets || {})?.length;
|
const isDashboardSecretEmpty = !isSecretsLoading && !Object.keys(secrets?.secrets || {})?.filter((secret: any) => secret.toUpperCase().includes(searchFilter.toUpperCase()))?.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto max-w-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
@@ -121,6 +125,15 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="absolute top-[11.1rem] right-6 flex w-full max-w-sm flex-grow space-x-2">
|
||||||
|
<Input
|
||||||
|
className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||||
|
placeholder="Search by secret name..."
|
||||||
|
value={searchFilter}
|
||||||
|
onChange={(e) => setSearchFilter(e.target.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="overflow-y-auto">
|
<div className="overflow-y-auto">
|
||||||
<div className="sticky top-0 mt-8 flex h-10 min-w-[60.3rem] flex-row rounded-md border border-mineshaft-600 bg-mineshaft-800">
|
<div className="sticky top-0 mt-8 flex h-10 min-w-[60.3rem] flex-row rounded-md border border-mineshaft-600 bg-mineshaft-800">
|
||||||
<div className="sticky top-0 flex w-10 items-center justify-center border-none px-4">
|
<div className="sticky top-0 flex w-10 items-center justify-center border-none px-4">
|
||||||
@@ -167,7 +180,7 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
|||||||
<TableContainer className="border-none">
|
<TableContainer className="border-none">
|
||||||
<table className="secret-table relative w-full bg-mineshaft-900">
|
<table className="secret-table relative w-full bg-mineshaft-900">
|
||||||
<tbody className="max-h-screen overflow-y-auto">
|
<tbody className="max-h-screen overflow-y-auto">
|
||||||
{Object.keys(secrets?.secrets || {}).map((key, index) => (
|
{Object.keys(secrets?.secrets || {})?.filter((secret: any) => secret.toUpperCase().includes(searchFilter.toUpperCase())).map((key, index) => (
|
||||||
<EnvComparisonRow
|
<EnvComparisonRow
|
||||||
key={`row-${key}`}
|
key={`row-${key}`}
|
||||||
secrets={secrets?.secrets?.[key]}
|
secrets={secrets?.secrets?.[key]}
|
||||||
@@ -184,8 +197,9 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
|||||||
{isDashboardSecretEmpty && (
|
{isDashboardSecretEmpty && (
|
||||||
<div className="flex h-40 w-full flex-row rounded-md">
|
<div className="flex h-40 w-full flex-row rounded-md">
|
||||||
<div className="flex w-full min-w-[11rem] flex-col items-center justify-center rounded-md border-none bg-mineshaft-800 text-bunker-300">
|
<div className="flex w-full min-w-[11rem] flex-col items-center justify-center rounded-md border-none bg-mineshaft-800 text-bunker-300">
|
||||||
<span className="mb-1">No secrets are available in this project yet.</span>
|
<FontAwesomeIcon icon={faKey} className="text-4xl mb-4" />
|
||||||
<span>You can go into any environment to add secrets there.</span>
|
<span className="mb-1">No secrets found.</span>
|
||||||
|
<span>To add more secrets you can explore any environment.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/jsx-no-useless-fragment */
|
/* eslint-disable react/jsx-no-useless-fragment */
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
|
import { faCircle, faEye, faEyeSlash, faMinus } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
@@ -29,7 +29,9 @@ const DashboardInput = ({
|
|||||||
const syntaxHighlight = useCallback((val: string) => {
|
const syntaxHighlight = useCallback((val: string) => {
|
||||||
if (val === undefined)
|
if (val === undefined)
|
||||||
return (
|
return (
|
||||||
<span className="cursor-default font-sans text-xs italic text-red-500/80">missing</span>
|
<span className="cursor-default font-sans text-xs italic text-red-500/80">
|
||||||
|
<FontAwesomeIcon icon={faMinus} className="mt-1" />
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
if (val?.length === 0)
|
if (val?.length === 0)
|
||||||
return <span className="w-full font-sans text-bunker-400/80">EMPTY</span>;
|
return <span className="w-full font-sans text-bunker-400/80">EMPTY</span>;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const OrgSettingsPage = () => {
|
|||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { subscriptionPlan } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
const orgId = currentOrg?._id || '';
|
const orgId = currentOrg?._id || '';
|
||||||
@@ -60,14 +60,7 @@ export const OrgSettingsPage = () => {
|
|||||||
|
|
||||||
const [completeInviteLink, setcompleteInviteLink] = useState<string | undefined>('');
|
const [completeInviteLink, setcompleteInviteLink] = useState<string | undefined>('');
|
||||||
|
|
||||||
const isMoreUsersNotAllowed =
|
const isMoreUsersNotAllowed = ((subscription?.membersUsed || 0) >= (subscription?.membersLimit || 1)) && host === 'https://app.infisical.com';
|
||||||
(orgUsers || []).length >= 5 &&
|
|
||||||
subscriptionPlan === plans.starter &&
|
|
||||||
host === 'https://app.infisical.com' &&
|
|
||||||
currentWorkspace?._id !== '63ea8121b6e2b0543ba79616' &&
|
|
||||||
currentWorkspace?._id !== '634870246fd2e26f28e76996' &&
|
|
||||||
currentWorkspace?._id !== '63d823cef9e728a0a961255a' &&
|
|
||||||
currentWorkspace?._id !== '6412ec319db25595ac00b8c6';
|
|
||||||
|
|
||||||
const onRenameOrg = async (name: string) => {
|
const onRenameOrg = async (name: string) => {
|
||||||
if (!currentOrg?._id) return;
|
if (!currentOrg?._id) return;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
encryptSymmetric
|
encryptSymmetric
|
||||||
} from '@app/components/utilities/cryptography/crypto';
|
} from '@app/components/utilities/cryptography/crypto';
|
||||||
import { Button, FormControl, Input } from '@app/components/v2';
|
import { Button, FormControl, Input } from '@app/components/v2';
|
||||||
import { plans } from '@app/const';
|
|
||||||
import { useSubscription, useWorkspace } from '@app/context';
|
import { useSubscription, useWorkspace } from '@app/context';
|
||||||
import { useToggle } from '@app/hooks';
|
import { useToggle } from '@app/hooks';
|
||||||
import {
|
import {
|
||||||
@@ -89,10 +88,9 @@ export const ProjectSettingsPage = () => {
|
|||||||
const deleteWsTag = useDeleteWsTag();
|
const deleteWsTag = useDeleteWsTag();
|
||||||
|
|
||||||
// get user subscription
|
// get user subscription
|
||||||
const { subscriptionPlan } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const host = window.location.origin;
|
const host = window.location.origin;
|
||||||
const isEnvServiceAllowed =
|
const isEnvServiceAllowed = ((currentWorkspace?.environments || []).length < (subscription?.envLimit || 3) && host === 'https://app.infisical.com');
|
||||||
subscriptionPlan !== plans.starter || host !== 'https://app.infisical.com';
|
|
||||||
|
|
||||||
const onRenameWorkspace = async (name: string) => {
|
const onRenameWorkspace = async (name: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -402,7 +400,7 @@ export const ProjectSettingsPage = () => {
|
|||||||
{!isBlindIndexedLoading && !isBlindIndexed && (
|
{!isBlindIndexedLoading && !isBlindIndexed && (
|
||||||
<ProjectIndexSecretsSection onEnableBlindIndices={onEnableBlindIndices} />
|
<ProjectIndexSecretsSection onEnableBlindIndices={onEnableBlindIndices} />
|
||||||
)}
|
)}
|
||||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md border-l border-red bg-white/5 px-6 pl-6 pb-4 pt-4">
|
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md border-l border-red bg-mineshaft-900 px-6 pl-6 pb-4 pt-4">
|
||||||
<p className="text-xl font-bold text-red">{t('settings.project.danger-zone')}</p>
|
<p className="text-xl font-bold text-red">{t('settings.project.danger-zone')}</p>
|
||||||
<p className="text-md mt-2 text-gray-400">{t('settings.project.danger-zone-note')}</p>
|
<p className="text-md mt-2 text-gray-400">{t('settings.project.danger-zone-note')}</p>
|
||||||
<div className="mr-auto mt-4 max-h-28 w-full max-w-md">
|
<div className="mr-auto mt-4 max-h-28 w-full max-w-md">
|
||||||
@@ -418,6 +416,7 @@ export const ProjectSettingsPage = () => {
|
|||||||
onChange={(e) => setDeleteProjectInput(e.target.value)}
|
onChange={(e) => setDeleteProjectInput(e.target.value)}
|
||||||
value={deleteProjectInput}
|
value={deleteProjectInput}
|
||||||
placeholder="Type the project name to delete"
|
placeholder="Type the project name to delete"
|
||||||
|
className="bg-mineshaft-800"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ export const AutoCapitalizationSection = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-mineshaft-900 px-6 pb-6 pt-2">
|
||||||
<p className="mb-4 mt-2 text-xl font-semibold">{t('settings.project.auto-capitalization')}</p>
|
<p className="mb-4 mt-2 text-xl font-semibold">{t('settings.project.auto-capitalization')}</p>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="data-[state=checked]:bg-primary"
|
className="data-[state=checked]:bg-primary"
|
||||||
|
|||||||
+2
-17
@@ -28,24 +28,9 @@ export const CopyProjectIDSection = ({ workspaceID }: Props): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pt-4 pb-2">
|
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-mineshaft-900 px-6 pt-4 pb-2">
|
||||||
<p className="self-start text-xl font-semibold">{t('common.project-id')}</p>
|
<p className="self-start text-xl font-semibold">{t('common.project-id')}</p>
|
||||||
<p className="mt-4 self-start text-base font-normal text-gray-400">
|
<p className="mt-4 text-sm text-bunker-300 mb-2">{t('settings.project.auto-generated')}</p>
|
||||||
{t('settings.project.project-id-description')}
|
|
||||||
</p>
|
|
||||||
<p className="mt-2 self-start text-base font-normal text-gray-400">
|
|
||||||
{t('settings.project.project-id-description2')}
|
|
||||||
{/* eslint-disable-next-line react/jsx-no-target-blank */}
|
|
||||||
<a
|
|
||||||
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
className="text-primary duration-200 hover:opacity-80"
|
|
||||||
>
|
|
||||||
{t('settings.project.docs')}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p className="mt-4 text-xs text-bunker-300">{t('settings.project.auto-generated')}</p>
|
|
||||||
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400">
|
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400">
|
||||||
<p className="mr-2 pl-4 font-bold">{`${t('common.project-id')}:`}</p>
|
<p className="mr-2 pl-4 font-bold">{`${t('common.project-id')}:`}</p>
|
||||||
<p className="mr-4">{workspaceID}</p>
|
<p className="mr-4">{workspaceID}</p>
|
||||||
|
|||||||
+11
-16
@@ -1,5 +1,5 @@
|
|||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { faPencil, faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons';
|
import { faPencil, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
@@ -82,7 +82,7 @@ export const EnvironmentSection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-white/5 p-6">
|
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-mineshaft-900 p-6">
|
||||||
<div className="mb-2 flex w-full flex-row justify-between">
|
<div className="mb-2 flex w-full flex-row justify-between">
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<p className="mb-3 text-xl font-semibold">Project Environments</p>
|
<p className="mb-3 text-xl font-semibold">Project Environments</p>
|
||||||
@@ -128,32 +128,27 @@ export const EnvironmentSection = ({
|
|||||||
<Td>{slug}</Td>
|
<Td>{slug}</Td>
|
||||||
<Td className="flex items-center justify-end">
|
<Td className="flex items-center justify-end">
|
||||||
<IconButton
|
<IconButton
|
||||||
className="mr-3"
|
className="mr-3 py-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isEnvServiceAllowed) {
|
handlePopUpOpen('createUpdateEnv', { name, slug });
|
||||||
handlePopUpOpen('createUpdateEnv', { name, slug });
|
reset({ environmentName: name, environmentSlug: slug });
|
||||||
reset({ environmentName: name, environmentSlug: slug });
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen('upgradePlan');
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
colorSchema="secondary"
|
colorSchema="primary"
|
||||||
|
variant="plain"
|
||||||
ariaLabel="update"
|
ariaLabel="update"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faPencil} />
|
<FontAwesomeIcon icon={faPencil} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isEnvServiceAllowed) {
|
handlePopUpOpen('deleteEnv', { name, slug });
|
||||||
handlePopUpOpen('deleteEnv', { name, slug });
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen('upgradePlan');
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
size="lg"
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
ariaLabel="update"
|
ariaLabel="update"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrashCan} />
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ export const ProjectIndexSecretsSection = ({
|
|||||||
onEnableBlindIndices
|
onEnableBlindIndices
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md bg-white/5 p-6 my-2">
|
<div className="rounded-md bg-mineshaft-900 p-6 my-2">
|
||||||
<p className="mb-4 text-xl font-semibold">Blind Indices</p>
|
<p className="mb-4 text-xl font-semibold">Blind Indices</p>
|
||||||
<p className="mb-4 text-sm text-gray-400">
|
<p className="mb-4 text-sm text-gray-400">
|
||||||
Your project, created before the introduction of blind indexing, contains unindexed secrets. To access individual secrets by name through the SDK and public API, please enable blind indexing.
|
Your project, created before the introduction of blind indexing, contains unindexed secrets. To access individual secrets by name through the SDK and public API, please enable blind indexing.
|
||||||
|
|||||||
+2
-2
@@ -41,14 +41,14 @@ export const ProjectNameChangeSection = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<div className="mb-6 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-3">
|
<div className="mb-6 flex w-full flex-col items-start rounded-md bg-mineshaft-900 px-6 pb-6 pt-3">
|
||||||
<p className="mb-4 mt-2 text-xl font-semibold">{t('common.display-name')}</p>
|
<p className="mb-4 mt-2 text-xl font-semibold">{t('common.display-name')}</p>
|
||||||
<div className="mb-2 w-full max-w-lg">
|
<div className="mb-2 w-full max-w-lg">
|
||||||
<Controller
|
<Controller
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input placeholder="Type your project name" {...field} />
|
<Input placeholder="Type your project name" {...field} className="bg-mineshaft-800" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+1
-1
@@ -74,7 +74,7 @@ export const SecretTagsSection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-white/5 p-6">
|
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-mineshaft-900 p-6">
|
||||||
<div className="flex w-full flex-row justify-between">
|
<div className="flex w-full flex-row justify-between">
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<p className="mb-3 text-xl font-semibold">Secret Tags</p>
|
<p className="mb-3 text-xl font-semibold">Secret Tags</p>
|
||||||
|
|||||||
+3
-15
@@ -120,23 +120,11 @@ export const ServiceTokenSection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-white/5 p-6">
|
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-mineshaft-900 p-6">
|
||||||
<div className="flex w-full flex-row justify-between">
|
<div className="flex w-full flex-row justify-between">
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<p className="mb-3 text-xl font-semibold">{t('section.token.service-tokens')}</p>
|
<p className="mb-3 text-xl font-semibold">{t('section.token.service-tokens')}</p>
|
||||||
<p className="text-sm text-gray-400">{t('section.token.service-tokens-description')}</p>
|
<p className="text-sm text-gray-400 mb-4">{t('section.token.service-tokens-description')}</p>
|
||||||
<p className="mb-4 text-sm text-gray-400">
|
|
||||||
Please, make sure you are on the
|
|
||||||
<a
|
|
||||||
className="ml-1 text-primary underline underline-offset-2"
|
|
||||||
href="https://infisical.com/docs/cli/overview"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
latest version of CLI
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -359,7 +347,7 @@ export const ServiceTokenSection = ({
|
|||||||
))}
|
))}
|
||||||
{!isLoading && tokens?.length === 0 && (
|
{!isLoading && tokens?.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={4} className="py-6 text-center text-bunker-400">
|
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||||
<EmptyState title="No service tokens found" icon={faKey} />
|
<EmptyState title="No service tokens found" icon={faKey} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user