Refactor PlatformLineChart and Tabs components for improved layout and styling, enhance WhitelistSettings with responsive design, and implement API candles health check in HealthChecks. Update global styles for scrollbar visibility and adjust tool tabs for better organization.
This commit is contained in:
@@ -265,13 +265,18 @@ function PlatformLineChart({
|
||||
color: theme['base-content'] || '#ffffff'
|
||||
},
|
||||
bgcolor: 'rgba(0,0,0,0)',
|
||||
bordercolor: 'rgba(0,0,0,0)'
|
||||
bordercolor: 'rgba(0,0,0,0)',
|
||||
orientation: 'h' as const,
|
||||
x: 0.5,
|
||||
y: -0.15,
|
||||
xanchor: 'center' as const,
|
||||
yanchor: 'top' as const
|
||||
},
|
||||
margin: {
|
||||
l: 60,
|
||||
r: 60,
|
||||
t: 60,
|
||||
b: 60,
|
||||
b: 80,
|
||||
pad: 4
|
||||
},
|
||||
paper_bgcolor: 'rgba(0,0,0,0)',
|
||||
@@ -290,9 +295,8 @@ function PlatformLineChart({
|
||||
}
|
||||
|
||||
const config: Partial<Config> = {
|
||||
displayModeBar: true,
|
||||
displayModeBar: false,
|
||||
displaylogo: false,
|
||||
modeBarButtonsToRemove: ['pan2d', 'lasso2d', 'select2d', 'autoScale2d', 'resetScale2d'] as any,
|
||||
responsive: true
|
||||
}
|
||||
|
||||
|
||||
@@ -27,40 +27,42 @@ const Tabs: FC<ITabsProps> = ({
|
||||
orientation === 'vertical' ? className + ' vertical' : className
|
||||
}
|
||||
>
|
||||
<div className="tabs" role="tablist" aria-orientation={orientation}>
|
||||
{tabs.map((tab: any) => (
|
||||
<button
|
||||
className={
|
||||
'mb-5 tab tab-bordered ' +
|
||||
(selectedTab === tab.index ? 'tab-active' : '')
|
||||
}
|
||||
onClick={() => onClick(tab.index)}
|
||||
key={tab.index}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={selectedTab === tab.index}
|
||||
aria-controls={`tabpanel-${tab.index}`}
|
||||
tabIndex={selectedTab === tab.index ? 0 : -1}
|
||||
id={`btn-${tab.index}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
{addButton && (
|
||||
<button
|
||||
className="tab tab-bordered mb-5"
|
||||
onClick={onAddButton}
|
||||
key={'add'}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={false}
|
||||
aria-controls={`tabpanel-${'add'}`}
|
||||
tabIndex={-1}
|
||||
id={`btn-${'add'}`}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
<div className="overflow-x-auto scrollbar-hide -mx-4 px-4">
|
||||
<div className="tabs flex-nowrap min-w-max" role="tablist" aria-orientation={orientation}>
|
||||
{tabs.map((tab: any) => (
|
||||
<button
|
||||
className={
|
||||
'mb-5 tab tab-bordered whitespace-nowrap flex-shrink-0 ' +
|
||||
(selectedTab === tab.index ? 'tab-active' : '')
|
||||
}
|
||||
onClick={() => onClick(tab.index)}
|
||||
key={tab.index}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={selectedTab === tab.index}
|
||||
aria-controls={`tabpanel-${tab.index}`}
|
||||
tabIndex={selectedTab === tab.index ? 0 : -1}
|
||||
id={`btn-${tab.index}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
{addButton && (
|
||||
<button
|
||||
className="tab tab-bordered mb-5 whitespace-nowrap flex-shrink-0"
|
||||
onClick={onAddButton}
|
||||
key={'add'}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={false}
|
||||
aria-controls={`tabpanel-${'add'}`}
|
||||
tabIndex={-1}
|
||||
id={`btn-${'add'}`}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
|
||||
@@ -72,27 +72,27 @@ const WhitelistSettings: React.FC = () => {
|
||||
Search Filters
|
||||
</div>
|
||||
<div className="collapse-content">
|
||||
<div className="flex gap-4 pt-4">
|
||||
<div className="form-control w-full max-w-xs">
|
||||
<label className="label">
|
||||
<span className="label-text">Search by Ethereum Account</span>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<div className="form-control w-full max-w-[200px]">
|
||||
<label className="label py-1">
|
||||
<span className="label-text text-xs">Search by Ethereum Account</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Ethereum address..."
|
||||
className="input input-bordered w-full"
|
||||
className="input input-bordered input-sm w-full"
|
||||
value={searchExternalEthereumAccount}
|
||||
onChange={(e) => handleSearchExternalEthereum(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-control w-full max-w-xs">
|
||||
<label className="label">
|
||||
<span className="label-text">Search by Twitter Account</span>
|
||||
<div className="form-control w-full max-w-[200px]">
|
||||
<label className="label py-1">
|
||||
<span className="label-text text-xs">Search by Twitter Account</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Twitter handle..."
|
||||
className="input input-bordered w-full"
|
||||
className="input input-bordered input-sm w-full"
|
||||
value={searchTwitterAccount}
|
||||
onChange={(e) => handleSearchTwitter(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -3,8 +3,7 @@ import BundleRequestsTable from './bundleRequestsTable';
|
||||
|
||||
const BacktestBundleForm: React.FC = () => {
|
||||
return (
|
||||
<div className="p-10 max-w-7xl mx-auto">
|
||||
<h2 className="text-2xl font-bold mb-6">Bundle Backtest</h2>
|
||||
<div className="p-4 md:p-6 lg:p-8 w-full">
|
||||
<BundleRequestsTable />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ function PlatformSummary({index}: { index: number }) {
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{/* Total Volume Traded */}
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<div className="card-body p-3 md:p-6">
|
||||
<h3 className="card-title text-base-content/70">Total Volume Traded</h3>
|
||||
<div className="text-3xl font-bold text-base-content mb-1">
|
||||
{formatCurrency(platformData?.totalPlatformVolume || 0)}
|
||||
@@ -189,36 +189,36 @@ function PlatformSummary({index}: { index: number }) {
|
||||
|
||||
{/* Top 3 Most Profitable */}
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<span className="text-2xl">🤑</span>
|
||||
<h3 className="card-title text-base-content/70">Top 3 Most Profitable</h3>
|
||||
<div className="card-body p-3 md:p-6">
|
||||
<div className="flex items-center gap-2 mb-3 md:mb-4">
|
||||
<span className="text-xl md:text-2xl">🤑</span>
|
||||
<h3 className="card-title text-base-content/70 text-sm md:text-base">Top 3 Most Profitable</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2 md:space-y-3">
|
||||
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-primary text-primary-content rounded-full w-8">
|
||||
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
|
||||
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
|
||||
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
|
||||
<div className="bg-primary text-primary-content rounded-full w-8 h-8">
|
||||
<span className="text-xs font-bold">
|
||||
{strategy.strategyName?.charAt(0) || 'S'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-base-content font-medium">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-xs md:text-sm text-base-content font-medium truncate">
|
||||
{strategy.strategyName || '[Strategy Name]'}
|
||||
</div>
|
||||
<div className="text-xs text-base-content/60">📧</div>
|
||||
<div className="text-[10px] md:text-xs text-base-content/60">📧</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`text-sm font-bold ${strategy.netPnL && strategy.netPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
className={`text-xs md:text-sm font-bold flex-shrink-0 ${strategy.netPnL && strategy.netPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
{strategy.netPnL && strategy.netPnL >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
||||
</div>
|
||||
</div>
|
||||
)) || (
|
||||
<div className="text-base-content/60 text-sm">No profitable strategies found</div>
|
||||
<div className="text-base-content/60 text-xs md:text-sm">No profitable strategies found</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -226,44 +226,44 @@ function PlatformSummary({index}: { index: number }) {
|
||||
|
||||
{/* Top 3 Rising (by ROI) */}
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<span className="text-2xl">📈</span>
|
||||
<h3 className="card-title text-base-content/70">Top 3 by ROI</h3>
|
||||
<div className="card-body p-3 md:p-6">
|
||||
<div className="flex items-center gap-2 mb-3 md:mb-4">
|
||||
<span className="text-xl md:text-2xl">📈</span>
|
||||
<h3 className="card-title text-base-content/70 text-sm md:text-base">Top 3 by ROI</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2 md:space-y-3">
|
||||
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-success text-success-content rounded-full w-8">
|
||||
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
|
||||
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
|
||||
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
|
||||
<div className="bg-success text-success-content rounded-full w-8 h-8">
|
||||
<span className="text-xs font-bold">
|
||||
{strategy.strategyName?.charAt(0) || 'S'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-base-content font-medium">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-xs md:text-sm text-base-content font-medium truncate">
|
||||
{strategy.strategyName || '[Strategy Name]'}
|
||||
</div>
|
||||
<div className="text-xs text-base-content/60">
|
||||
<div className="text-[10px] md:text-xs text-base-content/60 truncate">
|
||||
Vol: {formatCurrency(strategy.volume || 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-right flex-shrink-0">
|
||||
<div
|
||||
className={`text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
className={`text-xs md:text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
{(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}%
|
||||
</div>
|
||||
<div
|
||||
className={`text-xs ${(strategy.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
className={`text-[10px] md:text-xs ${(strategy.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
{(strategy.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)) || (
|
||||
<div className="text-base-content/60 text-sm">No ROI data available</div>
|
||||
<div className="text-base-content/60 text-xs md:text-sm">No ROI data available</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,43 +271,43 @@ function PlatformSummary({index}: { index: number }) {
|
||||
|
||||
{/* Top 3 Agents by PnL */}
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<span className="text-2xl">👥</span>
|
||||
<h3 className="card-title text-base-content/70">Top 3 Agents by PnL</h3>
|
||||
<div className="card-body p-3 md:p-6">
|
||||
<div className="flex items-center gap-2 mb-3 md:mb-4">
|
||||
<span className="text-xl md:text-2xl">👥</span>
|
||||
<h3 className="card-title text-base-content/70 text-sm md:text-base">Top 3 Agents by PnL</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2 md:space-y-3">
|
||||
{topAgentsByPnL?.slice(0, 3).map((agent, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-secondary text-secondary-content rounded-full w-8">
|
||||
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
|
||||
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
|
||||
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
|
||||
<div className="bg-secondary text-secondary-content rounded-full w-8 h-8">
|
||||
<span className="text-xs font-bold">
|
||||
{agent.agentName?.charAt(0) || 'A'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-base-content font-medium">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-xs md:text-sm text-base-content font-medium truncate">
|
||||
{agent.agentName || '[Agent Name]'}
|
||||
</div>
|
||||
<div className="text-xs text-base-content/60">
|
||||
<div className="text-[10px] md:text-xs text-base-content/60 truncate">
|
||||
{agent.activeStrategiesCount || 0} strategies
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-right flex-shrink-0">
|
||||
<div
|
||||
className={`text-sm font-bold ${(agent.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
className={`text-xs md:text-sm font-bold ${(agent.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||
{(agent.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(agent.netPnL || 0)}
|
||||
</div>
|
||||
<div className="text-xs text-base-content/60">
|
||||
<div className="text-[10px] md:text-xs text-base-content/60">
|
||||
{(agent.totalROI || 0).toFixed(2)}% ROI
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)) || (
|
||||
<div className="text-base-content/60 text-sm">No agent data available</div>
|
||||
<div className="text-base-content/60 text-xs md:text-sm">No agent data available</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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 {
|
||||
@@ -48,6 +47,16 @@ const HealthChecks: React.FC = () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Use TanStack Query for API candles health check
|
||||
const {data: candlesHealth, isLoading: isLoadingCandles} = useQuery({
|
||||
queryKey: ['health', 'api-candles'],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${apiUrl}/health-candles`)
|
||||
if (!response.ok) throw new Error('Failed to fetch API candles health')
|
||||
return response.json() as Promise<HealthCheckResponse>
|
||||
}
|
||||
})
|
||||
|
||||
// Use TanStack Query for Worker health check
|
||||
const {data: workerHealth, isLoading: isLoadingWorker} = useQuery({
|
||||
queryKey: ['health', 'worker'],
|
||||
@@ -59,9 +68,6 @@ const HealthChecks: React.FC = () => {
|
||||
})
|
||||
|
||||
|
||||
// Determine overall loading state
|
||||
const isLoading = isLoadingApi || isLoadingWorker
|
||||
|
||||
// Helper function to prepare table data from health response
|
||||
const prepareHealthData = (
|
||||
service: string,
|
||||
@@ -85,34 +91,22 @@ const HealthChecks: React.FC = () => {
|
||||
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
|
||||
// Extract timeframe checks - show all ticker-timeframe combinations
|
||||
Object.entries(entry.data)
|
||||
.filter(([dataKey]) => dataKey.startsWith('TimeframeCheck_'))
|
||||
.forEach(([dataKey, timeframeData]) => {
|
||||
const tfData = timeframeData as CandleTimeframeCheck;
|
||||
// Create a more descriptive component name with ticker and timeframe
|
||||
const componentName = `${tfData.CheckedTicker || 'N/A'} - ${tfData.CheckedTimeframe || 'N/A'}`
|
||||
results.push({
|
||||
service,
|
||||
component: `${key} - ${tfData.CheckedTimeframe}`,
|
||||
component: componentName,
|
||||
status: tfData.Status,
|
||||
duration: '',
|
||||
tags: 'candles',
|
||||
description: tfData.Message,
|
||||
description: tfData.Message || '',
|
||||
details: {
|
||||
Ticker: tfData.CheckedTicker,
|
||||
LatestCandle: tfData.LatestCandleDate,
|
||||
@@ -120,6 +114,47 @@ const HealthChecks: React.FC = () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Also check for ticker results if they exist (from detailed health check)
|
||||
Object.entries(entry.data)
|
||||
.filter(([dataKey]) => dataKey.startsWith('TickerResults_') || dataKey === 'TickerResults')
|
||||
.forEach(([dataKey, tickerData]) => {
|
||||
if (tickerData && typeof tickerData === 'object') {
|
||||
Object.entries(tickerData as Record<string, any>).forEach(([ticker, tickerInfo]) => {
|
||||
if (tickerInfo && typeof tickerInfo === 'object' && Array.isArray((tickerInfo as any).timeframeChecks)) {
|
||||
const timeframeChecks = (tickerInfo as any).timeframeChecks as CandleTimeframeCheck[];
|
||||
timeframeChecks.forEach((tfData: CandleTimeframeCheck) => {
|
||||
const componentName = `${ticker} - ${tfData.CheckedTimeframe || 'N/A'}`
|
||||
results.push({
|
||||
service,
|
||||
component: componentName,
|
||||
status: tfData.Status,
|
||||
duration: '',
|
||||
tags: 'candles',
|
||||
description: tfData.Message || '',
|
||||
details: {
|
||||
Ticker: tfData.CheckedTicker || ticker,
|
||||
LatestCandle: tfData.LatestCandleDate,
|
||||
TimeDifference: tfData.TimeDifference,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Basic health check entry (skip candle-data as it's expanded above)
|
||||
const baseEntry = {
|
||||
service,
|
||||
component: key,
|
||||
status: entry.status,
|
||||
duration: entry.duration,
|
||||
tags: entry.tags.join(', '),
|
||||
description: entry.description || '',
|
||||
details: null,
|
||||
};
|
||||
results.push(baseEntry);
|
||||
}
|
||||
|
||||
// Special handling for Web3Proxy components
|
||||
@@ -206,96 +241,112 @@ const HealthChecks: React.FC = () => {
|
||||
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,
|
||||
},
|
||||
],
|
||||
[]
|
||||
// Prepare health data for each service independently
|
||||
const apiHealthData = React.useMemo(() =>
|
||||
prepareHealthData('Managing API', apiHealth || null),
|
||||
[apiHealth]
|
||||
)
|
||||
|
||||
const candlesHealthData = React.useMemo(() =>
|
||||
prepareHealthData('Managing API - Candles', candlesHealth || null),
|
||||
[candlesHealth]
|
||||
)
|
||||
|
||||
const workerHealthData = React.useMemo(() =>
|
||||
prepareHealthData('Managing Worker', workerHealth || null),
|
||||
[workerHealth]
|
||||
)
|
||||
|
||||
// Create service entries with loading states
|
||||
const services = [
|
||||
{
|
||||
name: 'Managing API',
|
||||
data: apiHealthData,
|
||||
isLoading: isLoadingApi,
|
||||
},
|
||||
{
|
||||
name: 'Managing API - Candles',
|
||||
data: candlesHealthData,
|
||||
isLoading: isLoadingCandles,
|
||||
},
|
||||
{
|
||||
name: 'Managing Worker',
|
||||
data: workerHealthData,
|
||||
isLoading: isLoadingWorker,
|
||||
},
|
||||
]
|
||||
|
||||
// Get overall status for a service
|
||||
const getServiceStatus = (items: Array<{status: string}>) => {
|
||||
if (items.some(item => item.status.toLowerCase() === 'unhealthy' || item.status.toLowerCase() === 'unreachable')) {
|
||||
return { status: 'Unhealthy', color: 'badge-error' }
|
||||
}
|
||||
if (items.some(item => item.status.toLowerCase() === 'degraded')) {
|
||||
return { status: 'Degraded', color: 'badge-warning' }
|
||||
}
|
||||
return { status: 'Healthy', color: 'badge-success' }
|
||||
}
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusLower = status.toLowerCase()
|
||||
if (statusLower === 'healthy') return 'badge-success badge-xs'
|
||||
if (statusLower === 'unhealthy' || statusLower === 'unreachable') return 'badge-error badge-xs'
|
||||
return 'badge-warning badge-xs'
|
||||
}
|
||||
|
||||
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 className="container mx-auto p-1 md:p-2">
|
||||
<div className="space-y-1.5">
|
||||
{services.map((service) => {
|
||||
if (service.isLoading && service.data.length === 0) {
|
||||
return (
|
||||
<div key={service.name} className="card bg-base-200">
|
||||
<div className="card-body p-1.5 md:p-2">
|
||||
<div className="flex items-center justify-between mb-0.5">
|
||||
<h3 className="text-[10px] md:text-xs font-semibold">{service.name}</h3>
|
||||
<progress className="progress progress-primary w-16 h-0.5"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (service.data.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const serviceStatus = getServiceStatus(service.data)
|
||||
return (
|
||||
<div key={service.name} className="card bg-base-200">
|
||||
<div className="card-body p-1.5 md:p-2">
|
||||
<div className="flex items-center justify-between mb-0.5">
|
||||
<h3 className="text-[10px] md:text-xs font-semibold">{service.name}</h3>
|
||||
<div className="flex items-center gap-1">
|
||||
{service.isLoading && (
|
||||
<progress className="progress progress-primary w-8 h-0.5"></progress>
|
||||
)}
|
||||
<div className={`badge ${serviceStatus.color} badge-xs px-1 py-0 text-[8px]`}>
|
||||
{serviceStatus.status}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-0.5 md:gap-1">
|
||||
{service.data.map((item, idx) => (
|
||||
<div key={idx} className="flex items-center justify-between bg-base-100 rounded px-1 py-0.5 text-[10px]">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="truncate font-medium leading-tight">{item.component}</div>
|
||||
</div>
|
||||
<div className={`badge ${getStatusBadge(item.status)} ml-0.5 flex-shrink-0 px-0.5 py-0 text-[8px]`}>
|
||||
{item.status.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -265,7 +265,6 @@ const SqlMonitoring: React.FC = () => {
|
||||
<div className="container mx-auto space-y-3 sm:space-y-6 px-2 sm:px-4">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2 sm:gap-4">
|
||||
<h2 className="text-xl sm:text-2xl font-bold">SQL Monitoring</h2>
|
||||
<button
|
||||
className="btn btn-outline btn-xs sm:btn-sm"
|
||||
onClick={() => clearTrackingMutation.mutate()}
|
||||
|
||||
@@ -72,27 +72,27 @@ const WhitelistSettings: React.FC = () => {
|
||||
Search Filters
|
||||
</div>
|
||||
<div className="collapse-content">
|
||||
<div className="flex gap-4 pt-4">
|
||||
<div className="form-control w-full max-w-xs">
|
||||
<label className="label">
|
||||
<span className="label-text">Search by Ethereum Account</span>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<div className="form-control w-full max-w-[200px]">
|
||||
<label className="label py-1">
|
||||
<span className="label-text text-xs">Search by Ethereum Account</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Ethereum address..."
|
||||
className="input input-bordered w-full"
|
||||
className="input input-bordered input-sm w-full"
|
||||
value={searchExternalEthereumAccount}
|
||||
onChange={(e) => handleSearchExternalEthereum(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-control w-full max-w-xs">
|
||||
<label className="label">
|
||||
<span className="label-text">Search by Twitter Account</span>
|
||||
<div className="form-control w-full max-w-[200px]">
|
||||
<label className="label py-1">
|
||||
<span className="label-text text-xs">Search by Twitter Account</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter Twitter handle..."
|
||||
className="input input-bordered w-full"
|
||||
className="input input-bordered input-sm w-full"
|
||||
value={searchTwitterAccount}
|
||||
onChange={(e) => handleSearchTwitter(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -9,19 +9,19 @@ import FeeCalculator from './feeCalculator'
|
||||
|
||||
const tabs: ITabsType = [
|
||||
{
|
||||
Component: SpotlightView,
|
||||
Component: FeeCalculator,
|
||||
index: 1,
|
||||
label: 'Fee Calculator',
|
||||
},
|
||||
{
|
||||
Component: SpotlightView,
|
||||
index: 2,
|
||||
label: 'Spotlight',
|
||||
},
|
||||
{
|
||||
Component: RektFees,
|
||||
index: 2,
|
||||
label: 'RektFees',
|
||||
},
|
||||
{
|
||||
Component: FeeCalculator,
|
||||
index: 3,
|
||||
label: 'Fee Calculator',
|
||||
label: 'RektFees',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
.layout {
|
||||
@apply sm:w-11/12 w-10/12 mx-auto;
|
||||
}
|
||||
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
||||
Reference in New Issue
Block a user