diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index b5fe038..27fb440 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -411,6 +411,8 @@ public class TradingBot : Bot, ITradingBot { Logger.LogInformation( $"Position {signal.Identifier} don't need to be update. Position still opened"); + + await SetPositionStatus(signal.Identifier, PositionStatus.Filled); } } @@ -445,6 +447,10 @@ public class TradingBot : Bot, ITradingBot { Logger.LogInformation( $"Position {signal.Identifier} don't need to be update. Position still opened"); + + position.Status = PositionStatus.Filled; + + await SetPositionStatus(signal.Identifier, PositionStatus.Filled); } } } diff --git a/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx b/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx index c562ad9..c9cf88a 100644 --- a/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx +++ b/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx @@ -1,14 +1,8 @@ -import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid' +import {ArrowDownIcon, ArrowUpIcon} from '@heroicons/react/solid' import React from 'react' -import { - useTable, - usePagination, - useSortBy, - useFilters, - useExpanded, -} from 'react-table' +import {useExpanded, useFilters, usePagination, useSortBy, useTable,} from 'react-table' -import type { TableInstanceWithHooks } from '../../../global/type' +import type {TableInstanceWithHooks} from '../../../global/type' // Define a default UI for filtering function DefaultColumnFilter({ @@ -31,7 +25,7 @@ export default function Table({ columns, data, renderRowSubCompontent, - showPagination, + showPagination = true, hiddenColumns, showTotal = false, }: any) { @@ -49,10 +43,7 @@ export default function Table({ headerGroups, prepareRow, visibleColumns, - page, // Instead of using 'rows', we'll use page, - // which has only the rows for the active page - - // The rest of these things are super handy, too ;) + page, canPreviousPage, canNextPage, pageOptions, diff --git a/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx b/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx index 4814f8a..3046d99 100644 --- a/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx +++ b/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx @@ -1,25 +1,14 @@ -import { - ArrowDownIcon, - ArrowUpIcon, - ChevronDownIcon, - ChevronRightIcon, - PlayIcon, -} from '@heroicons/react/solid' -import React, { useEffect, useState } from 'react' +import {ArrowDownIcon, ArrowUpIcon, ChevronDownIcon, ChevronRightIcon, PlayIcon,} from '@heroicons/react/solid' +import React, {useEffect, useState} from 'react' -import { Hub } from '../../../app/providers/Hubs' +import {Hub} from '../../../app/providers/Hubs' import useApiUrlStore from '../../../app/store/apiStore' -import type { Account, TradingBot } from '../../../generated/ManagingApi' -import { - AccountClient, - BotClient, - TradeDirection, - TradeStatus, -} from '../../../generated/ManagingApi' -import { SelectColumnFilter, Table } from '../../mollecules' -import BacktestRowDetails from '../Backtest/backtestRowDetails' +import type {Account, TradingBot} from '../../../generated/ManagingApi' +import {AccountClient, BotClient, TradeDirection, TradeStatus,} from '../../../generated/ManagingApi' +import {SelectColumnFilter, Table} from '../../mollecules' import StatusBadge from '../StatusBadge/StatusBadge' import Summary from '../Trading/Summary' +import BotRowDetails from './botRowDetails' export default function ActiveBots() { const [bots, setBots] = useState([]) @@ -63,10 +52,14 @@ export default function ActiveBots() { Header: 'Status', accessor: 'status', disableFilters: true, - sortType: 'basic', + disableSortBy: true, + search: false, }, { accessor: 'isForWatchingOnly', + disableFilters: true, + disableSortBy: true, + search: false, }, { Filter: SelectColumnFilter, @@ -185,10 +178,9 @@ export default function ActiveBots() { const renderRowSubComponent = React.useCallback( ({ row }: any) => ( <> - + ), [] @@ -204,6 +196,7 @@ export default function ActiveBots() { columns={columns} data={bots} renderRowSubCompontent={renderRowSubComponent} + hiddenColumns={['isForWatchingOnly']} /> diff --git a/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx b/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx index 27743f1..ca937e0 100644 --- a/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx +++ b/src/Managing.WebApp/src/pages/settingsPage/healthchecks/healthChecks.tsx @@ -1,315 +1,300 @@ -import React, {useEffect, useState} from 'react' - +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 - duration: string - status: string - tags: string[] - description?: string + data: Record + duration: string + status: string + tags: string[] + description?: string } interface HealthCheckResponse { - status: string - totalDuration: string - entries: Record + status: string + totalDuration: string + entries: Record } // Interface for candle timeframe check data interface CandleTimeframeCheck { - CheckedTicker: string - CheckedTimeframe: string - StartDate: string - LatestCandleDate?: string - TimeDifference?: string - Status: string - Message: string + CheckedTicker: string + CheckedTimeframe: string + StartDate: string + LatestCandleDate?: string + TimeDifference?: string + Status: string + Message: string } interface Web3ProxyHealthDetail { - status: string - message: string - data?: Record + status: string + message: string + data?: 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) + const {apiUrl, workerUrl} = useApiUrlStore() - 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) + // 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 + } + }) + + // 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 + } + }) + + + // 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, + }, + ] } - // Fetch Worker health check - const workerResponse = await fetch(`${workerUrl}/health`) - if (workerResponse.ok) { - const data = await workerResponse.json() - setWorkerHealth(data) - } + // Convert entries to rows for the table + const results: any[] = []; - // Fetch Web3Proxy health check - use the dedicated endpoint we created - const web3Response = await fetch(`${apiUrl}/health/web3proxy`) - if (web3Response.ok) { - const data = await web3Response.json() - setWeb3ProxyHealth(data) - } - } catch (error) { - console.error('Error fetching health checks:', error) - } finally { - setIsLoading(false) - } - } + 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, + }; - fetchHealthChecks() - }, [apiUrl, workerUrl]) + // Add the base entry + results.push(baseEntry); - // 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 = {}; - - // Add market count and response time if available - if (gmxData.data) { - if (gmxData.data.marketCount) { - marketDetails['Market Count'] = gmxData.data.marketCount; + // 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, + }, + }); + }); } - if (gmxData.data.responseTimeMs) { - marketDetails['Response Time'] = `${gmxData.data.responseTimeMs}ms`; - } - - // 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; + + // 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 = {}; + + // 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`; + } + + // 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 = { + 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, + }); } - } } - } - - 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 = { - 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), - ...prepareHealthData('Managing Worker', workerHealth), - ...prepareHealthData('Web3 Proxy', web3ProxyHealth), - ] + return results; + } - // 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 }) => ( - + // 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 }) => ( + {value.charAt(0).toUpperCase() + value.slice(1)} - ), - disableSortBy: true, - disableFilters: true, - }, - { - Header: 'Description', - accessor: 'description', - disableSortBy: true, - disableFilters: true, - Cell: ({ value, row }: any) => ( -
-
{value}
- {row.original.details && ( -
- {Object.entries(row.original.details).filter(([_, val]) => val !== undefined).map( - ([key, val]) => ( -
- {key}: {String(val)} + ), + disableSortBy: true, + disableFilters: true, + }, + { + Header: 'Description', + accessor: 'description', + disableSortBy: true, + disableFilters: true, + Cell: ({value, row}: any) => ( +
+
{value}
+ {row.original.details && ( +
+ {Object.entries(row.original.details).filter(([_, val]) => val !== undefined).map( + ([key, val]) => ( +
+ {key}: {String(val)} +
+ ) + )} +
+ )}
- ) - )} -
- )} -
- ), - }, - { - Header: 'Duration', - accessor: 'duration', - disableSortBy: true, - disableFilters: true, - }, - { - Header: 'Tags', - accessor: 'tags', - 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 ? ( - - ) : ( - - )} - - ) + return ( +
+

System Health Status

+ {isLoading ? ( + + ) : ( +
+ )} + + ) } export default HealthChecks