import { useParams } from 'common' import dayjs from 'dayjs' import { AlertTriangle, CheckCircle2, ChevronRight, Loader2, MinusCircle } from 'lucide-react' import Link from 'next/link' import { cn, HoverCard, HoverCardContent, HoverCardTrigger, InfoIcon } from 'ui' import { useUnifiedLogsPreview } from '../App/FeaturePreview/FeaturePreviewContext' import { resolveRealtimeServiceStatus, type ProjectServiceStatus } from './ServiceStatus.utils' import { InlineLink } from '@/components/ui/InlineLink' import { SingleStat } from '@/components/ui/SingleStat' import { useBranchesQuery } from '@/data/branches/branches-query' import { useEdgeFunctionServiceStatusQuery } from '@/data/service-status/edge-functions-status-query' import { useProjectServiceStatusQuery, type ServiceHealthResponse, } from '@/data/service-status/service-status-query' import { useHighAvailability } from '@/hooks/misc/useHighAvailability' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' const SERVICE_STATUS_THRESHOLD = 5 // minutes const SERVICE_ROW_CLASS = 'px-3 py-2 text-xs flex items-center justify-between border-b last:border-none' const iconProps = { size: 18, strokeWidth: 1.5, } const LoaderIcon = () => const AlertIcon = () => const CheckIcon = () => const DisabledIcon = () => export const StatusIcon = ({ isLoading, isProjectNew, projectStatus, }: { isLoading: boolean isProjectNew: boolean projectStatus?: ProjectServiceStatus }) => { // if (projectStatus === 'ACTIVE_HEALTHY') return if (projectStatus === 'DISABLED') return if (projectStatus === 'COMING_UP') return if (isLoading) return // isProjectNew has to be above UNHEALTHY because in the first few minutes, some services might be starting up and show as UNHEALTHY if (isProjectNew) return if (projectStatus === 'UNHEALTHY') return return } const StatusMessage = ({ status, isLoading, isProjectNew, }: { isLoading: boolean isProjectNew: boolean status?: ProjectServiceStatus }) => { if (isLoading) return 'Checking status' if (status === 'DISABLED') return 'Disabled' if (status === 'UNHEALTHY') return 'Unhealthy' if (status === 'COMING_UP') return 'Coming up...' if (status === 'ACTIVE_HEALTHY') return 'Healthy' // isProjectNew has to be after all other statuses if (isProjectNew) return 'Coming up...' if (status) return status return 'Unable to connect' } /* * Extract the db_schema from the response.info object */ const extractDbSchema = (response: ServiceHealthResponse | undefined) => { if (response?.info && 'db_schema' in response.info) { return response.info.db_schema } return undefined } /** * [Joshen] JFYI before we go live with this, we need to revisit the migrations section * as I don't think it should live in the ServiceStatus component since its not indicative * of a project's "service". ServiceStatus's intention is to be an ongoing health/status check. * * For context, migrations are meant to be indicative for only when creating branches or projects * with an initial SQL, so "healthy" migrations just means that migrations have all been successfully * ran. So it might be a matter of decoupling "ready" state vs "health checks" * [Edit] Now that migrations are only showing up if the project is a branch, i think its okay for now * * [Joshen] Another issue that requires investigation before we go live with the changes: * We've removed the isProjectNew check in this component which we had that logic cause new * projects would show unhealthy as the services are still starting up - but it causes a * perceived negative impression as new projects were showing unhealthy, hence the 5 minute * threshold check (we’d show “Coming up” instead of “unhealthy” if the project is within 5 * minutes of when it was created). Might be related to decoupling "ready" state vs "health checks" */ export const ServiceStatus = () => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const { isHighAvailability } = useHighAvailability() const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview() const { projectAuthAll: authEnabled, projectEdgeFunctionAll: edgeFunctionsEnabled, realtimeAll: realtimeEnabled, projectStorageAll: storageEnabled, } = useIsFeatureEnabled([ 'project_auth:all', 'project_edge_function:all', 'realtime:all', 'project_storage:all', ]) const isBranch = project?.parentRef !== project?.ref // Get branches data when on a branch const { data: branches, isPending: isBranchesLoading } = useBranchesQuery( { projectRef: isBranch ? project?.parentRef : undefined }, { enabled: isBranch, } ) const currentBranch = isBranch ? branches?.find((branch) => branch.project_ref === ref) : undefined // [Joshen] Need pooler service check eventually const { data: status, isPending: isLoading } = useProjectServiceStatusQuery( { projectRef: ref }, { refetchInterval: (query) => { const data = query.state.data const isServiceUnhealthy = data?.some((service) => { if (isHighAvailability && service.name === 'realtime') { return false } // if the postgrest service has an empty schema, postgrest has been disabled if (service.name === 'rest' && extractDbSchema(service) === '') { return false } if (service.status === 'ACTIVE_HEALTHY') { return false } return true }) return isServiceUnhealthy ? 5000 : false }, } ) const { data: edgeFunctionsStatus } = useEdgeFunctionServiceStatusQuery( { projectRef: ref }, { refetchInterval: (query) => (!query.state.data?.healthy ? 5000 : false) } ) const authStatus = status?.find((service) => service.name === 'auth') const restStatus = status?.find((service) => service.name === 'rest') const realtimeStatus = status?.find((service) => service.name === 'realtime') const storageStatus = status?.find((service) => service.name === 'storage') const dbStatus = status?.find((service) => service.name === 'db') const isMigrationLoading = project?.status === 'COMING_UP' || (isBranch && (isBranchesLoading || currentBranch?.status === 'CREATING_PROJECT' || currentBranch?.status === 'RUNNING_MIGRATIONS')) // [Joshen] Need individual troubleshooting docs for each service eventually for users to self serve const services: { name: string error?: string docsUrl?: string isLoading: boolean status: ProjectServiceStatus logsUrl: string }[] = [ { name: 'Database', error: undefined, docsUrl: undefined, isLoading: isLoading, status: dbStatus?.status ?? 'UNHEALTHY', logsUrl: isUnifiedLogsEnabled ? '/logs?filter=log_type:eq:postgres' : '/logs/postgres-logs', }, { name: 'PostgREST', error: restStatus?.error, docsUrl: undefined, isLoading, // If PostgREST has an empty schema, it means it's been disabled status: extractDbSchema(restStatus) === '' ? 'DISABLED' : (restStatus?.status ?? 'UNHEALTHY'), logsUrl: isUnifiedLogsEnabled ? '/logs?filter=log_type:eq:postgrest' : '/logs/postgrest-logs', }, ...(authEnabled ? [ { name: 'Auth', error: authStatus?.error, docsUrl: undefined, isLoading, status: authStatus?.status ?? 'UNHEALTHY', logsUrl: isUnifiedLogsEnabled ? '/logs?filter=log_type:eq:auth' : '/logs/auth-logs', }, ] : []), ...(realtimeEnabled ? [ { name: 'Realtime', error: realtimeStatus?.error, docsUrl: undefined, isLoading, status: resolveRealtimeServiceStatus(isHighAvailability, realtimeStatus?.status), logsUrl: isUnifiedLogsEnabled ? '/logs?filter=log_type:eq:realtime' : '/logs/realtime-logs', }, ] : []), ...(storageEnabled ? [ { name: 'Storage', error: storageStatus?.error, docsUrl: undefined, isLoading, status: storageStatus?.status ?? 'UNHEALTHY', logsUrl: isUnifiedLogsEnabled ? '/logs?filter=log_type:eq:storage' : '/logs/storage-logs', }, ] : []), ...(edgeFunctionsEnabled ? [ { name: 'Edge Functions', error: undefined, docsUrl: `${DOCS_URL}/guides/functions/troubleshooting`, isLoading, status: edgeFunctionsStatus?.healthy ? ('ACTIVE_HEALTHY' as const) : isLoading ? ('COMING_UP' as const) : ('UNHEALTHY' as const), logsUrl: isUnifiedLogsEnabled ? '/logs?filter=log_type:eq:edge+function' : '/logs/edge-functions-logs', }, ] : []), ...(isBranch ? [ { name: 'Migrations', error: undefined, docsUrl: undefined, isLoading: isBranchesLoading, status: isBranch ? currentBranch?.status === 'FUNCTIONS_DEPLOYED' ? ('ACTIVE_HEALTHY' as const) : currentBranch?.status === 'FUNCTIONS_FAILED' || currentBranch?.status === 'MIGRATIONS_FAILED' ? ('UNHEALTHY' as const) : ('COMING_UP' as const) : isMigrationLoading ? 'COMING_UP' : 'ACTIVE_HEALTHY', logsUrl: isBranch ? '/branches' : '/logs/database-logs', }, ] : []), ] const isLoadingChecks = services.some((service) => service.isLoading) // We consider a service operational if it's healthy or intentionally disabled const allServicesOperational = services.every( (service) => service.status === 'ACTIVE_HEALTHY' || service.status === 'DISABLED' ) // Check if project or branch is in a startup state const isProjectNew = dayjs.utc().diff(dayjs.utc(project?.inserted_at), 'minute') < SERVICE_STATUS_THRESHOLD || project?.status === 'COMING_UP' || (isBranch && (currentBranch?.status === 'CREATING_PROJECT' || currentBranch?.status === 'RUNNING_MIGRATIONS' || isMigrationLoading)) const isProjectComingUp = ['COMING_UP', 'UNKNOWN'].includes(project?.status ?? '') const anyUnhealthy = services.some((service) => service.status === 'UNHEALTHY') const anyComingUp = isProjectComingUp || services.some((service) => service.status === 'COMING_UP') const getOverallStatusLabel = (): string => { if (isLoadingChecks) return 'Checking...' if (anyComingUp) return 'Coming up...' if (anyUnhealthy) return 'Unhealthy' return 'Healthy' } const overallStatusLabel = getOverallStatusLabel() return ( ) : ( {services.map((service, index) => ( ))} ) } label={Status} value={{overallStatusLabel}} /> {services.map((service) => { const serviceInfo = ( {service.name} ) // Disabled services have no logs to link out to if (service.status === 'DISABLED') { return ( {serviceInfo} ) } return ( {serviceInfo} View logs ) })} {!allServicesOperational && ( {isProjectNew ? 'New' : 'Recently restored'} projects can take up to{' '} {SERVICE_STATUS_THRESHOLD} minutes to become fully operational. If services stay unhealthy, refer to our{' '} docs {' '} for more information. )} ) }
{service.name}
{isProjectNew ? 'New' : 'Recently restored'} projects can take up to{' '} {SERVICE_STATUS_THRESHOLD} minutes to become fully operational.
If services stay unhealthy, refer to our{' '} docs {' '} for more information.