Admin panel fixes (#10792)

<img width="573" alt="Screenshot 2025-03-12 at 17 36 44"
src="https://github.com/user-attachments/assets/be6c20b0-626d-4a2c-810c-78a49e9f65ee"
/>
<img width="579" alt="Screenshot 2025-03-12 at 17 37 03"
src="https://github.com/user-attachments/assets/23692ff8-ac88-4104-823e-1a06b3074551"
/>
<img width="590" alt="Screenshot 2025-03-12 at 17 37 14"
src="https://github.com/user-attachments/assets/b46de1d3-a312-44cc-a54d-72208224453d"
/>
<img width="556" alt="Screenshot 2025-03-12 at 17 37 37"
src="https://github.com/user-attachments/assets/12176d49-d76d-4fb1-abe6-1f7dc5349d94"
/>
<img width="607" alt="Screenshot 2025-03-12 at 17 37 50"
src="https://github.com/user-attachments/assets/00e2edff-09db-45c5-a4df-6fd9ead830b6"
/>
This commit is contained in:
nitin
2025-03-13 01:25:38 +05:30
committed by GitHub
parent 75da64876a
commit c61748cd6e
20 changed files with 670 additions and 440 deletions

View File

@ -0,0 +1,63 @@
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { IconCopy, OverflowingTextWithTooltip } from 'twenty-ui';
import { useDebouncedCallback } from 'use-debounce';
const StyledEllipsisLabel = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
const StyledExpandedEllipsisLabel = styled.div`
white-space: normal;
word-break: break-all;
`;
const StyledCopyContainer = styled.span`
cursor: pointer;
`;
export const SettingsAdminEnvCopyableText = ({
text,
displayText,
multiline = false,
maxRows,
}: {
text: string;
displayText?: React.ReactNode;
multiline?: boolean;
maxRows?: number;
}) => {
const { enqueueSnackBar } = useSnackBar();
const theme = useTheme();
const { t } = useLingui();
const copyToClipboardDebounced = useDebouncedCallback((value: string) => {
navigator.clipboard.writeText(value);
enqueueSnackBar(t`Copied to clipboard!`, {
variant: SnackBarVariant.Success,
icon: <IconCopy size={theme.icon.size.md} />,
});
}, 200);
return (
<StyledCopyContainer onClick={() => copyToClipboardDebounced(text)}>
{maxRows ? (
<OverflowingTextWithTooltip
text={displayText?.toString() || text}
displayedMaxRows={maxRows}
isTooltipMultiline={multiline}
/>
) : multiline ? (
<StyledExpandedEllipsisLabel>
{displayText || text}
</StyledExpandedEllipsisLabel>
) : (
<StyledEllipsisLabel>{displayText || text}</StyledEllipsisLabel>
)}
</StyledCopyContainer>
);
};

View File

@ -10,14 +10,16 @@ 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 StyledGroupContainer = styled.div``;
const StyledInfoText = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
`;
const StyledCard = styled(Card)`
margin-bottom: ${({ theme }) => theme.spacing(8)};
`;
export const SettingsAdminEnvVariables = () => {
const theme = useTheme();
const { data: environmentVariables, loading: environmentVariablesLoading } =
@ -38,22 +40,20 @@ export const SettingsAdminEnvVariables = () => {
<>
<Section>
<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.`}
{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.`}
</StyledInfoText>
</Section>
<Section>
{visibleGroups.map((group) => (
<StyledGroupContainer key={group.name}>
<H2Title title={group.name} description={group.description} />
{group.variables.length > 0 && (
<SettingsAdminEnvVariablesTable variables={group.variables} />
)}
</StyledGroupContainer>
))}
{visibleGroups.map((group) => (
<StyledGroupContainer key={group.name}>
<H2Title title={group.name} description={group.description} />
{group.variables.length > 0 && (
<SettingsAdminEnvVariablesTable variables={group.variables} />
)}
</StyledGroupContainer>
))}
<Card rounded>
<Section>
<StyledCard rounded>
<SettingsListItemCardContent
label={t`Other Variables`}
to={getSettingsPath(SettingsPath.AdminPanelOtherEnvVariables)}
@ -61,7 +61,7 @@ export const SettingsAdminEnvVariables = () => {
LeftIcon={IconHeartRateMonitor}
LeftIconColor={theme.font.color.tertiary}
/>
</Card>
</StyledCard>
</Section>
</>
);

View File

@ -1,3 +1,5 @@
import { SettingsAdminEnvCopyableText } from '@/settings/admin-panel/components/SettingsAdminEnvCopyableText';
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
@ -19,6 +21,8 @@ type SettingsAdminEnvVariablesRowProps = {
value: string;
sensitive: boolean;
};
isExpanded: boolean;
onExpandToggle: (name: string) => void;
};
const StyledTruncatedCell = styled(TableCell)`
@ -43,53 +47,36 @@ const StyledButton = styled(motion.button)`
const MotionIconChevronDown = motion(IconChevronRight);
const StyledExpandedDetails = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border-radius: ${({ theme }) => theme.border.radius.sm};
margin: ${({ theme }) => theme.spacing(2)} 0;
padding: ${({ theme }) => theme.spacing(2)};
border: 1px solid ${({ theme }) => theme.border.color.medium};
display: grid;
grid-template-columns: auto 1fr;
gap: ${({ theme }) => theme.spacing(1)};
height: fit-content;
min-height: min-content;
`;
const StyledDetailLabel = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-weight: ${({ theme }) => theme.font.weight.regular};
padding-right: ${({ theme }) => theme.spacing(4)};
`;
const StyledEllipsisLabel = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
const StyledExpandedLabel = styled.div`
word-break: break-word;
white-space: normal;
overflow: visible;
`;
const StyledValueContainer = styled.div`
display: flex;
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: space-between;
width: 100%;
`;
const StyledTableRow = styled(TableRow)<{ isExpanded: boolean }>`
background-color: ${({ isExpanded, theme }) =>
isExpanded ? theme.background.transparent.light : 'transparent'};
margin-bottom: ${({ theme }) => theme.spacing(0.5)};
`;
const StyledExpandableContainer = styled.div`
width: 100%;
padding-top: ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsAdminEnvVariablesRow = ({
variable,
isExpanded,
onExpandToggle,
}: SettingsAdminEnvVariablesRowProps) => {
const [isExpanded, setIsExpanded] = useState(false);
const [showSensitiveValue, setShowSensitiveValue] = useState(false);
const theme = useTheme();
@ -105,10 +92,47 @@ export const SettingsAdminEnvVariablesRow = ({
setShowSensitiveValue(!showSensitiveValue);
};
const environmentVariablesDetails = [
{
label: 'Name',
value: <SettingsAdminEnvCopyableText text={variable.name} />,
},
{
label: 'Description',
value: (
<SettingsAdminEnvCopyableText
text={variable.description}
maxRows={1}
multiline={true}
/>
),
},
{
label: 'Value',
value: (
<StyledValueContainer>
<SettingsAdminEnvCopyableText
text={variable.value}
displayText={displayValue}
multiline={true}
/>
{variable.sensitive && variable.value !== '' && (
<LightIconButton
Icon={showSensitiveValue ? IconEyeOff : IconEye}
size="small"
accent="secondary"
onClick={handleToggleVisibility}
/>
)}
</StyledValueContainer>
),
},
];
return (
<>
<StyledTableRow
onClick={() => setIsExpanded(!isExpanded)}
onClick={() => onExpandToggle(variable.name)}
gridAutoColumns="5fr 4fr 3fr 1fr"
isExpanded={isExpanded}
>
@ -122,7 +146,12 @@ export const SettingsAdminEnvVariablesRow = ({
<StyledEllipsisLabel>{displayValue}</StyledEllipsisLabel>
</StyledTruncatedCell>
<TableCell align="right">
<StyledButton onClick={() => setIsExpanded(!isExpanded)}>
<StyledButton
onClick={(e) => {
e.stopPropagation();
onExpandToggle(variable.name);
}}
>
<MotionIconChevronDown
size={theme.icon.size.md}
color={theme.font.color.tertiary}
@ -133,26 +162,12 @@ export const SettingsAdminEnvVariablesRow = ({
</TableCell>
</StyledTableRow>
<AnimatedExpandableContainer isExpanded={isExpanded} mode="fit-content">
<StyledExpandedDetails>
<StyledDetailLabel>Name</StyledDetailLabel>
<StyledEllipsisLabel>{variable.name}</StyledEllipsisLabel>
<StyledDetailLabel>Description</StyledDetailLabel>
<StyledExpandedLabel>{variable.description}</StyledExpandedLabel>
<StyledDetailLabel>Value</StyledDetailLabel>
<StyledExpandedLabel>
<StyledValueContainer>
{displayValue}
{variable.sensitive && variable.value !== '' && (
<LightIconButton
Icon={showSensitiveValue ? IconEyeOff : IconEye}
size="small"
accent="secondary"
onClick={handleToggleVisibility}
/>
)}
</StyledValueContainer>
</StyledExpandedLabel>
</StyledExpandedDetails>
<StyledExpandableContainer>
<SettingsAdminTableCard
items={environmentVariablesDetails}
gridAutoColumns="1fr 4fr"
/>
</StyledExpandableContainer>
</AnimatedExpandableContainer>
</>
);

View File

@ -1,11 +1,13 @@
import { SettingsAdminEnvVariablesRow } from '@/settings/admin-panel/components/SettingsAdminEnvVariablesRow';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { useState } from 'react';
const StyledTable = styled(Table)`
margin-top: ${({ theme }) => theme.spacing(3)};
const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
type SettingsAdminEnvVariablesTableProps = {
@ -19,16 +21,31 @@ type SettingsAdminEnvVariablesTableProps = {
export const SettingsAdminEnvVariablesTable = ({
variables,
}: SettingsAdminEnvVariablesTableProps) => (
<StyledTable>
<TableRow gridAutoColumns="5fr 4fr 3fr 1fr">
<TableHeader>Name</TableHeader>
<TableHeader>Description</TableHeader>
<TableHeader align="right">Value</TableHeader>
<TableHeader align="right"></TableHeader>
</TableRow>
{variables.map((variable) => (
<SettingsAdminEnvVariablesRow key={variable.name} variable={variable} />
))}
</StyledTable>
);
}: SettingsAdminEnvVariablesTableProps) => {
const [expandedRowName, setExpandedRowName] = useState<string | null>(null);
const handleExpandToggle = (name: string) => {
setExpandedRowName(expandedRowName === name ? null : name);
};
return (
<Table>
<TableRow gridAutoColumns="5fr 4fr 3fr 1fr">
<TableHeader>Name</TableHeader>
<TableHeader>Description</TableHeader>
<TableHeader align="right">Value</TableHeader>
<TableHeader align="right"></TableHeader>
</TableRow>
<StyledTableBody>
{variables.map((variable) => (
<SettingsAdminEnvVariablesRow
key={variable.name}
variable={variable}
isExpanded={expandedRowName === variable.name}
onExpandToggle={handleExpandToggle}
/>
))}
</StyledTableBody>
</Table>
);
};

View File

@ -16,16 +16,18 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { getImageAbsoluteURI, isDefined } from 'twenty-shared';
import {
Button,
H1Title,
H1TitleFontColor,
H2Title,
IconId,
IconMail,
IconSearch,
IconUser,
Section,
} from 'twenty-ui';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useUserLookupAdminPanelMutation } from '~/generated/graphql';
import { currentUserState } from '@/auth/states/currentUserState';
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer';
const StyledContainer = styled.div`
@ -35,10 +37,6 @@ const StyledContainer = styled.div`
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledUserInfo = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(5)};
`;
const StyledTabListContainer = styled.div`
align-items: center;
border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
@ -47,12 +45,6 @@ const StyledTabListContainer = styled.div`
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledContentContainer = styled.div`
flex: 1;
width: 100%;
padding: ${({ theme }) => theme.spacing(4)} 0;
`;
export const SettingsAdminGeneral = () => {
const [userIdentifier, setUserIdentifier] = useState('');
const { enqueueSnackBar } = useSnackBar();
@ -124,6 +116,24 @@ export const SettingsAdminGeneral = () => {
userLookupResult?.user.lastName || ''
}`.trim();
const userInfoItems = [
{
Icon: IconUser,
label: t`Name`,
value: userFullName,
},
{
Icon: IconMail,
label: t`Email`,
value: userLookupResult?.user.email,
},
{
Icon: IconId,
label: t`ID`,
value: userLookupResult?.user.id,
},
];
return (
<>
{canAccessFullAdminPanel && (
@ -173,36 +183,31 @@ export const SettingsAdminGeneral = () => {
)}
{isDefined(userLookupResult) && (
<Section>
<StyledUserInfo>
<H1Title
title={t`User Info`}
fontColor={H1TitleFontColor.Primary}
<>
<Section>
<H2Title title={t`User Info`} description={t`About this user`} />
<SettingsAdminTableCard
items={userInfoItems}
rounded
gridAutoColumns="1fr 4fr"
/>
<H2Title title={userFullName} description={t`User Name`} />
</Section>
<Section>
<H2Title
title={userLookupResult.user.email}
description={t`User Email`}
title={t`Workspaces`}
description={t`All workspaces this user is a member of`}
/>
<H2Title
title={userLookupResult.user.id}
description={t`User ID`}
/>
</StyledUserInfo>
<StyledTabListContainer>
<TabList
tabs={tabs}
tabListInstanceId={SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID}
behaveAsLinks={false}
/>
</StyledTabListContainer>
<H1Title title={t`Workspaces`} fontColor={H1TitleFontColor.Primary} />
<StyledTabListContainer>
<TabList
tabs={tabs}
tabListInstanceId={SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID}
behaveAsLinks={false}
/>
</StyledTabListContainer>
<StyledContentContainer>
<SettingsAdminWorkspaceContent activeWorkspace={activeWorkspace} />
</StyledContentContainer>
</Section>
</Section>
</>
)}
</>
);

View File

@ -0,0 +1,93 @@
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Card, IconComponent } from 'twenty-ui';
const StyledCard = styled(Card)`
background-color: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
`;
const StyledTableRow = styled(TableRow)`
height: ${({ theme }) => theme.spacing(6)};
`;
const StyledTableCellLabel = styled(TableCell)<{
align?: 'left' | 'center' | 'right';
}>`
color: ${({ theme }) => theme.font.color.tertiary};
height: ${({ theme }) => theme.spacing(6)};
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: ${({ align }) =>
align === 'right'
? 'flex-end'
: align === 'center'
? 'center'
: 'flex-start'};
`;
const StyledTableCellValue = styled(TableCell)<{
align?: 'left' | 'center' | 'right';
}>`
color: ${({ theme }) => theme.font.color.primary};
height: ${({ theme }) => theme.spacing(6)};
justify-content: ${({ align }) =>
align === 'left'
? 'flex-start'
: align === 'center'
? 'center'
: 'flex-end'};
`;
type TableItem = {
Icon?: IconComponent;
label: string;
value: string | number | React.ReactNode;
};
type SettingsAdminTableCardProps = {
items: TableItem[];
rounded?: boolean;
gridAutoColumns?: string;
labelAlign?: 'left' | 'center' | 'right';
valueAlign?: 'left' | 'center' | 'right';
className?: string;
};
export const SettingsAdminTableCard = ({
items,
rounded = false,
gridAutoColumns,
labelAlign = 'left',
valueAlign = 'left',
className,
}: SettingsAdminTableCardProps) => {
const theme = useTheme();
return (
<StyledCard rounded={rounded} className={className}>
<Table>
<TableBody>
{items.map((item, index) => (
<StyledTableRow
key={index + item.label}
gridAutoColumns={gridAutoColumns}
>
<StyledTableCellLabel align={labelAlign}>
{item.Icon && <item.Icon size={theme.icon.size.md} />}
<span>{item.label}</span>
</StyledTableCellLabel>
<StyledTableCellValue align={valueAlign}>
{item.value}
</StyledTableCellValue>
</StyledTableRow>
))}
</TableBody>
</Table>
</StyledCard>
);
};

View File

@ -1,46 +1,20 @@
import { IconCircleDot, IconComponent, IconStatusChange } from 'twenty-ui';
import { GITHUB_LINK } from '@ui/navigation/link/constants/GithubLink';
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
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 { GITHUB_LINK } from '@ui/navigation/link/constants/GithubLink';
import { useEffect, useState } from 'react';
import { IconCircleDot, IconStatusChange } from 'twenty-ui';
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};
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: ${({ theme }) => theme.spacing(1)};
padding: 0 ${({ theme }) => theme.spacing(1)};
text-decoration: none;
:hover {
@ -52,19 +26,10 @@ const StyledActionLink = styled.a`
const StyledSpan = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
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);
@ -73,46 +38,44 @@ export const SettingsAdminVersionContainer = () => {
checkTwentyVersionExists(packageJson.version).then(setCurrentVersionExists);
}, []);
const VERSION_DETAILS: VersionDetail[] = [
const versionItems = [
{
Icon: IconCircleDot,
text: t`Current version:`,
version: packageJson.version,
link: `${GITHUB_LINK}/releases/tag/v${packageJson.version}`,
type: 'current',
label: t`Current version`,
value: currentVersionExists ? (
<StyledActionLink
href={`${GITHUB_LINK}/releases/tag/v${packageJson.version}`}
target="_blank"
rel="noreferrer"
>
{packageJson.version}
</StyledActionLink>
) : (
<StyledSpan>{packageJson.version}</StyledSpan>
),
},
{
Icon: IconStatusChange,
text: t`Latest version:`,
version: latestVersion,
link: `${GITHUB_LINK}/releases/tag/v${latestVersion}`,
type: 'latest',
label: t`Latest version`,
value: latestVersion ? (
<StyledActionLink
href={`${GITHUB_LINK}/releases/tag/v${latestVersion}`}
target="_blank"
rel="noreferrer"
>
{latestVersion}
</StyledActionLink>
) : (
<StyledSpan>{latestVersion ?? 'Loading...'}</StyledSpan>
),
},
];
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>
<SettingsAdminTableCard
rounded
items={versionItems}
gridAutoColumns="3fr 8fr"
/>
);
};

View File

@ -1,8 +1,19 @@
import { Button, H2Title, IconUser, Toggle } from 'twenty-ui';
import {
AvatarChip,
Button,
H2Title,
IconEyeShare,
IconHome,
IconId,
IconUser,
Section,
Toggle,
} from 'twenty-ui';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
import { useFeatureFlagState } from '@/settings/admin-panel/hooks/useFeatureFlagState';
import { useImpersonationAuth } from '@/settings/admin-panel/hooks/useImpersonationAuth';
import { useImpersonationRedirect } from '@/settings/admin-panel/hooks/useImpersonationRedirect';
@ -11,14 +22,18 @@ import { WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
import { getImageAbsoluteURI, isDefined } from 'twenty-shared';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import {
FeatureFlagKey,
useImpersonateMutation,
@ -29,7 +44,14 @@ type SettingsAdminWorkspaceContentProps = {
activeWorkspace: WorkspaceInfo | undefined;
};
const StyledTable = styled(Table)`
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
margin-top: ${({ theme }) => theme.spacing(6)};
`;
const StyledButtonContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(3)};
`;
@ -115,64 +137,101 @@ export const SettingsAdminWorkspaceContent = ({
});
};
const workspaceInfoItems = [
{
Icon: IconHome,
label: t`Name`,
value: (
<AvatarChip
name={activeWorkspace?.name ?? ''}
avatarUrl={
getImageAbsoluteURI({
imageUrl: isNonEmptyString(activeWorkspace?.logo)
? activeWorkspace?.logo
: DEFAULT_WORKSPACE_LOGO,
baseUrl: REACT_APP_SERVER_BASE_URL,
}) ?? ''
}
/>
),
},
{
Icon: IconId,
label: t`ID`,
value: activeWorkspace?.id,
},
{
Icon: IconUser,
label: t`Members`,
value: activeWorkspace?.totalUsers,
},
];
if (!activeWorkspace) return null;
return (
<>
<H2Title title={activeWorkspace.name} description={t`Workspace Name`} />
<H2Title
title={`${activeWorkspace.totalUsers} ${
activeWorkspace.totalUsers > 1 ? t`Users` : t`User`
}`}
description={t`Total Users`}
/>
{currentUser?.canImpersonate && (
<Button
Icon={IconUser}
variant="primary"
accent="blue"
title={t`Impersonate`}
onClick={() => handleImpersonate(activeWorkspace.id)}
disabled={
isImpersonateLoading || activeWorkspace.allowImpersonation === false
}
dataTestId="impersonate-button"
<StyledContainer>
<Section>
<H2Title
title={t`Workspace Info`}
description={t`About this workspace`}
/>
)}
<SettingsAdminTableCard
items={workspaceInfoItems}
gridAutoColumns="1fr 4fr"
/>
<StyledButtonContainer>
{currentUser?.canImpersonate && (
<Button
Icon={IconEyeShare}
variant="primary"
accent="default"
title={t`Impersonate`}
onClick={() => handleImpersonate(activeWorkspace.id)}
disabled={
isImpersonateLoading ||
activeWorkspace.allowImpersonation === false
}
dataTestId="impersonate-button"
/>
)}
</StyledButtonContainer>
</Section>
{canManageFeatureFlags && (
<StyledTable>
<TableRow
gridAutoColumns="1fr 100px"
mobileGridAutoColumns="1fr 80px"
>
<TableHeader>{t`Feature Flag`}</TableHeader>
<TableHeader align="right">{t`Status`}</TableHeader>
</TableRow>
{activeWorkspace.featureFlags.map((flag) => (
<Table>
<TableBody>
<TableRow
gridAutoColumns="1fr 100px"
mobileGridAutoColumns="1fr 80px"
key={flag.key}
>
<TableCell>{flag.key}</TableCell>
<TableCell align="right">
<Toggle
value={flag.value}
onChange={(newValue) =>
handleFeatureFlagUpdate(
activeWorkspace.id,
flag.key,
newValue,
)
}
/>
</TableCell>
<TableHeader>{t`Feature Flag`}</TableHeader>
<TableHeader align="right">{t`Status`}</TableHeader>
</TableRow>
))}
</StyledTable>
{activeWorkspace.featureFlags.map((flag) => (
<TableRow
gridAutoColumns="1fr 100px"
mobileGridAutoColumns="1fr 80px"
key={flag.key}
>
<TableCell>{flag.key}</TableCell>
<TableCell align="right">
<Toggle
value={flag.value}
onChange={(newValue) =>
handleFeatureFlagUpdate(
activeWorkspace.id,
flag.key,
newValue,
)
}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</>
</StyledContainer>
);
};

View File

@ -10,6 +10,12 @@ const StyledErrorMessage = styled.div`
margin-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(8)};
`;
export const ConnectedAccountHealthStatus = () => {
const { indicatorHealth } = useContext(SettingsAdminIndicatorHealthContext);
const details = indicatorHealth.details;
@ -35,7 +41,7 @@ export const ConnectedAccountHealthStatus = () => {
}
return (
<>
<StyledContainer>
{errorMessages.length > 0 && (
<StyledErrorMessage>
{`${errorMessages.join(' and ')} ${errorMessages.length > 1 ? 'are' : 'is'} not available because the service is down`}
@ -55,6 +61,6 @@ export const ConnectedAccountHealthStatus = () => {
title={t`Calendar Sync Status`}
/>
)}
</>
</StyledContainer>
);
};

View File

@ -10,8 +10,8 @@ const StyledDetailsContainer = styled.div`
padding: ${({ theme }) => theme.spacing(4)};
border-radius: ${({ theme }) => theme.border.radius.md};
border: 1px solid ${({ theme }) => theme.border.color.medium};
white-space: pre-wrap;
font-size: ${({ theme }) => theme.font.size.sm};
overflow-x: auto;
`;
const StyledErrorMessage = styled.div`

View File

@ -1,55 +1,25 @@
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
import { WorkerMetricsTooltip } from '@/settings/admin-panel/health-status/components/WorkerMetricsTooltip';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Select } from '@/ui/input/components/Select';
import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { ResponsiveLine } from '@nivo/line';
import { isNumber } from '@tiptap/core';
import {
QueueMetricsTimeRange,
useGetQueueMetricsQuery,
} from '~/generated/graphql';
const StyledTableRow = styled(TableRow)`
height: ${({ theme }) => theme.spacing(6)};
`;
const StyledQueueMetricsTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-bottom: ${({ theme }) => theme.spacing(3)};
padding-left: ${({ theme }) => theme.spacing(3)};
`;
const StyledGraphContainer = styled.div`
background-color: ${({ theme }) => theme.background.tertiary};
border-radius: ${({ theme }) => theme.border.radius.sm};
height: 230px;
margin-bottom: ${({ theme }) => theme.spacing(4)};
padding-top: ${({ theme }) => theme.spacing(4)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
const StyledQueueMetricsContainer = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border-radius: ${({ theme }) => theme.border.radius.md};
height: 240px;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
padding-top: ${({ theme }) => theme.spacing(1)};
padding-bottom: ${({ theme }) => theme.spacing(3)};
padding-left: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(3)};
`;
const StyledGraphControls = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: flex-end;
margin-bottom: ${({ theme }) => theme.spacing(2)};
margin-top: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${({ theme }) => theme.spacing(4)};
padding-top: ${({ theme }) => theme.spacing(2.5)};
width: 100%;
`;
const StyledNoDataMessage = styled.div`
@ -60,31 +30,9 @@ const StyledNoDataMessage = styled.div`
justify-content: center;
`;
const StyledTooltipContainer = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: ${({ theme }) => theme.spacing(2)};
font-size: ${({ theme }) => theme.font.size.sm};
`;
const StyledTooltipItem = styled.div<{ color: string }>`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
padding: ${({ theme }) => theme.spacing(0.5)} 0;
`;
const StyledTooltipColorSquare = styled.div<{ color: string }>`
width: 12px;
height: 12px;
background-color: ${({ color }) => color};
margin-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledTooltipValue = styled.span`
font-weight: ${({ theme }) => theme.font.weight.medium};
const StyledSettingsAdminTableCard = styled(SettingsAdminTableCard)`
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
type WorkerMetricsGraphProps = {
@ -96,7 +44,6 @@ type WorkerMetricsGraphProps = {
export const WorkerMetricsGraph = ({
queueName,
timeRange,
onTimeRangeChange,
}: WorkerMetricsGraphProps) => {
const theme = useTheme();
const { enqueueSnackBar } = useSnackBar();
@ -158,25 +105,6 @@ export const WorkerMetricsGraph = ({
return (
<>
<StyledGraphControls>
<Select
dropdownId={`timerange-${queueName}`}
value={timeRange}
options={[
{ value: QueueMetricsTimeRange.SevenDays, label: t`This week` },
{ value: QueueMetricsTimeRange.OneDay, label: t`Today` },
{
value: QueueMetricsTimeRange.TwelveHours,
label: t`Last 12 hours`,
},
{ value: QueueMetricsTimeRange.FourHours, label: t`Last 4 hours` },
{ value: QueueMetricsTimeRange.OneHour, label: t`Last 1 hour` },
]}
onChange={onTimeRangeChange}
needIconCheck
/>
</StyledGraphControls>
<StyledGraphContainer>
{loading ? (
<StyledNoDataMessage>{t`Loading metrics data...`}</StyledNoDataMessage>
@ -185,7 +113,7 @@ export const WorkerMetricsGraph = ({
data={metricsData}
curve="monotoneX"
enableArea={true}
colors={[theme.color.green, theme.color.red]}
colors={[theme.color.blue, theme.color.red]}
theme={{
text: {
fill: theme.font.color.light,
@ -216,7 +144,7 @@ export const WorkerMetricsGraph = ({
},
},
}}
margin={{ top: 40, right: 30, bottom: 40, left: 50 }}
margin={{ top: 40, right: 30, bottom: 40, left: 40 }}
xScale={{
type: 'linear',
min: 0,
@ -230,7 +158,7 @@ export const WorkerMetricsGraph = ({
}}
axisBottom={{
legend: getAxisLabel(),
legendOffset: 30,
legendOffset: 20,
legendPosition: 'middle',
tickSize: 5,
tickPadding: 5,
@ -241,29 +169,12 @@ export const WorkerMetricsGraph = ({
tickSize: 6,
tickPadding: 5,
tickValues: 4,
legend: 'Count',
legendOffset: -40,
legendPosition: 'middle',
}}
enableGridX={false}
gridYValues={4}
pointSize={0}
enableSlices="x"
sliceTooltip={({ slice }) => (
<StyledTooltipContainer>
{slice.points.map((point) => (
<StyledTooltipItem key={point.id} color={point.serieColor}>
<StyledTooltipColorSquare color={point.serieColor} />
<span>
{point.serieId}:{' '}
<StyledTooltipValue>
{String(point.data.y)}
</StyledTooltipValue>
</span>
</StyledTooltipItem>
))}
</StyledTooltipContainer>
)}
sliceTooltip={({ slice }) => <WorkerMetricsTooltip slice={slice} />}
useMesh={true}
legends={[
{
@ -276,8 +187,9 @@ export const WorkerMetricsGraph = ({
itemDirection: 'left-to-right',
itemWidth: 100,
itemHeight: 20,
symbolSize: 12,
symbolShape: 'square',
itemTextColor: theme.font.color.secondary,
symbolSize: 4,
symbolShape: 'circle',
},
]}
/>
@ -286,29 +198,22 @@ export const WorkerMetricsGraph = ({
)}
</StyledGraphContainer>
{metricsDetails && (
<>
<StyledQueueMetricsTitle>Metrics:</StyledQueueMetricsTitle>
<StyledQueueMetricsContainer>
<Table>
{Object.entries(metricsDetails)
.filter(([key]) => key !== '__typename')
.map(([key, value]) => (
<StyledTableRow key={key}>
<TableCell align="left">
{key.charAt(0).toUpperCase() + key.slice(1)}
</TableCell>
<TableCell align="right">
{typeof value === 'number'
? value
: Array.isArray(value)
? value.length
: String(value)}
</TableCell>
</StyledTableRow>
))}
</Table>
</StyledQueueMetricsContainer>
</>
<StyledSettingsAdminTableCard
rounded
items={Object.entries(metricsDetails)
.filter(([key]) => key !== '__typename')
.map(([key, value]) => ({
label: key.charAt(0).toUpperCase() + key.slice(1),
value: isNumber(value)
? value
: Array.isArray(value)
? value.length
: String(value),
}))}
gridAutoColumns="1fr 1fr"
labelAlign="left"
valueAlign="right"
/>
)}
</>
);

View File

@ -0,0 +1,68 @@
import styled from '@emotion/styled';
import { Point } from '@nivo/line';
import { ReactElement } from 'react';
const StyledTooltipContainer = styled.div`
backdrop-filter: ${({ theme }) => theme.blur.medium};
background-color: ${({ theme }) => theme.background.transparent.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
box-shadow: ${({ theme }) => theme.boxShadow.light};
display: flex;
flex-direction: column;
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
`;
const StyledTooltipItem = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
`;
const StyledTooltipColorCircle = styled.div<{ color: string }>`
background-color: ${({ color }) => color};
border-radius: 50%;
height: 8px;
margin-right: ${({ theme }) => theme.spacing(2)};
width: 8px;
`;
const StyledTooltipDataRow = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
color: ${({ theme }) => theme.font.color.tertiary};
gap: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
const StyledTooltipValue = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
`;
type WorkerMetricsTooltipProps = {
slice: {
points: readonly Point[];
};
};
export const WorkerMetricsTooltip = ({
slice,
}: WorkerMetricsTooltipProps): ReactElement => {
return (
<StyledTooltipContainer>
{slice.points.map((point) => (
<StyledTooltipItem key={point.id} color={point.serieColor}>
<StyledTooltipColorCircle color={point.serieColor} />
<StyledTooltipDataRow>
<span>{point.serieId}</span>
<StyledTooltipValue>{String(point.data.y)}</StyledTooltipValue>
</StyledTooltipDataRow>
</StyledTooltipItem>
))}
</StyledTooltipContainer>
);
};

View File

@ -1,3 +1,5 @@
import { Select } from '@/ui/input/components/Select';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { H2Title, Section } from 'twenty-ui';
@ -11,19 +13,55 @@ type WorkerQueueMetricsSectionProps = {
queue: AdminPanelWorkerQueueHealth;
};
const StyledControlsContainer = styled.div`
display: flex;
align-items: flex-start;
justify-content: space-between;
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
margin-bottom: ${({ theme }) => theme.spacing(8)};
`;
export const WorkerQueueMetricsSection = ({
queue,
}: WorkerQueueMetricsSectionProps) => {
const [timeRange, setTimeRange] = useState(QueueMetricsTimeRange.OneHour);
return (
<Section>
<H2Title title={queue.queueName} description={t`Queue performance`} />
<StyledContainer>
<Section>
<StyledControlsContainer>
<H2Title title={queue.queueName} description={t`Queue performance`} />
<Select
dropdownId={`timerange-${queue.queueName}`}
value={timeRange}
options={[
{ value: QueueMetricsTimeRange.SevenDays, label: t`This week` },
{ value: QueueMetricsTimeRange.OneDay, label: t`Today` },
{
value: QueueMetricsTimeRange.TwelveHours,
label: t`Last 12 hours`,
},
{
value: QueueMetricsTimeRange.FourHours,
label: t`Last 4 hours`,
},
{ value: QueueMetricsTimeRange.OneHour, label: t`Last 1 hour` },
]}
onChange={setTimeRange}
needIconCheck
selectSizeVariant="small"
/>
</StyledControlsContainer>
</Section>
<WorkerMetricsGraph
queueName={queue.queueName}
timeRange={timeRange}
onTimeRangeChange={setTimeRange}
/>
</Section>
</StyledContainer>
);
};

View File

@ -5,9 +5,9 @@ import { Link } from 'react-router-dom';
import { isDefined } from 'twenty-shared';
import { CardContent, IconChevronRight, IconComponent } from 'twenty-ui';
const StyledRow = styled(CardContent)`
const StyledRow = styled(CardContent)<{ to?: boolean }>`
align-items: center;
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
cursor: ${({ onClick, to }) => (onClick || to ? 'pointer' : 'default')};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
@ -15,6 +15,11 @@ const StyledRow = styled(CardContent)`
padding: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(3)};
min-height: ${({ theme }) => theme.spacing(6)};
&:hover {
${({ to, theme }) =>
to && `background: ${theme.background.transparent.light};`}
}
`;
const StyledRightContainer = styled.div`
@ -36,12 +41,8 @@ const StyledDescription = styled.span`
`;
const StyledLink = styled(Link)`
text-decoration: none;
color: ${({ theme }) => theme.font.color.secondary};
&:hover {
color: ${({ theme }) => theme.font.color.secondary};
}
text-decoration: none;
`;
type SettingsListItemCardContentProps = {
@ -68,7 +69,7 @@ export const SettingsListItemCardContent = ({
const theme = useTheme();
const content = (
<StyledRow onClick={onClick} divider={divider}>
<StyledRow onClick={onClick} divider={divider} to={!!to}>
{!!LeftIcon && (
<LeftIcon
size={theme.icon.size.md}

View File

@ -8,7 +8,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useParams } from 'react-router-dom';
import { H3Title, Section } from 'twenty-ui';
import { H2Title, H3Title, Section } from 'twenty-ui';
import {
AdminPanelHealthServiceStatus,
HealthIndicatorId,
@ -16,23 +16,10 @@ import {
} from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledH3Title = styled(H3Title)`
margin-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledDescription = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
`;
const StyledHealthStatusContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(2)};
`;
@ -87,23 +74,25 @@ export const SettingsAdminIndicatorHealthStatus = () => {
>
<Section>
<StyledTitleContainer>
<StyledH3Title
title={`${data?.getIndicatorHealthStatus?.label}`}
/>
<H3Title title={data?.getIndicatorHealthStatus?.label} />
{data?.getIndicatorHealthStatus?.status && (
<StyledHealthStatusContainer>
<SettingsAdminHealthStatusRightContainer
status={data?.getIndicatorHealthStatus.status}
/>
</StyledHealthStatusContainer>
<SettingsAdminHealthStatusRightContainer
status={data?.getIndicatorHealthStatus.status}
/>
)}
</StyledTitleContainer>
<StyledDescription>
{data?.getIndicatorHealthStatus?.description}
</StyledDescription>
</Section>
<SettingsAdminIndicatorHealthStatusContent />
<Section>
{data?.getIndicatorHealthStatus?.id !== HealthIndicatorId.worker &&
data?.getIndicatorHealthStatus?.id !==
HealthIndicatorId.connectedAccount && (
<H2Title
title={t`Status`}
description={data?.getIndicatorHealthStatus?.description}
/>
)}
<SettingsAdminIndicatorHealthStatusContent />
</Section>
</SettingsAdminIndicatorHealthContext.Provider>
</SettingsPageContainer>
</SubMenuTopBarContainer>

View File

@ -5,13 +5,11 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { H2Title, Section } from 'twenty-ui';
import { H2Title } 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 StyledGroupContainer = styled.div``;
export const SettingsAdminSecondaryEnvVariables = () => {
const { data: environmentVariables, loading: environmentVariablesLoading } =
@ -30,6 +28,7 @@ export const SettingsAdminSecondaryEnvVariables = () => {
return (
<SubMenuTopBarContainer
title={t`Other Environment Variables`}
links={[
{
children: t`Other`,
@ -45,16 +44,14 @@ export const SettingsAdminSecondaryEnvVariables = () => {
]}
>
<SettingsPageContainer>
<Section>
{hiddenGroups.map((group) => (
<StyledGroupContainer key={group.name}>
<H2Title title={group.name} description={group.description} />
{group.variables.length > 0 && (
<SettingsAdminEnvVariablesTable variables={group.variables} />
)}
</StyledGroupContainer>
))}
</Section>
{hiddenGroups.map((group) => (
<StyledGroupContainer key={group.name}>
<H2Title title={group.name} description={group.description} />
{group.variables.length > 0 && (
<SettingsAdminEnvVariablesTable variables={group.variables} />
)}
</StyledGroupContainer>
))}
</SettingsPageContainer>
</SubMenuTopBarContainer>
);

View File

@ -10,27 +10,27 @@ export const HEALTH_INDICATORS: Record<HealthIndicatorId, HealthIndicatorInfo> =
{
[HealthIndicatorId.database]: {
id: HealthIndicatorId.database,
label: 'Database Status',
label: 'Database',
description: 'PostgreSQL database connection status',
},
[HealthIndicatorId.redis]: {
id: HealthIndicatorId.redis,
label: 'Redis Status',
label: 'Redis',
description: 'Redis connection status',
},
[HealthIndicatorId.worker]: {
id: HealthIndicatorId.worker,
label: 'Worker Status',
description: 'Background job worker status',
label: 'Worker',
description: 'Background job worker health status',
},
[HealthIndicatorId.connectedAccount]: {
id: HealthIndicatorId.connectedAccount,
label: 'Connected Account Status',
description: 'Connected accounts status',
label: 'Connected Accounts',
description: 'Connected accounts health status',
},
[HealthIndicatorId.app]: {
id: HealthIndicatorId.app,
label: 'App Status',
label: 'App',
description: 'Workspace metadata migration status check',
},
};

View File

@ -4,18 +4,18 @@ export {
IconAlertCircle,
IconAlertTriangle,
IconApi,
IconAppWindow,
IconApps,
IconAppWindow,
IconArchive,
IconArchiveOff,
IconArrowBackUp,
IconArrowDown,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpRight,
IconArrowsDiagonal,
IconArrowsVertical,
IconArrowUp,
IconArrowUpRight,
IconAt,
IconBaselineDensitySmall,
IconBell,
@ -47,8 +47,8 @@ export {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconChevronUp,
IconChevronsRight,
IconChevronUp,
IconCircleDot,
IconCircleOff,
IconCirclePlus,
@ -128,6 +128,7 @@ export {
IconExternalLink,
IconEye,
IconEyeOff,
IconEyeShare,
IconFile,
IconFileCheck,
IconFileExport,
@ -167,6 +168,7 @@ export {
IconHistoryToggle,
IconHome,
IconHours24,
IconId,
IconInbox,
IconInfoCircle,
IconJson,

View File

@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { OverflowingTextWithTooltip } from '@ui/display/tooltip/OverflowingTextWithTooltip';
type H2TitleProps = {
title: string;
@ -45,6 +46,14 @@ export const H2Title = ({
<StyledTitle>{title}</StyledTitle>
{adornment}
</StyledTitleContainer>
{description && <StyledDescription>{description}</StyledDescription>}
{description && (
<StyledDescription>
<OverflowingTextWithTooltip
text={description}
displayedMaxRows={2}
isTooltipMultiline={true}
/>
</StyledDescription>
)}
</StyledContainer>
);