From df5f7185c8bca01f42d9095ba8aaf22fabfe0bad Mon Sep 17 00:00:00 2001 From: cryptooda Date: Mon, 21 Apr 2025 18:54:28 +0700 Subject: [PATCH] Display healthcheck on prod --- src/Managing.Api.Workers/Program.cs | 54 +++--- .../healthchecks/healthChecks.tsx | 162 ++++++++++++++++++ .../src/pages/settingsPage/settings.tsx | 6 + 3 files changed, 191 insertions(+), 31 deletions(-) create mode 100644 src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx diff --git a/src/Managing.Api.Workers/Program.cs b/src/Managing.Api.Workers/Program.cs index 8aa3278..469faa5 100644 --- a/src/Managing.Api.Workers/Program.cs +++ b/src/Managing.Api.Workers/Program.cs @@ -26,23 +26,19 @@ using OpenApiSecurityScheme = NSwag.OpenApiSecurityScheme; // Builder var builder = WebApplication.CreateBuilder(args); -// Add health checks when Aspire is enabled, in all environments -if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPIRE_ENABLED"))) -{ - var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"]; - var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"]; - var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"]; - - // Add service discovery for Aspire - builder.Services.AddServiceDiscovery(); - - // Configure health checks - builder.Services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]) - .AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"]) - .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"]) - .AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]); -} +var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"]; +var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"]; +var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"]; + +// Add service discovery for Aspire +builder.Services.AddServiceDiscovery(); + +// Configure health checks +builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]) + .AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"]) + .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"]) + .AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]); builder.WebHost.UseUrls("http://localhost:5001"); builder.Configuration.SetBasePath(AppContext.BaseDirectory); @@ -177,21 +173,17 @@ app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapHub("/positionhub"); - - // Always add health check endpoints when Aspire is enabled, regardless of environment - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPIRE_ENABLED"))) + + endpoints.MapHealthChecks("/health", new HealthCheckOptions { - endpoints.MapHealthChecks("/health", new HealthCheckOptions - { - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - - endpoints.MapHealthChecks("/alive", new HealthCheckOptions - { - Predicate = r => r.Tags.Contains("live"), - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - } + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + endpoints.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live"), + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); }); app.Run(); \ No newline at end of file diff --git a/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx b/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx new file mode 100644 index 0000000..51adb4e --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx @@ -0,0 +1,162 @@ +import React, { useEffect, useState } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import { Table } from '../../../components/mollecules' + +// Define health check response interface based on the provided example +interface HealthCheckEntry { + data: Record + duration: string + status: string + tags: string[] +} + +interface HealthCheckResponse { + status: string + totalDuration: string + entries: Record +} + +const HealthChecks: React.FC = () => { + const { apiUrl, workerUrl } = useApiUrlStore() + const [apiHealth, setApiHealth] = useState(null) + const [workerHealth, setWorkerHealth] = useState(null) + const [web3ProxyHealth, setWeb3ProxyHealth] = useState(null) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + const fetchHealthChecks = async () => { + setIsLoading(true) + try { + // Fetch API health check + const apiResponse = await fetch(`${apiUrl}/health`) + if (apiResponse.ok) { + const data = await apiResponse.json() + setApiHealth(data) + } + + // Fetch Worker health check + const workerResponse = await fetch(`${workerUrl}/health`) + if (workerResponse.ok) { + const data = await workerResponse.json() + setWorkerHealth(data) + } + + // Fetch Web3Proxy health check - assuming it's accessible via the API + // This might need adjustment based on your actual deployment + const web3Response = await fetch(`${apiUrl.replace(':5000', ':5002')}/health`) + if (web3Response.ok) { + const data = await web3Response.json() + setWeb3ProxyHealth(data) + } + } catch (error) { + console.error('Error fetching health checks:', error) + } finally { + setIsLoading(false) + } + } + + fetchHealthChecks() + }, [apiUrl, workerUrl]) + + // Helper function to prepare table data from health response + const prepareHealthData = ( + service: string, + health: HealthCheckResponse | null + ) => { + if (!health) { + return [ + { + service, + component: 'N/A', + status: 'Unreachable', + duration: 'N/A', + tags: 'N/A', + }, + ] + } + + // Convert entries to rows for the table + return Object.entries(health.entries).map(([key, entry]) => ({ + service, + component: key, + status: entry.status, + duration: entry.duration, + tags: entry.tags.join(', '), + })) + } + + // Combine all health check data for display + const healthData = [ + ...prepareHealthData('Managing API', apiHealth), + ...prepareHealthData('Managing Worker', workerHealth), + ...prepareHealthData('Web3 Proxy', web3ProxyHealth), + ] + + // Define columns for the table + const columns = React.useMemo( + () => [ + { + Header: 'Service', + accessor: 'service', + disableSortBy: true, + disableFilters: true, + }, + { + Header: 'Component', + accessor: 'component', + disableSortBy: true, + disableFilters: true, + }, + { + Header: 'Status', + accessor: 'status', + Cell: ({ value }: { value: string }) => ( + + {value} + + ), + disableSortBy: true, + disableFilters: true, + }, + { + Header: 'Duration', + accessor: 'duration', + disableSortBy: true, + disableFilters: true, + }, + { + Header: 'Tags', + accessor: 'tags', + disableSortBy: true, + disableFilters: true, + }, + ], + [] + ) + + return ( +
+

System Health Status

+ {isLoading ? ( + + ) : ( + + )} + + ) +} + +export default HealthChecks diff --git a/src/Managing.WebApp/src/pages/settingsPage/settings.tsx b/src/Managing.WebApp/src/pages/settingsPage/settings.tsx index c1c6fba..05317d6 100644 --- a/src/Managing.WebApp/src/pages/settingsPage/settings.tsx +++ b/src/Managing.WebApp/src/pages/settingsPage/settings.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { Tabs } from '../../components/mollecules' import AccountSettings from './account/accountSettings' +import HealthChecks from './healthchecks/healthChecks' import MoneyManagementSettings from './moneymanagement/moneyManagement' import Theme from './theme' import DefaultConfig from './defaultConfig/defaultConfig' @@ -35,6 +36,11 @@ const tabs: TabsType = [ index: 4, label: 'Quick Start Config', }, + { + Component: HealthChecks, + index: 5, + label: 'Health Checks', + }, ] const Settings: React.FC = () => {