1- import { AlertTriangle , CheckCircle2 , Loader2 } from 'lucide-react'
2- import { useState } from 'react'
1+ import dayjs from 'dayjs'
2+ import { AlertTriangle , CheckCircle2 , ChevronRight , Loader2 } from 'lucide-react'
3+ import Link from 'next/link'
4+ import { useEffect , useState } from 'react'
35
46import { useParams } from 'common'
57import { useEdgeFunctionServiceStatusQuery } from 'data/service-status/edge-functions-status-query'
@@ -9,6 +11,37 @@ import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
911import { useSelectedProject } from 'hooks/misc/useSelectedProject'
1012import { Button , PopoverContent_Shadcn_ , PopoverTrigger_Shadcn_ , Popover_Shadcn_ } from 'ui'
1113
14+ const SERVICE_STATUS_THRESHOLD = 5 // minutes
15+
16+ const StatusMessage = ( {
17+ isLoading,
18+ isSuccess,
19+ isProjectNew,
20+ } : {
21+ isLoading : boolean
22+ isSuccess : boolean
23+ isProjectNew : boolean
24+ } ) => {
25+ if ( isLoading ) return 'Checking status'
26+ if ( isProjectNew ) return 'Coming up...'
27+ if ( isSuccess ) return 'No issues'
28+ return 'Unable to connect'
29+ }
30+
31+ const StatusIcon = ( {
32+ isLoading,
33+ isSuccess,
34+ isProjectNew,
35+ } : {
36+ isLoading : boolean
37+ isSuccess : boolean
38+ isProjectNew : boolean
39+ } ) => {
40+ if ( isLoading || isProjectNew ) return < Loader2 size = { 14 } className = "animate-spin" />
41+ if ( isSuccess ) return < CheckCircle2 className = "text-brand" size = { 18 } strokeWidth = { 1.5 } />
42+ return < AlertTriangle className = "text-warning" size = { 18 } strokeWidth = { 1.5 } />
43+ }
44+
1245const ServiceStatus = ( ) => {
1346 const { ref } = useParams ( )
1447 const project = useSelectedProject ( )
@@ -29,13 +62,21 @@ const ServiceStatus = () => {
2962 const isBranch = project ?. parentRef !== project ?. ref
3063
3164 // [Joshen] Need pooler service check eventually
32- const { data : status , isLoading } = useProjectServiceStatusQuery ( { projectRef : ref } )
33- const { data : edgeFunctionsStatus } = useEdgeFunctionServiceStatusQuery ( { projectRef : ref } )
34- const { isLoading : isLoadingPostgres , isSuccess : isSuccessPostgres } =
35- usePostgresServiceStatusQuery ( {
36- projectRef : ref ,
37- connectionString : project ?. connectionString ,
38- } )
65+ const {
66+ data : status ,
67+ isLoading,
68+ refetch : refetchServiceStatus ,
69+ } = useProjectServiceStatusQuery ( { projectRef : ref } )
70+ const { data : edgeFunctionsStatus , refetch : refetchEdgeFunctionServiceStatus } =
71+ useEdgeFunctionServiceStatusQuery ( { projectRef : ref } )
72+ const {
73+ isLoading : isLoadingPostgres ,
74+ isSuccess : isSuccessPostgres ,
75+ refetch : refetchPostgresServiceStatus ,
76+ } = usePostgresServiceStatusQuery ( {
77+ projectRef : ref ,
78+ connectionString : project ?. connectionString ,
79+ } )
3980
4081 const authStatus = status ?. find ( ( service ) => service . name === 'auth' )
4182 const restStatus = status ?. find ( ( service ) => service . name === 'rest' )
@@ -49,20 +90,23 @@ const ServiceStatus = () => {
4990 docsUrl ?: string
5091 isLoading : boolean
5192 isSuccess ?: boolean
93+ logsUrl : string
5294 } [ ] = [
5395 {
5496 name : 'Database' ,
5597 error : undefined ,
5698 docsUrl : undefined ,
5799 isLoading : isLoadingPostgres ,
58100 isSuccess : isSuccessPostgres ,
101+ logsUrl : '/logs/postgres-logs' ,
59102 } ,
60103 {
61104 name : 'PostgREST' ,
62105 error : restStatus ?. error ,
63106 docsUrl : undefined ,
64107 isLoading,
65108 isSuccess : restStatus ?. healthy ,
109+ logsUrl : '/logs/postgrest-logs' ,
66110 } ,
67111 ...( authEnabled
68112 ? [
@@ -72,6 +116,7 @@ const ServiceStatus = () => {
72116 docsUrl : undefined ,
73117 isLoading,
74118 isSuccess : authStatus ?. healthy ,
119+ logsUrl : '/logs/auth-logs' ,
75120 } ,
76121 ]
77122 : [ ] ) ,
@@ -83,6 +128,7 @@ const ServiceStatus = () => {
83128 docsUrl : undefined ,
84129 isLoading,
85130 isSuccess : realtimeStatus ?. healthy ,
131+ logsUrl : '/logs/realtime-logs' ,
86132 } ,
87133 ]
88134 : [ ] ) ,
@@ -94,6 +140,7 @@ const ServiceStatus = () => {
94140 docsUrl : undefined ,
95141 isLoading,
96142 isSuccess : storageStatus ?. healthy ,
143+ logsUrl : '/logs/storage-logs' ,
97144 } ,
98145 ]
99146 : [ ] ) ,
@@ -105,6 +152,7 @@ const ServiceStatus = () => {
105152 docsUrl : 'https://supabase.com/docs/guides/functions/troubleshooting' ,
106153 isLoading,
107154 isSuccess : edgeFunctionsStatus ?. healthy ,
155+ logsUrl : '/logs/edge-functions-logs' ,
108156 } ,
109157 ]
110158 : [ ] ) ,
@@ -113,13 +161,39 @@ const ServiceStatus = () => {
113161 const isLoadingChecks = services . some ( ( service ) => service . isLoading )
114162 const allServicesOperational = services . every ( ( service ) => service . isSuccess )
115163
164+ // If the project is less than 5 minutes old, and status is not operational, then it's likely the service is still starting up
165+ const isProjectNew =
166+ dayjs . utc ( ) . diff ( dayjs . utc ( project ?. inserted_at ) , 'minute' ) < SERVICE_STATUS_THRESHOLD
167+
168+ useEffect ( ( ) => {
169+ let timer : any
170+
171+ if ( isProjectNew ) {
172+ const secondsSinceProjectCreated = dayjs
173+ . utc ( )
174+ . diff ( dayjs . utc ( project ?. inserted_at ) , 'seconds' )
175+ const remainingTimeTillNextCheck = SERVICE_STATUS_THRESHOLD * 60 - secondsSinceProjectCreated
176+
177+ timer = setTimeout ( ( ) => {
178+ refetchServiceStatus ( )
179+ refetchPostgresServiceStatus ( )
180+ refetchEdgeFunctionServiceStatus ( )
181+ } , remainingTimeTillNextCheck * 1000 )
182+ }
183+
184+ return ( ) => {
185+ clearTimeout ( timer )
186+ }
187+ // eslint-disable-next-line react-hooks/exhaustive-deps
188+ } , [ isProjectNew ] )
189+
116190 return (
117191 < Popover_Shadcn_ modal = { false } open = { open } onOpenChange = { setOpen } >
118192 < PopoverTrigger_Shadcn_ asChild >
119193 < Button
120194 type = "default"
121195 icon = {
122- isLoadingChecks ? (
196+ isLoadingChecks || isProjectNew ? (
123197 < Loader2 className = "animate-spin" />
124198 ) : (
125199 < div
@@ -135,28 +209,33 @@ const ServiceStatus = () => {
135209 </ PopoverTrigger_Shadcn_ >
136210 < PopoverContent_Shadcn_ className = "p-0 w-56" side = "bottom" align = "center" >
137211 { services . map ( ( service ) => (
138- < div
212+ < Link
213+ href = { `/project/${ ref } ${ service . logsUrl } ` }
139214 key = { service . name }
140- className = "px-4 py-2 text-xs flex items-center justify-between border-b last:border-none"
215+ className = "transition px-3 py-2 text-xs flex items-center justify-between border-b last:border-none group relative hover:bg-surface-300 "
141216 >
142- < div >
143- < p > { service . name } </ p >
144- < p className = "text-foreground-light" >
145- { service . isLoading
146- ? 'Checking status'
147- : service . isSuccess
148- ? 'No issues'
149- : 'Unable to connect' }
150- </ p >
217+ < div className = "flex gap-x-2" >
218+ < StatusIcon
219+ isLoading = { service . isLoading }
220+ isSuccess = { ! ! service . isSuccess }
221+ isProjectNew = { isProjectNew }
222+ />
223+ < div className = "flex-1" >
224+ < p > { service . name } </ p >
225+ < p className = "text-foreground-light flex items-center gap-1" >
226+ < StatusMessage
227+ isLoading = { service . isLoading }
228+ isSuccess = { ! ! service . isSuccess }
229+ isProjectNew = { isProjectNew }
230+ />
231+ </ p >
232+ </ div >
151233 </ div >
152- { service . isLoading ? (
153- < Loader2 className = "animate-spin text-foreground-light" size = { 18 } />
154- ) : service . isSuccess ? (
155- < CheckCircle2 className = "text-brand" size = { 18 } strokeWidth = { 1.5 } />
156- ) : (
157- < AlertTriangle className = "text-warning" size = { 18 } strokeWidth = { 1.5 } />
158- ) }
159- </ div >
234+ < div className = "flex items-center gap-x-1 transition opacity-0 group-hover:opacity-100" >
235+ < span className = "text-xs text-foreground" > View logs</ span >
236+ < ChevronRight size = { 14 } className = "text-foreground" />
237+ </ div >
238+ </ Link >
160239 ) ) }
161240 </ PopoverContent_Shadcn_ >
162241 </ Popover_Shadcn_ >
0 commit comments