admin panel fast follows (#10723)
fast follows: - https://discord.com/channels/1130383047699738754/1346433965451382845 - https://discord.com/channels/1130383047699738754/1346434512757981264 - https://discord.com/channels/1130383047699738754/1346453484911853610 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import { SettingsAdminHealthAccountSyncCountersTable } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthAccountSyncCountersTable';
|
||||
import { SettingsAdminIndicatorHealthContext } from '@/settings/admin-panel/health-status/contexts/SettingsAdminIndicatorHealthContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { AdminPanelHealthServiceStatus } from '~/generated/graphql';
|
||||
|
||||
@ -44,14 +45,14 @@ export const ConnectedAccountHealthStatus = () => {
|
||||
{!isMessageSyncDown && serviceDetails.messageSync?.details && (
|
||||
<SettingsAdminHealthAccountSyncCountersTable
|
||||
details={serviceDetails.messageSync.details}
|
||||
title="Message Sync Status"
|
||||
title={t`Message Sync Status`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isCalendarSyncDown && serviceDetails.calendarSync?.details && (
|
||||
<SettingsAdminHealthAccountSyncCountersTable
|
||||
details={serviceDetails.calendarSync.details}
|
||||
title="Calendar Sync Status"
|
||||
title={t`Calendar Sync Status`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { SettingsAdminTabSkeletonLoader } from '@/settings/admin-panel/components/SettingsAdminTabSkeletonLoader';
|
||||
import { SettingsHealthStatusListCard } from '@/settings/admin-panel/health-status/components/SettingsHealthStatusListCard';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
import { useGetSystemHealthStatusQuery } from '~/generated/graphql';
|
||||
|
||||
@ -17,7 +18,10 @@ export const SettingsAdminHealthStatus = () => {
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title title="Health Status" description="How your system is doing" />
|
||||
<H2Title
|
||||
title={t`Health Status`}
|
||||
description={t`How your system is doing`}
|
||||
/>
|
||||
<SettingsHealthStatusListCard
|
||||
services={services}
|
||||
loading={loadingHealthStatus}
|
||||
|
||||
@ -1,9 +1,26 @@
|
||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SystemHealthService } from '~/generated/graphql';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import {
|
||||
IconAppWindow,
|
||||
IconComponent,
|
||||
IconDatabase,
|
||||
IconServer2,
|
||||
IconTool,
|
||||
IconUserCircle,
|
||||
} from 'twenty-ui';
|
||||
import { HealthIndicatorId, SystemHealthService } from '~/generated/graphql';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { SettingsAdminHealthStatusRightContainer } from './SettingsAdminHealthStatusRightContainer';
|
||||
|
||||
const HealthStatusIcons: { [k in HealthIndicatorId]: IconComponent } = {
|
||||
[HealthIndicatorId.database]: IconDatabase,
|
||||
[HealthIndicatorId.redis]: IconServer2,
|
||||
[HealthIndicatorId.worker]: IconTool,
|
||||
[HealthIndicatorId.connectedAccount]: IconUserCircle,
|
||||
[HealthIndicatorId.app]: IconAppWindow,
|
||||
};
|
||||
|
||||
export const SettingsHealthStatusListCard = ({
|
||||
services,
|
||||
loading,
|
||||
@ -11,16 +28,21 @@ export const SettingsHealthStatusListCard = ({
|
||||
services: Array<SystemHealthService>;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<SettingsListCard
|
||||
items={services}
|
||||
rounded={true}
|
||||
RowIconFn={(row) => HealthStatusIcons[row.id]}
|
||||
RowIconColor={theme.font.color.tertiary}
|
||||
getItemLabel={(service) => service.label}
|
||||
isLoading={loading}
|
||||
RowRightComponent={({ item: service }) => (
|
||||
<SettingsAdminHealthStatusRightContainer status={service.status} />
|
||||
)}
|
||||
to={(service) =>
|
||||
getSettingsPath(SettingsPath.ServerAdminIndicatorHealthStatus, {
|
||||
getSettingsPath(SettingsPath.AdminPanelIndicatorHealthStatus, {
|
||||
indicatorId: service.id,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { WorkerQueueMetricsSection } from '@/settings/admin-panel/health-status/components/WorkerQueueMetricsSection';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { AdminPanelHealthServiceStatus } from '~/generated/graphql';
|
||||
import { SettingsAdminIndicatorHealthContext } from '../contexts/SettingsAdminIndicatorHealthContext';
|
||||
@ -20,7 +21,7 @@ export const WorkerHealthStatus = () => {
|
||||
<>
|
||||
{isWorkerDown ? (
|
||||
<StyledErrorMessage>
|
||||
Queue information is not available because the worker is down
|
||||
{t`Queue information is not available because the worker is down`}
|
||||
</StyledErrorMessage>
|
||||
) : (
|
||||
(indicatorHealth.queues ?? []).map((queue) => (
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ResponsiveLine } from '@nivo/line';
|
||||
|
||||
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 {
|
||||
QueueMetricsTimeRange,
|
||||
useGetQueueMetricsQuery,
|
||||
@ -142,17 +142,17 @@ export const WorkerMetricsGraph = ({
|
||||
const getAxisLabel = () => {
|
||||
switch (timeRange) {
|
||||
case QueueMetricsTimeRange.OneHour:
|
||||
return 'Last 1 Hour (oldest → newest)';
|
||||
return t`Last 1 Hour (oldest → newest)`;
|
||||
case QueueMetricsTimeRange.FourHours:
|
||||
return 'Last 4 Hours (oldest → newest)';
|
||||
return t`Last 4 Hours (oldest → newest)`;
|
||||
case QueueMetricsTimeRange.TwelveHours:
|
||||
return 'Last 12 Hours (oldest → newest)';
|
||||
return t`Last 12 Hours (oldest → newest)`;
|
||||
case QueueMetricsTimeRange.OneDay:
|
||||
return 'Last 24 Hours (oldest → newest)';
|
||||
return t`Last 24 Hours (oldest → newest)`;
|
||||
case QueueMetricsTimeRange.SevenDays:
|
||||
return 'Last 7 Days (oldest → newest)';
|
||||
return t`Last 7 Days (oldest → newest)`;
|
||||
default:
|
||||
return 'Recent Events (oldest → newest)';
|
||||
return t`Recent Events (oldest → newest)`;
|
||||
}
|
||||
};
|
||||
|
||||
@ -163,14 +163,14 @@ export const WorkerMetricsGraph = ({
|
||||
dropdownId={`timerange-${queueName}`}
|
||||
value={timeRange}
|
||||
options={[
|
||||
{ value: QueueMetricsTimeRange.SevenDays, label: 'This week' },
|
||||
{ value: QueueMetricsTimeRange.OneDay, label: 'Today' },
|
||||
{ value: QueueMetricsTimeRange.SevenDays, label: t`This week` },
|
||||
{ value: QueueMetricsTimeRange.OneDay, label: t`Today` },
|
||||
{
|
||||
value: QueueMetricsTimeRange.TwelveHours,
|
||||
label: 'Last 12 hours',
|
||||
label: t`Last 12 hours`,
|
||||
},
|
||||
{ value: QueueMetricsTimeRange.FourHours, label: 'Last 4 hours' },
|
||||
{ value: QueueMetricsTimeRange.OneHour, label: 'Last 1 hour' },
|
||||
{ value: QueueMetricsTimeRange.FourHours, label: t`Last 4 hours` },
|
||||
{ value: QueueMetricsTimeRange.OneHour, label: t`Last 1 hour` },
|
||||
]}
|
||||
onChange={onTimeRangeChange}
|
||||
needIconCheck
|
||||
@ -179,7 +179,7 @@ export const WorkerMetricsGraph = ({
|
||||
|
||||
<StyledGraphContainer>
|
||||
{loading ? (
|
||||
<StyledNoDataMessage>Loading metrics data...</StyledNoDataMessage>
|
||||
<StyledNoDataMessage>{t`Loading metrics data...`}</StyledNoDataMessage>
|
||||
) : hasData ? (
|
||||
<ResponsiveLine
|
||||
data={metricsData}
|
||||
@ -282,7 +282,7 @@ export const WorkerMetricsGraph = ({
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<StyledNoDataMessage>No metrics data available</StyledNoDataMessage>
|
||||
<StyledNoDataMessage>{t`No metrics data available`}</StyledNoDataMessage>
|
||||
)}
|
||||
</StyledGraphContainer>
|
||||
{metricsDetails && (
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
import {
|
||||
@ -17,7 +18,7 @@ export const WorkerQueueMetricsSection = ({
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title title={queue.queueName} description="Queue performance" />
|
||||
<H2Title title={queue.queueName} description={t`Queue performance`} />
|
||||
<WorkerMetricsGraph
|
||||
queueName={queue.queueName}
|
||||
timeRange={timeRange}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
export const checkTwentyVersionExists = async (
|
||||
version: string,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/twentyhq/twenty/releases/tags/v${version}`,
|
||||
);
|
||||
return response.status === 200;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
export const fetchLatestTwentyRelease = async (): Promise<string> => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://api.github.com/repos/twentyhq/twenty/releases/latest',
|
||||
);
|
||||
const data = await response.json();
|
||||
return data.tag_name.replace('v', '');
|
||||
} catch (error) {
|
||||
return 'Could not fetch latest release';
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user