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'
|
color: theme['base-content'] || '#ffffff'
|
||||||
},
|
},
|
||||||
bgcolor: 'rgba(0,0,0,0)',
|
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: {
|
margin: {
|
||||||
l: 60,
|
l: 60,
|
||||||
r: 60,
|
r: 60,
|
||||||
t: 60,
|
t: 60,
|
||||||
b: 60,
|
b: 80,
|
||||||
pad: 4
|
pad: 4
|
||||||
},
|
},
|
||||||
paper_bgcolor: 'rgba(0,0,0,0)',
|
paper_bgcolor: 'rgba(0,0,0,0)',
|
||||||
@@ -290,9 +295,8 @@ function PlatformLineChart({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config: Partial<Config> = {
|
const config: Partial<Config> = {
|
||||||
displayModeBar: true,
|
displayModeBar: false,
|
||||||
displaylogo: false,
|
displaylogo: false,
|
||||||
modeBarButtonsToRemove: ['pan2d', 'lasso2d', 'select2d', 'autoScale2d', 'resetScale2d'] as any,
|
|
||||||
responsive: true
|
responsive: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,40 +27,42 @@ const Tabs: FC<ITabsProps> = ({
|
|||||||
orientation === 'vertical' ? className + ' vertical' : className
|
orientation === 'vertical' ? className + ' vertical' : className
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="tabs" role="tablist" aria-orientation={orientation}>
|
<div className="overflow-x-auto scrollbar-hide -mx-4 px-4">
|
||||||
{tabs.map((tab: any) => (
|
<div className="tabs flex-nowrap min-w-max" role="tablist" aria-orientation={orientation}>
|
||||||
<button
|
{tabs.map((tab: any) => (
|
||||||
className={
|
<button
|
||||||
'mb-5 tab tab-bordered ' +
|
className={
|
||||||
(selectedTab === tab.index ? 'tab-active' : '')
|
'mb-5 tab tab-bordered whitespace-nowrap flex-shrink-0 ' +
|
||||||
}
|
(selectedTab === tab.index ? 'tab-active' : '')
|
||||||
onClick={() => onClick(tab.index)}
|
}
|
||||||
key={tab.index}
|
onClick={() => onClick(tab.index)}
|
||||||
type="button"
|
key={tab.index}
|
||||||
role="tab"
|
type="button"
|
||||||
aria-selected={selectedTab === tab.index}
|
role="tab"
|
||||||
aria-controls={`tabpanel-${tab.index}`}
|
aria-selected={selectedTab === tab.index}
|
||||||
tabIndex={selectedTab === tab.index ? 0 : -1}
|
aria-controls={`tabpanel-${tab.index}`}
|
||||||
id={`btn-${tab.index}`}
|
tabIndex={selectedTab === tab.index ? 0 : -1}
|
||||||
>
|
id={`btn-${tab.index}`}
|
||||||
{tab.label}
|
>
|
||||||
</button>
|
{tab.label}
|
||||||
))}
|
</button>
|
||||||
{addButton && (
|
))}
|
||||||
<button
|
{addButton && (
|
||||||
className="tab tab-bordered mb-5"
|
<button
|
||||||
onClick={onAddButton}
|
className="tab tab-bordered mb-5 whitespace-nowrap flex-shrink-0"
|
||||||
key={'add'}
|
onClick={onAddButton}
|
||||||
type="button"
|
key={'add'}
|
||||||
role="tab"
|
type="button"
|
||||||
aria-selected={false}
|
role="tab"
|
||||||
aria-controls={`tabpanel-${'add'}`}
|
aria-selected={false}
|
||||||
tabIndex={-1}
|
aria-controls={`tabpanel-${'add'}`}
|
||||||
id={`btn-${'add'}`}
|
tabIndex={-1}
|
||||||
>
|
id={`btn-${'add'}`}
|
||||||
+
|
>
|
||||||
</button>
|
+
|
||||||
)}
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
|
|||||||
@@ -72,27 +72,27 @@ const WhitelistSettings: React.FC = () => {
|
|||||||
Search Filters
|
Search Filters
|
||||||
</div>
|
</div>
|
||||||
<div className="collapse-content">
|
<div className="collapse-content">
|
||||||
<div className="flex gap-4 pt-4">
|
<div className="flex gap-2 pt-2">
|
||||||
<div className="form-control w-full max-w-xs">
|
<div className="form-control w-full max-w-[200px]">
|
||||||
<label className="label">
|
<label className="label py-1">
|
||||||
<span className="label-text">Search by Ethereum Account</span>
|
<span className="label-text text-xs">Search by Ethereum Account</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter Ethereum address..."
|
placeholder="Enter Ethereum address..."
|
||||||
className="input input-bordered w-full"
|
className="input input-bordered input-sm w-full"
|
||||||
value={searchExternalEthereumAccount}
|
value={searchExternalEthereumAccount}
|
||||||
onChange={(e) => handleSearchExternalEthereum(e.target.value)}
|
onChange={(e) => handleSearchExternalEthereum(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control w-full max-w-xs">
|
<div className="form-control w-full max-w-[200px]">
|
||||||
<label className="label">
|
<label className="label py-1">
|
||||||
<span className="label-text">Search by Twitter Account</span>
|
<span className="label-text text-xs">Search by Twitter Account</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter Twitter handle..."
|
placeholder="Enter Twitter handle..."
|
||||||
className="input input-bordered w-full"
|
className="input input-bordered input-sm w-full"
|
||||||
value={searchTwitterAccount}
|
value={searchTwitterAccount}
|
||||||
onChange={(e) => handleSearchTwitter(e.target.value)}
|
onChange={(e) => handleSearchTwitter(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import BundleRequestsTable from './bundleRequestsTable';
|
|||||||
|
|
||||||
const BacktestBundleForm: React.FC = () => {
|
const BacktestBundleForm: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="p-10 max-w-7xl mx-auto">
|
<div className="p-4 md:p-6 lg:p-8 w-full">
|
||||||
<h2 className="text-2xl font-bold mb-6">Bundle Backtest</h2>
|
|
||||||
<BundleRequestsTable />
|
<BundleRequestsTable />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||||
{/* Total Volume Traded */}
|
{/* Total Volume Traded */}
|
||||||
<div className="card bg-base-200">
|
<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>
|
<h3 className="card-title text-base-content/70">Total Volume Traded</h3>
|
||||||
<div className="text-3xl font-bold text-base-content mb-1">
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
{formatCurrency(platformData?.totalPlatformVolume || 0)}
|
{formatCurrency(platformData?.totalPlatformVolume || 0)}
|
||||||
@@ -189,36 +189,36 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
|
|
||||||
{/* Top 3 Most Profitable */}
|
{/* Top 3 Most Profitable */}
|
||||||
<div className="card bg-base-200">
|
<div className="card bg-base-200">
|
||||||
<div className="card-body">
|
<div className="card-body p-3 md:p-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-3 md:mb-4">
|
||||||
<span className="text-2xl">🤑</span>
|
<span className="text-xl md:text-2xl">🤑</span>
|
||||||
<h3 className="card-title text-base-content/70">Top 3 Most Profitable</h3>
|
<h3 className="card-title text-base-content/70 text-sm md:text-base">Top 3 Most Profitable</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-2 md:space-y-3">
|
||||||
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
|
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
|
||||||
<div key={index} className="flex items-center justify-between">
|
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
|
||||||
<div className="avatar placeholder">
|
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
|
||||||
<div className="bg-primary text-primary-content rounded-full w-8">
|
<div className="bg-primary text-primary-content rounded-full w-8 h-8">
|
||||||
<span className="text-xs font-bold">
|
<span className="text-xs font-bold">
|
||||||
{strategy.strategyName?.charAt(0) || 'S'}
|
{strategy.strategyName?.charAt(0) || 'S'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<div className="text-sm text-base-content font-medium">
|
<div className="text-xs md:text-sm text-base-content font-medium truncate">
|
||||||
{strategy.strategyName || '[Strategy Name]'}
|
{strategy.strategyName || '[Strategy Name]'}
|
||||||
</div>
|
</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>
|
</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)}
|
{strategy.netPnL && strategy.netPnL >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,44 +226,44 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
|
|
||||||
{/* Top 3 Rising (by ROI) */}
|
{/* Top 3 Rising (by ROI) */}
|
||||||
<div className="card bg-base-200">
|
<div className="card bg-base-200">
|
||||||
<div className="card-body">
|
<div className="card-body p-3 md:p-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-3 md:mb-4">
|
||||||
<span className="text-2xl">📈</span>
|
<span className="text-xl md:text-2xl">📈</span>
|
||||||
<h3 className="card-title text-base-content/70">Top 3 by ROI</h3>
|
<h3 className="card-title text-base-content/70 text-sm md:text-base">Top 3 by ROI</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-2 md:space-y-3">
|
||||||
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
|
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
|
||||||
<div key={index} className="flex items-center justify-between">
|
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
|
||||||
<div className="avatar placeholder">
|
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
|
||||||
<div className="bg-success text-success-content rounded-full w-8">
|
<div className="bg-success text-success-content rounded-full w-8 h-8">
|
||||||
<span className="text-xs font-bold">
|
<span className="text-xs font-bold">
|
||||||
{strategy.strategyName?.charAt(0) || 'S'}
|
{strategy.strategyName?.charAt(0) || 'S'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<div className="text-sm text-base-content font-medium">
|
<div className="text-xs md:text-sm text-base-content font-medium truncate">
|
||||||
{strategy.strategyName || '[Strategy Name]'}
|
{strategy.strategyName || '[Strategy Name]'}
|
||||||
</div>
|
</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)}
|
Vol: {formatCurrency(strategy.volume || 0)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right flex-shrink-0">
|
||||||
<div
|
<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}%
|
{(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}%
|
||||||
</div>
|
</div>
|
||||||
<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)}
|
{(strategy.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
@@ -271,43 +271,43 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
|
|
||||||
{/* Top 3 Agents by PnL */}
|
{/* Top 3 Agents by PnL */}
|
||||||
<div className="card bg-base-200">
|
<div className="card bg-base-200">
|
||||||
<div className="card-body">
|
<div className="card-body p-3 md:p-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-3 md:mb-4">
|
||||||
<span className="text-2xl">👥</span>
|
<span className="text-xl md:text-2xl">👥</span>
|
||||||
<h3 className="card-title text-base-content/70">Top 3 Agents by PnL</h3>
|
<h3 className="card-title text-base-content/70 text-sm md:text-base">Top 3 Agents by PnL</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-2 md:space-y-3">
|
||||||
{topAgentsByPnL?.slice(0, 3).map((agent, index) => (
|
{topAgentsByPnL?.slice(0, 3).map((agent, index) => (
|
||||||
<div key={index} className="flex items-center justify-between">
|
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
|
||||||
<div className="avatar placeholder">
|
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
|
||||||
<div className="bg-secondary text-secondary-content rounded-full w-8">
|
<div className="bg-secondary text-secondary-content rounded-full w-8 h-8">
|
||||||
<span className="text-xs font-bold">
|
<span className="text-xs font-bold">
|
||||||
{agent.agentName?.charAt(0) || 'A'}
|
{agent.agentName?.charAt(0) || 'A'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<div className="text-sm text-base-content font-medium">
|
<div className="text-xs md:text-sm text-base-content font-medium truncate">
|
||||||
{agent.agentName || '[Agent Name]'}
|
{agent.agentName || '[Agent Name]'}
|
||||||
</div>
|
</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
|
{agent.activeStrategiesCount || 0} strategies
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right flex-shrink-0">
|
||||||
<div
|
<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)}
|
{(agent.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(agent.netPnL || 0)}
|
||||||
</div>
|
</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
|
{(agent.totalROI || 0).toFixed(2)}% ROI
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useQuery} from '@tanstack/react-query'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import {Table} from '../../../components/mollecules'
|
|
||||||
|
|
||||||
// Define health check response interface based on the provided example
|
// Define health check response interface based on the provided example
|
||||||
interface HealthCheckEntry {
|
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
|
// Use TanStack Query for Worker health check
|
||||||
const {data: workerHealth, isLoading: isLoadingWorker} = useQuery({
|
const {data: workerHealth, isLoading: isLoadingWorker} = useQuery({
|
||||||
queryKey: ['health', 'worker'],
|
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
|
// Helper function to prepare table data from health response
|
||||||
const prepareHealthData = (
|
const prepareHealthData = (
|
||||||
service: string,
|
service: string,
|
||||||
@@ -85,34 +91,22 @@ const HealthChecks: React.FC = () => {
|
|||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
|
|
||||||
Object.entries(health.entries).forEach(([key, entry]) => {
|
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
|
// Special handling for candle-data to expand timeframe checks
|
||||||
if (key === 'candle-data' && entry.data) {
|
if (key === 'candle-data' && entry.data) {
|
||||||
// Extract timeframe checks
|
// Extract timeframe checks - show all ticker-timeframe combinations
|
||||||
Object.entries(entry.data)
|
Object.entries(entry.data)
|
||||||
.filter(([dataKey]) => dataKey.startsWith('TimeframeCheck_'))
|
.filter(([dataKey]) => dataKey.startsWith('TimeframeCheck_'))
|
||||||
.forEach(([dataKey, timeframeData]) => {
|
.forEach(([dataKey, timeframeData]) => {
|
||||||
const tfData = timeframeData as CandleTimeframeCheck;
|
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({
|
results.push({
|
||||||
service,
|
service,
|
||||||
component: `${key} - ${tfData.CheckedTimeframe}`,
|
component: componentName,
|
||||||
status: tfData.Status,
|
status: tfData.Status,
|
||||||
duration: '',
|
duration: '',
|
||||||
tags: 'candles',
|
tags: 'candles',
|
||||||
description: tfData.Message,
|
description: tfData.Message || '',
|
||||||
details: {
|
details: {
|
||||||
Ticker: tfData.CheckedTicker,
|
Ticker: tfData.CheckedTicker,
|
||||||
LatestCandle: tfData.LatestCandleDate,
|
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
|
// Special handling for Web3Proxy components
|
||||||
@@ -206,96 +241,112 @@ const HealthChecks: React.FC = () => {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine all health check data for display
|
// Prepare health data for each service independently
|
||||||
const healthData = [
|
const apiHealthData = React.useMemo(() =>
|
||||||
...prepareHealthData('Managing API', apiHealth || null),
|
prepareHealthData('Managing API', apiHealth || null),
|
||||||
...prepareHealthData('Managing Worker', workerHealth || null),
|
[apiHealth]
|
||||||
]
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto p-1 md:p-2">
|
||||||
<h2 className="text-xl font-bold mb-4">System Health Status</h2>
|
<div className="space-y-1.5">
|
||||||
{isLoading ? (
|
{services.map((service) => {
|
||||||
<progress className="progress progress-primary w-56"></progress>
|
if (service.isLoading && service.data.length === 0) {
|
||||||
) : (
|
return (
|
||||||
<Table
|
<div key={service.name} className="card bg-base-200">
|
||||||
columns={columns}
|
<div className="card-body p-1.5 md:p-2">
|
||||||
data={healthData}
|
<div className="flex items-center justify-between mb-0.5">
|
||||||
showPagination={true}
|
<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>
|
</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">
|
<div className="container mx-auto space-y-3 sm:space-y-6 px-2 sm:px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2 sm:gap-4">
|
<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
|
<button
|
||||||
className="btn btn-outline btn-xs sm:btn-sm"
|
className="btn btn-outline btn-xs sm:btn-sm"
|
||||||
onClick={() => clearTrackingMutation.mutate()}
|
onClick={() => clearTrackingMutation.mutate()}
|
||||||
|
|||||||
@@ -72,27 +72,27 @@ const WhitelistSettings: React.FC = () => {
|
|||||||
Search Filters
|
Search Filters
|
||||||
</div>
|
</div>
|
||||||
<div className="collapse-content">
|
<div className="collapse-content">
|
||||||
<div className="flex gap-4 pt-4">
|
<div className="flex gap-2 pt-2">
|
||||||
<div className="form-control w-full max-w-xs">
|
<div className="form-control w-full max-w-[200px]">
|
||||||
<label className="label">
|
<label className="label py-1">
|
||||||
<span className="label-text">Search by Ethereum Account</span>
|
<span className="label-text text-xs">Search by Ethereum Account</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter Ethereum address..."
|
placeholder="Enter Ethereum address..."
|
||||||
className="input input-bordered w-full"
|
className="input input-bordered input-sm w-full"
|
||||||
value={searchExternalEthereumAccount}
|
value={searchExternalEthereumAccount}
|
||||||
onChange={(e) => handleSearchExternalEthereum(e.target.value)}
|
onChange={(e) => handleSearchExternalEthereum(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control w-full max-w-xs">
|
<div className="form-control w-full max-w-[200px]">
|
||||||
<label className="label">
|
<label className="label py-1">
|
||||||
<span className="label-text">Search by Twitter Account</span>
|
<span className="label-text text-xs">Search by Twitter Account</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter Twitter handle..."
|
placeholder="Enter Twitter handle..."
|
||||||
className="input input-bordered w-full"
|
className="input input-bordered input-sm w-full"
|
||||||
value={searchTwitterAccount}
|
value={searchTwitterAccount}
|
||||||
onChange={(e) => handleSearchTwitter(e.target.value)}
|
onChange={(e) => handleSearchTwitter(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import FeeCalculator from './feeCalculator'
|
|||||||
|
|
||||||
const tabs: ITabsType = [
|
const tabs: ITabsType = [
|
||||||
{
|
{
|
||||||
Component: SpotlightView,
|
Component: FeeCalculator,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
label: 'Fee Calculator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Component: SpotlightView,
|
||||||
|
index: 2,
|
||||||
label: 'Spotlight',
|
label: 'Spotlight',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: RektFees,
|
Component: RektFees,
|
||||||
index: 2,
|
|
||||||
label: 'RektFees',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Component: FeeCalculator,
|
|
||||||
index: 3,
|
index: 3,
|
||||||
label: 'Fee Calculator',
|
label: 'RektFees',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,15 @@
|
|||||||
.layout {
|
.layout {
|
||||||
@apply sm:w-11/12 w-10/12 mx-auto;
|
@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 {
|
@layer components {
|
||||||
|
|||||||
Reference in New Issue
Block a user