nitin
2025-03-10 19:02:40 +05:30
committed by GitHub
parent a1e0d7b7d7
commit 77574594f2
29 changed files with 496 additions and 212 deletions

View File

@ -1,9 +1,11 @@
import { currentUserState } from '@/auth/states/currentUserState';
import { SettingsAdminTabContent } from '@/settings/admin-panel/components/SettingsAdminTabContent';
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
import { TabList } from '@/ui/layout/tab/components/TabList';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { IconHeart, IconSettings2, IconVariable } from 'twenty-ui';
const StyledTabListContainer = styled.div`
@ -15,21 +17,28 @@ const StyledTabListContainer = styled.div`
`;
export const SettingsAdminContent = () => {
const currentUser = useRecoilValue(currentUserState);
const canAccessFullAdminPanel = currentUser?.canAccessFullAdminPanel;
const canImpersonate = currentUser?.canImpersonate;
const tabs = [
{
id: SETTINGS_ADMIN_TABS.GENERAL,
title: t`General`,
Icon: IconSettings2,
disabled: !canAccessFullAdminPanel && !canImpersonate,
},
{
id: SETTINGS_ADMIN_TABS.ENV_VARIABLES,
title: t`Env Variables`,
Icon: IconVariable,
disabled: !canAccessFullAdminPanel,
},
{
id: SETTINGS_ADMIN_TABS.HEALTH_STATUS,
title: t`Health Status`,
Icon: IconHeart,
disabled: !canAccessFullAdminPanel,
},
];

View File

@ -1,60 +1,35 @@
import { SettingsAdminEnvVariablesTable } from '@/settings/admin-panel/components/SettingsAdminEnvVariablesTable';
import { SettingsAdminTabSkeletonLoader } from '@/settings/admin-panel/components/SettingsAdminTabSkeletonLoader';
import { SettingsListItemCardContent } from '@/settings/components/SettingsListItemCardContent';
import { SettingsPath } from '@/types/SettingsPath';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useState } from 'react';
import { Button, H1Title, H1TitleFontColor, H2Title, Section } from 'twenty-ui';
import { t } from '@lingui/core/macro';
import { Card, H2Title, IconHeartRateMonitor, Section } from 'twenty-ui';
import { useGetEnvironmentVariablesGroupedQuery } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledGroupContainer = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(6)};
`;
const StyledGroupDescription = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(4)};
`;
const StyledButtonsRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(6)};
`;
const StyledShowMoreButton = styled(Button)<{ isSelected?: boolean }>`
${({ isSelected, theme }) =>
isSelected &&
`
background-color: ${theme.background.transparent.light};
`}
const StyledInfoText = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
`;
export const SettingsAdminEnvVariables = () => {
const theme = useTheme();
const { data: environmentVariables, loading: environmentVariablesLoading } =
useGetEnvironmentVariablesGroupedQuery({
fetchPolicy: 'network-only',
});
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
const toggleGroupVisibility = (groupName: string) => {
setSelectedGroup(selectedGroup === groupName ? null : groupName);
};
const hiddenGroups =
environmentVariables?.getEnvironmentVariablesGrouped.groups.filter(
(group) => group.isHiddenOnLoad,
) ?? [];
const visibleGroups =
environmentVariables?.getEnvironmentVariablesGrouped.groups.filter(
(group) => !group.isHiddenOnLoad,
) ?? [];
const selectedGroupData =
environmentVariables?.getEnvironmentVariablesGrouped.groups.find(
(group) => group.name === selectedGroup,
);
if (environmentVariablesLoading) {
return <SettingsAdminTabSkeletonLoader />;
}
@ -62,9 +37,11 @@ export const SettingsAdminEnvVariables = () => {
return (
<>
<Section>
These are only the server values. Ensure your worker environment has the
<StyledInfoText>
{t` These are only the server values. Ensure your worker environment has the
same variables and values, this is required for asynchronous tasks like
email sync.
email sync.`}
</StyledInfoText>
</Section>
<Section>
{visibleGroups.map((group) => (
@ -76,42 +53,15 @@ export const SettingsAdminEnvVariables = () => {
</StyledGroupContainer>
))}
{hiddenGroups.length > 0 && (
<>
<StyledButtonsRow>
{hiddenGroups.map((group) => (
<StyledShowMoreButton
key={group.name}
onClick={() => toggleGroupVisibility(group.name)}
title={group.name}
variant="secondary"
isSelected={selectedGroup === group.name}
>
{group.name} variables
</StyledShowMoreButton>
))}
</StyledButtonsRow>
{selectedGroupData && (
<StyledGroupContainer>
<H1Title
title={selectedGroupData.name}
fontColor={H1TitleFontColor.Primary}
/>
{selectedGroupData.description !== '' && (
<StyledGroupDescription>
{selectedGroupData.description}
</StyledGroupDescription>
)}
{selectedGroupData.variables.length > 0 && (
<SettingsAdminEnvVariablesTable
variables={selectedGroupData.variables}
/>
)}
</StyledGroupContainer>
)}
</>
)}
<Card rounded>
<SettingsListItemCardContent
label={t`Other Variables`}
to={getSettingsPath(SettingsPath.AdminPanelOtherEnvVariables)}
rightComponent={null}
LeftIcon={IconHeartRateMonitor}
LeftIconColor={theme.font.color.tertiary}
/>
</Card>
</Section>
</>
);

View File

@ -16,7 +16,6 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { getImageAbsoluteURI, isDefined } from 'twenty-shared';
import {
Button,
GithubVersionLink,
H1Title,
H1TitleFontColor,
H2Title,
@ -27,7 +26,7 @@ import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useUserLookupAdminPanelMutation } from '~/generated/graphql';
import { currentUserState } from '@/auth/states/currentUserState';
import packageJson from '../../../../../package.json';
import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer';
const StyledContainer = styled.div`
align-items: center;
@ -54,11 +53,6 @@ const StyledContentContainer = styled.div`
padding: ${({ theme }) => theme.spacing(4)} 0;
`;
const StyledErrorMessage = styled.div`
color: ${({ theme }) => theme.color.red};
margin-top: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsAdminGeneral = () => {
const [userIdentifier, setUserIdentifier] = useState('');
const { enqueueSnackBar } = useSnackBar();
@ -75,6 +69,8 @@ export const SettingsAdminGeneral = () => {
const currentUser = useRecoilValue(currentUserState);
const canAccessFullAdminPanel = currentUser?.canAccessFullAdminPanel;
const canImpersonate = currentUser?.canImpersonate;
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
@ -130,51 +126,51 @@ export const SettingsAdminGeneral = () => {
return (
<>
<Section>
<H2Title title={t`About`} description={t`Version of the application`} />
<GithubVersionLink version={packageJson.version} />
</Section>
<Section>
<H2Title
title={
canManageFeatureFlags
? t`Feature Flags & Impersonation`
: t`User Impersonation`
}
description={
canManageFeatureFlags
? t`Look up users and manage their workspace feature flags or impersonate them.`
: t`Look up users to impersonate them.`
}
/>
<StyledContainer>
<TextInput
value={userIdentifier}
onChange={setUserIdentifier}
onInputEnter={handleSearch}
placeholder={t`Enter user ID or email address`}
fullWidth
disabled={isUserLookupLoading}
{canAccessFullAdminPanel && (
<Section>
<H2Title
title={t`About`}
description={t`Version of the application`}
/>
<Button
Icon={IconSearch}
variant="primary"
accent="blue"
title={t`Search`}
onClick={handleSearch}
disabled={
!userIdentifier.trim() || isUserLookupLoading || !canImpersonate
<SettingsAdminVersionContainer />
</Section>
)}
{canImpersonate && (
<Section>
<H2Title
title={
canManageFeatureFlags
? t`Feature Flags & Impersonation`
: t`User Impersonation`
}
description={
canManageFeatureFlags
? t`Look up users and manage their workspace feature flags or impersonate them.`
: t`Look up users to impersonate them.`
}
/>
</StyledContainer>
{!canImpersonate && (
<StyledErrorMessage>
{t`You do not have access to impersonate users.`}
</StyledErrorMessage>
)}
</Section>
<StyledContainer>
<TextInput
value={userIdentifier}
onChange={setUserIdentifier}
onInputEnter={handleSearch}
placeholder={t`Enter user ID or email address`}
fullWidth
disabled={isUserLookupLoading}
/>
<Button
Icon={IconSearch}
variant="primary"
accent="blue"
title={t`Search`}
onClick={handleSearch}
disabled={!userIdentifier.trim() || isUserLookupLoading}
/>
</StyledContainer>
</Section>
)}
{isDefined(userLookupResult) && (
<Section>

View File

@ -0,0 +1,118 @@
import { IconCircleDot, IconComponent, IconStatusChange } from 'twenty-ui';
import { GITHUB_LINK } from '@ui/navigation/link/constants/GithubLink';
import { checkTwentyVersionExists } from '@/settings/admin-panel/utils/checkTwentyVersionExists';
import { fetchLatestTwentyRelease } from '@/settings/admin-panel/utils/fetchLatestTwentyRelease';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useEffect, useState } from 'react';
import packageJson from '../../../../../package.json';
const StyledVersionContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(3)};
display: grid;
`;
const StyledVersionDetails = styled.div`
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledVersionText = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
const StyledActionLink = styled.a`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(1)};
padding: 0 ${({ theme }) => theme.spacing(1)};
text-decoration: none;
:hover {
color: ${({ theme }) => theme.font.color.primary};
cursor: pointer;
}
`;
const StyledSpan = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
`;
type VersionDetail = {
Icon: IconComponent;
text: string;
version: string | null;
link: string;
type: 'current' | 'latest';
};
export const SettingsAdminVersionContainer = () => {
const theme = useTheme();
const [latestVersion, setLatestVersion] = useState<string | null>(null);
const [currentVersionExists, setCurrentVersionExists] = useState(false);
useEffect(() => {
fetchLatestTwentyRelease().then(setLatestVersion);
checkTwentyVersionExists(packageJson.version).then(setCurrentVersionExists);
}, []);
const VERSION_DETAILS: VersionDetail[] = [
{
Icon: IconCircleDot,
text: t`Current version:`,
version: packageJson.version,
link: `${GITHUB_LINK}/releases/tag/v${packageJson.version}`,
type: 'current',
},
{
Icon: IconStatusChange,
text: t`Latest version:`,
version: latestVersion,
link: `${GITHUB_LINK}/releases/tag/v${latestVersion}`,
type: 'latest',
},
];
return (
<StyledVersionContainer>
{VERSION_DETAILS.map((versionDetail, index) => (
<StyledVersionDetails key={index}>
<versionDetail.Icon
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
<StyledVersionText>{versionDetail.text}</StyledVersionText>
{versionDetail.version &&
(versionDetail.type === 'current' ? currentVersionExists : true) ? (
<StyledActionLink
href={versionDetail.link}
target="_blank"
rel="noreferrer"
>
{versionDetail.version}
</StyledActionLink>
) : (
<StyledSpan>{versionDetail.version}</StyledSpan>
)}
</StyledVersionDetails>
))}
</StyledVersionContainer>
);
};