update web ui
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
import React from 'react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
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<string, any>
|
||||
duration: string
|
||||
status: string
|
||||
tags: string[]
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface HealthCheckResponse {
|
||||
status: string
|
||||
totalDuration: string
|
||||
entries: Record<string, HealthCheckEntry>
|
||||
}
|
||||
|
||||
// Interface for candle timeframe check data
|
||||
interface CandleTimeframeCheck {
|
||||
CheckedTicker: string
|
||||
CheckedTimeframe: string
|
||||
StartDate: string
|
||||
LatestCandleDate?: string
|
||||
TimeDifference?: string
|
||||
Status: string
|
||||
Message: string
|
||||
}
|
||||
|
||||
interface Web3ProxyHealthDetail {
|
||||
status: string
|
||||
message: string
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
const HealthChecks: React.FC = () => {
|
||||
const {apiUrl, workerUrl} = useApiUrlStore()
|
||||
|
||||
// Use TanStack Query for API health check
|
||||
const {data: apiHealth, isLoading: isLoadingApi} = useQuery({
|
||||
queryKey: ['health', 'api'],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${apiUrl}/health`)
|
||||
if (!response.ok) throw new Error('Failed to fetch API health')
|
||||
return response.json() as Promise<HealthCheckResponse>
|
||||
}
|
||||
})
|
||||
|
||||
// Use TanStack Query for Worker health check
|
||||
const {data: workerHealth, isLoading: isLoadingWorker} = useQuery({
|
||||
queryKey: ['health', 'worker'],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${workerUrl}/health`)
|
||||
if (!response.ok) throw new Error('Failed to fetch Worker health')
|
||||
return response.json() as Promise<HealthCheckResponse>
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Determine overall loading state
|
||||
const isLoading = isLoadingApi || isLoadingWorker
|
||||
|
||||
// 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',
|
||||
description: 'Service unreachable',
|
||||
details: null,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Convert entries to rows for the table
|
||||
const results: any[] = [];
|
||||
|
||||
Object.entries(health.entries).forEach(([key, entry]) => {
|
||||
// Basic health check entry
|
||||
const baseEntry = {
|
||||
service,
|
||||
component: key,
|
||||
status: entry.status,
|
||||
duration: entry.duration,
|
||||
tags: entry.tags.join(', '),
|
||||
description: entry.description || '',
|
||||
details: null,
|
||||
};
|
||||
|
||||
// Add the base entry
|
||||
results.push(baseEntry);
|
||||
|
||||
// Special handling for candle-data to expand timeframe checks
|
||||
if (key === 'candle-data' && entry.data) {
|
||||
// Extract timeframe checks
|
||||
Object.entries(entry.data)
|
||||
.filter(([dataKey]) => dataKey.startsWith('TimeframeCheck_'))
|
||||
.forEach(([dataKey, timeframeData]) => {
|
||||
const tfData = timeframeData as CandleTimeframeCheck;
|
||||
results.push({
|
||||
service,
|
||||
component: `${key} - ${tfData.CheckedTimeframe}`,
|
||||
status: tfData.Status,
|
||||
duration: '',
|
||||
tags: 'candles',
|
||||
description: tfData.Message,
|
||||
details: {
|
||||
Ticker: tfData.CheckedTicker,
|
||||
LatestCandle: tfData.LatestCandleDate,
|
||||
TimeDifference: tfData.TimeDifference,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Special handling for Web3Proxy components
|
||||
if (key === 'web3proxy' && entry.data) {
|
||||
// Handle Privy check if present
|
||||
if (entry.data.privy) {
|
||||
const privyData = entry.data.privy as Web3ProxyHealthDetail;
|
||||
results.push({
|
||||
service,
|
||||
component: `${key} - Privy`,
|
||||
status: privyData.status,
|
||||
duration: '',
|
||||
tags: 'privy, external',
|
||||
description: privyData.message || '',
|
||||
details: null,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle GMX check if present
|
||||
if (entry.data.gmx) {
|
||||
const gmxData = entry.data.gmx as Web3ProxyHealthDetail;
|
||||
const marketDetails: Record<string, any> = {};
|
||||
|
||||
// Add market count and response time if available
|
||||
if (gmxData.data) {
|
||||
if (gmxData.data.marketCount) {
|
||||
marketDetails['Market Count'] = gmxData.data.marketCount;
|
||||
}
|
||||
if (gmxData.data.responseTimeMs) {
|
||||
marketDetails['Response Time'] = `${gmxData.data.responseTimeMs}ms`;
|
||||
}
|
||||
if (gmxData.data.uiFeeFactor) {
|
||||
marketDetails['UI Fee Factor'] = gmxData.data.uiFeeFactor;
|
||||
}
|
||||
|
||||
// Add sample markets info (just count for details section)
|
||||
if (gmxData.data.sampleMarkets && Array.isArray(gmxData.data.sampleMarkets)) {
|
||||
marketDetails['Sample Markets'] = gmxData.data.sampleMarkets.length;
|
||||
|
||||
// If there are sample markets, add the first one's details
|
||||
if (gmxData.data.sampleMarkets.length > 0) {
|
||||
const firstMarket = gmxData.data.sampleMarkets[0];
|
||||
if (firstMarket.indexToken) {
|
||||
marketDetails['Example Market'] = firstMarket.indexToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.push({
|
||||
service,
|
||||
component: `${key} - GMX`,
|
||||
status: gmxData.status,
|
||||
duration: '',
|
||||
tags: 'gmx, external',
|
||||
description: gmxData.message || '',
|
||||
details: Object.keys(marketDetails).length > 0 ? marketDetails : null,
|
||||
});
|
||||
}
|
||||
|
||||
// Add version info if available
|
||||
if (entry.data.version) {
|
||||
const versionDetails: Record<string, any> = {
|
||||
Version: entry.data.version
|
||||
};
|
||||
|
||||
if (entry.data.timestamp) {
|
||||
versionDetails['Last Updated'] = entry.data.timestamp;
|
||||
}
|
||||
|
||||
results.push({
|
||||
service,
|
||||
component: `${key} - Version`,
|
||||
status: entry.status,
|
||||
duration: '',
|
||||
tags: 'version',
|
||||
description: `Web3Proxy Version: ${entry.data.version}`,
|
||||
details: versionDetails,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Combine all health check data for display
|
||||
const healthData = [
|
||||
...prepareHealthData('Managing API', apiHealth || null),
|
||||
...prepareHealthData('Managing Worker', workerHealth || null),
|
||||
]
|
||||
|
||||
// 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 }) => (
|
||||
<span
|
||||
className={`badge ${
|
||||
value === 'Healthy' || value === 'healthy'
|
||||
? 'badge-success'
|
||||
: value === 'Unreachable' || value === 'unhealthy'
|
||||
? 'badge-error'
|
||||
: 'badge-warning'
|
||||
}`}
|
||||
>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</span>
|
||||
),
|
||||
disableSortBy: true,
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: 'description',
|
||||
disableSortBy: true,
|
||||
disableFilters: true,
|
||||
Cell: ({value, row}: any) => (
|
||||
<div>
|
||||
<div>{value}</div>
|
||||
{row.original.details && (
|
||||
<div className="text-xs mt-1 opacity-80">
|
||||
{Object.entries(row.original.details).filter(([_, val]) => val !== undefined).map(
|
||||
([key, val]) => (
|
||||
<div key={key}>
|
||||
<span className="font-semibold">{key}:</span> {String(val)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Duration',
|
||||
accessor: 'duration',
|
||||
disableSortBy: true,
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Header: 'Tags',
|
||||
accessor: 'tags',
|
||||
disableSortBy: true,
|
||||
disableFilters: true,
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="container mx-auto">
|
||||
<h2 className="text-xl font-bold mb-4">System Health Status</h2>
|
||||
{isLoading ? (
|
||||
<progress className="progress progress-primary w-56"></progress>
|
||||
) : (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={healthData}
|
||||
showPagination={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HealthChecks
|
||||
Reference in New Issue
Block a user