Skip to content

Commit e4d34df

Browse files
committed
Add backend health check before attempting to read other server endpoints
This way, if the server isn't running, we can show an accurate error message
1 parent 8697185 commit e4d34df

2 files changed

Lines changed: 47 additions & 4 deletions

File tree

api/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ func setupCommonRoutes(r *gin.Engine, runbookPath string, outputPath string, reg
2323
panic(fmt.Sprintf("failed to get embedded assets filesystem: %v", err))
2424
}
2525

26+
// Health check endpoint - used by frontend to detect if backend is running
27+
r.GET("/api/health", func(c *gin.Context) {
28+
c.JSON(http.StatusOK, gin.H{"status": "ok"})
29+
})
30+
2631
// API endpoint to serve the runbook file contents
2732
r.POST("/api/file", HandleFileRequest(runbookPath))
2833

web/src/contexts/ExecutableRegistryContext.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,33 @@ export function ExecutableRegistryProvider({ children }: ExecutableRegistryProvi
1515
const [error, setError] = useState<AppError | null>(null)
1616
const [useExecutableRegistry, setUseExecutableRegistry] = useState(true)
1717

18-
const fetchRegistry = async () => {
18+
// Check if the backend server is reachable
19+
const checkBackendHealth = async (): Promise<boolean> => {
1920
try {
20-
setLoading(true)
21-
setError(null)
21+
const response = await fetch('/api/health')
22+
return response.ok
23+
} catch {
24+
return false
25+
}
26+
}
2227

28+
const fetchRegistry = async () => {
29+
setLoading(true)
30+
setError(null)
31+
32+
// First, check if the backend is reachable
33+
const isBackendHealthy = await checkBackendHealth()
34+
if (!isBackendHealthy) {
35+
setError(createAppError(
36+
'Cannot connect to backend server',
37+
'The Runbooks backend server is not responding. It may not be running or may have crashed.'
38+
))
39+
setLoading(false)
40+
return
41+
}
42+
43+
// Backend is healthy, now fetch the registry
44+
try {
2345
const response = await fetch('/api/runbook/executables')
2446

2547
if (!response.ok) {
@@ -82,11 +104,27 @@ export function ExecutableRegistryProvider({ children }: ExecutableRegistryProvi
82104

83105
// Show error state if registry fails to load
84106
if (error) {
107+
const isConnectionError = error.message === 'Cannot connect to backend server'
108+
85109
return (
86110
<div className="p-8 text-center bg-red-50 border-2 border-red-600 m-8 rounded-lg w-2xl mx-auto">
87111
<h2 className="text-red-600 text-2xl font-semibold mb-3">{error.message}</h2>
88112
<p className="text-lg mb-2">{error.details}</p>
89-
{error.details && (
113+
{isConnectionError ? (
114+
<div className="text-gray-600 text-sm mt-4 w-xl text-center mx-auto space-y-2">
115+
<p>The Runbooks backend server needs to be running for this page to work.</p>
116+
<p>Start it with one of these commands:</p>
117+
<code className="block bg-gray-100 p-2 rounded mt-2 font-mono text-sm">
118+
runbooks open /path/to/your-runbook
119+
</code>
120+
<code className="block bg-gray-100 p-2 rounded font-mono text-sm">
121+
runbooks watch /path/to/your-runbook
122+
</code>
123+
<code className="block bg-gray-100 p-2 rounded font-mono text-sm">
124+
runbooks serve /path/to/your-runbook
125+
</code>
126+
</div>
127+
) : (
90128
<p className="text-gray-600 text-sm mt-2 w-xl text-center mx-auto">
91129
The executable registry is a list of all the "executables" like files, scripts, or commands in the runbook. We use the executable registry so that only Runbook-authored execuables are actually executed, not arbitrary scripts. The Executable Registry loads at runtime, but it looks like we hit an error trying to do that.
92130
</p>

0 commit comments

Comments
 (0)