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:
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user