Health monitor status for admin panel (#10186)

# Health Monitoring for Self-Hosted Instances

This PR implements basic health monitoring for self-hosted instances in
the admin panel.

## Service Status Checks
We're adding real-time health checks for:
- Redis Connection
- Database Connection
- Worker Status
- Message Sync Status

## Existing Functionality
We already have message sync and captcha counters that store aggregated
metrics in cache within a configurable time window (default: 5 minutes).

## New Endpoints
1. `/healthz` - Basic server health check for Kubernetes pod monitoring
2. `/healthz/{serviceName}` - Individual service health checks (returns
200 if healthy)
3. `/metricsz/{metricName}` - Time-windowed metrics (message sync,
captcha)
4. GraphQL resolver in admin panel for UI consumption

All endpoints use the same underlying service, with different
presentation layers for infrastructure and UI needs.

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
nitin
2025-02-18 20:22:19 +05:30
committed by GitHub
parent 2fca60436b
commit d6655a2c3b
54 changed files with 2307 additions and 95 deletions

View File

@ -1,12 +1,6 @@
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import {
GithubVersionLink,
H2Title,
IconWorld,
Section,
UndecoratedLink,
} from 'twenty-ui';
import { H2Title, IconWorld, Section, UndecoratedLink } from 'twenty-ui';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { SettingsCard } from '@/settings/components/SettingsCard';
@ -18,7 +12,6 @@ import { WorkspaceLogoUploader } from '@/settings/workspace/components/Workspace
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import packageJson from '../../../package.json';
export const SettingsWorkspace = () => {
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
@ -70,9 +63,6 @@ export const SettingsWorkspace = () => {
<Section>
<DeleteWorkspace />
</Section>
<Section>
<GithubVersionLink version={packageJson.version} />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);

View File

@ -0,0 +1,137 @@
import { SettingsAdminQueueExpandableContainer } from '@/settings/admin-panel/components/SettingsAdminQueueExpandableContainer';
import { SettingsAdminQueueHealthButtons } from '@/settings/admin-panel/components/SettingsAdminQueueHealthButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { H2Title, Section, Status } from 'twenty-ui';
import {
AdminPanelHealthServiceStatus,
AdminPanelIndicatorHealthStatusInputEnum,
useGetIndicatorHealthStatusQuery,
} from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledStatusContainer = styled.div``;
const StyledTitleContainer = styled.div`
display: flex;
flex-direction: column;
flex: 1;
`;
const StyledErrorMessage = styled.div`
color: ${({ theme }) => theme.color.red};
margin-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledDetailsContainer = styled.pre`
background-color: ${({ theme }) => theme.background.quaternary};
padding: ${({ theme }) => theme.spacing(6)};
border-radius: ${({ theme }) => theme.border.radius.sm};
white-space: pre-wrap;
font-size: ${({ theme }) => theme.font.size.sm};
`;
export const SettingsAdminIndicatorHealthStatus = () => {
const { t } = useLingui();
const { indicatorName } = useParams();
const { data, loading } = useGetIndicatorHealthStatusQuery({
variables: {
indicatorName: indicatorName as AdminPanelIndicatorHealthStatusInputEnum,
},
});
const formattedDetails = data?.getIndicatorHealthStatus.details
? JSON.stringify(JSON.parse(data.getIndicatorHealthStatus.details), null, 2)
: null;
const isWorkerDown =
!data?.getIndicatorHealthStatus.status ||
data?.getIndicatorHealthStatus.status ===
AdminPanelHealthServiceStatus.OUTAGE;
const [selectedQueue, setSelectedQueue] = useState<string | null>(null);
const toggleQueueVisibility = (queueName: string) => {
setSelectedQueue(selectedQueue === queueName ? null : queueName);
};
return (
<SubMenuTopBarContainer
links={[
{
children: t`Other`,
href: getSettingsPath(SettingsPath.AdminPanel),
},
{
children: t`Server Admin Panel`,
href: getSettingsPath(SettingsPath.AdminPanel),
},
{
children: t`Health Status`,
href: getSettingsPath(SettingsPath.AdminPanelHealthStatus),
},
{ children: `${indicatorName}` },
]}
>
<SettingsPageContainer>
<Section>
<H2Title title={`${indicatorName}`} description="Health status" />
<StyledStatusContainer>
{data?.getIndicatorHealthStatus.status ===
AdminPanelHealthServiceStatus.OPERATIONAL && (
<Status color="green" text="Operational" weight="medium" />
)}
{data?.getIndicatorHealthStatus.status ===
AdminPanelHealthServiceStatus.OUTAGE && (
<Status color="red" text="Outage" weight="medium" />
)}
</StyledStatusContainer>
</Section>
{indicatorName === AdminPanelIndicatorHealthStatusInputEnum.WORKER ? (
<Section>
<StyledTitleContainer>
<H2Title
title="Queue Status"
description="Background job processing status and metrics"
/>
</StyledTitleContainer>
{isWorkerDown && !loading ? (
<StyledErrorMessage>
Queue information is not available because the worker is down
</StyledErrorMessage>
) : (
<>
<SettingsAdminQueueHealthButtons
queues={data?.getIndicatorHealthStatus.queues ?? []}
selectedQueue={selectedQueue}
toggleQueueVisibility={toggleQueueVisibility}
/>
<SettingsAdminQueueExpandableContainer
queues={data?.getIndicatorHealthStatus.queues ?? []}
selectedQueue={selectedQueue}
/>
</>
)}
</Section>
) : null}
{indicatorName === AdminPanelIndicatorHealthStatusInputEnum.DATABASE ||
indicatorName === AdminPanelIndicatorHealthStatusInputEnum.REDIS ? (
<Section>
{formattedDetails && (
<StyledDetailsContainer>
{formattedDetails}
</StyledDetailsContainer>
)}
</Section>
) : null}
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};