From daf6fbbf68ac656f7bb99e5870b9a8d5acef75e4 Mon Sep 17 00:00:00 2001 From: Sergii Date: Thu, 16 Jan 2025 08:44:19 +0200 Subject: [PATCH] feat(stats): shows ephemeral and persistent storage separately on pie chart refs #645, #665 --- apps/api/src/routes/v1/networkCapacity.ts | 8 +- apps/api/src/routes/v1/providers/byAddress.ts | 14 ++ .../src/services/db/providerStatusService.ts | 83 +++++++---- apps/api/src/types/provider.ts | 18 +++ apps/api/src/utils/map/provider.ts | 67 ++++++--- .../components/providers/NetworkCapacity.tsx | 138 +++++++++++------- .../components/providers/ProviderDetail.tsx | 49 ++++--- .../src/components/providers/ProviderList.tsx | 15 +- apps/deploy-web/src/types/provider.ts | 18 +++ 9 files changed, 274 insertions(+), 136 deletions(-) diff --git a/apps/api/src/routes/v1/networkCapacity.ts b/apps/api/src/routes/v1/networkCapacity.ts index eef693da0..50178c717 100644 --- a/apps/api/src/routes/v1/networkCapacity.ts +++ b/apps/api/src/routes/v1/networkCapacity.ts @@ -28,7 +28,13 @@ const route = createRoute({ totalCPU: z.number(), totalGPU: z.number(), totalMemory: z.number(), - totalStorage: z.number() + totalStorage: z.number(), + activeEphemeralStorage: z.number(), + pendingEphemeralStorage: z.number(), + availableEphemeralStorage: z.number(), + activePersistentStorage: z.number(), + pendingPersistentStorage: z.number(), + availablePersistentStorage: z.number(), }) } } diff --git a/apps/api/src/routes/v1/providers/byAddress.ts b/apps/api/src/routes/v1/providers/byAddress.ts index e87c5f792..736351902 100644 --- a/apps/api/src/routes/v1/providers/byAddress.ts +++ b/apps/api/src/routes/v1/providers/byAddress.ts @@ -3,6 +3,11 @@ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { getProviderDetail } from "@src/services/db/providerStatusService"; import { openApiExampleProviderAddress } from "@src/utils/constants"; +const statsItemSchema = () => z.object({ + active: z.number(), + available: z.number(), + pending: z.number(), +}); const route = createRoute({ method: "get", path: "/providers/{address}", @@ -46,6 +51,15 @@ const route = createRoute({ isOnline: z.boolean(), lastOnlineDate: z.string().nullable(), isAudited: z.boolean(), + stats: z.object({ + cpu: statsItemSchema(), + gpu: statsItemSchema(), + memory: statsItemSchema(), + storage: z.object({ + ephemeral: statsItemSchema(), + persistent: statsItemSchema(), + }) + }), activeStats: z.object({ cpu: z.number(), gpu: z.number(), diff --git a/apps/api/src/services/db/providerStatusService.ts b/apps/api/src/services/db/providerStatusService.ts index ec3d967d4..8ddc9f2a2 100644 --- a/apps/api/src/services/db/providerStatusService.ts +++ b/apps/api/src/services/db/providerStatusService.ts @@ -2,6 +2,7 @@ import { Provider, ProviderAttribute, ProviderAttributeSignature, ProviderSnapsh import { ProviderSnapshot } from "@akashnetwork/database/dbSchemas/akash/providerSnapshot"; import { add, sub } from "date-fns"; import { Op } from "sequelize"; +import uniqBy from 'lodash/uniqBy' import { ProviderDetail } from "@src/types/provider"; import { toUTC } from "@src/utils"; @@ -24,31 +25,56 @@ export async function getNetworkCapacity() { ] }); - const filteredProviders = providers.filter((value, index, self) => self.map(x => x.hostUri).indexOf(value.hostUri) === index); - - const stats = { - activeProviderCount: filteredProviders.length, - activeCPU: filteredProviders.map(x => x.lastSuccessfulSnapshot.activeCPU).reduce((a, b) => a + b, 0), - activeGPU: filteredProviders.map(x => x.lastSuccessfulSnapshot.activeGPU).reduce((a, b) => a + b, 0), - activeMemory: filteredProviders.map(x => x.lastSuccessfulSnapshot.activeMemory).reduce((a, b) => a + b, 0), - activeStorage: filteredProviders - .map(x => x.lastSuccessfulSnapshot.activeEphemeralStorage + x.lastSuccessfulSnapshot.activePersistentStorage) - .reduce((a, b) => a + b, 0), - pendingCPU: filteredProviders.map(x => x.lastSuccessfulSnapshot.pendingCPU).reduce((a, b) => a + b, 0), - pendingGPU: filteredProviders.map(x => x.lastSuccessfulSnapshot.pendingGPU).reduce((a, b) => a + b, 0), - pendingMemory: filteredProviders.map(x => x.lastSuccessfulSnapshot.pendingMemory).reduce((a, b) => a + b, 0), - pendingStorage: filteredProviders - .map(x => x.lastSuccessfulSnapshot.pendingEphemeralStorage + x.lastSuccessfulSnapshot.pendingPersistentStorage) - .reduce((a, b) => a + b, 0), - availableCPU: filteredProviders.map(x => x.lastSuccessfulSnapshot.availableCPU).reduce((a, b) => a + b, 0), - availableGPU: filteredProviders.map(x => x.lastSuccessfulSnapshot.availableGPU).reduce((a, b) => a + b, 0), - availableMemory: filteredProviders.map(x => x.lastSuccessfulSnapshot.availableMemory).reduce((a, b) => a + b, 0), - availableStorage: filteredProviders - .map(x => x.lastSuccessfulSnapshot.availableEphemeralStorage + x.lastSuccessfulSnapshot.availablePersistentStorage) - .reduce((a, b) => a + b, 0) - }; + const filteredProviders = uniqBy(providers, (provider) => provider.hostUri); + // TODO: use aggregated stats format per stats entry as in ProviderList['stats'] + const stats = filteredProviders.reduce((all, provider) => { + stats.activeCPU += provider.lastSuccessfulSnapshot.activeCPU; + stats.pendingCPU += provider.lastSuccessfulSnapshot.pendingCPU; + stats.availableCPU += provider.lastSuccessfulSnapshot.availableCPU; + + stats.activeGPU += provider.lastSuccessfulSnapshot.activeGPU; + stats.pendingGPU += provider.lastSuccessfulSnapshot.pendingGPU; + stats.availableGPU += provider.lastSuccessfulSnapshot.availableGPU; + + stats.activeMemory += provider.lastSuccessfulSnapshot.activeMemory; + stats.pendingMemory += provider.lastSuccessfulSnapshot.pendingMemory; + stats.availableMemory += provider.lastSuccessfulSnapshot.availableMemory; + + stats.activeEphemeralStorage += provider.lastSuccessfulSnapshot.activeEphemeralStorage; + stats.pendingEphemeralStorage += provider.lastSuccessfulSnapshot.pendingEphemeralStorage; + stats.availableEphemeralStorage += provider.lastSuccessfulSnapshot.availableEphemeralStorage; + + stats.activePersistentStorage += provider.lastSuccessfulSnapshot.activePersistentStorage; + stats.pendingPersistentStorage += provider.lastSuccessfulSnapshot.pendingPersistentStorage; + stats.availablePersistentStorage += provider.lastSuccessfulSnapshot.availablePersistentStorage; + + return all; + }, { + activeCPU: 0, + pendingCPU: 0, + availableCPU: 0, + activeGPU: 0, + pendingGPU: 0, + availableGPU: 0, + activeMemory: 0, + pendingMemory: 0, + availableMemory: 0, + activeStorage: 0, + pendingStorage: 0, + availableStorage: 0, + activeEphemeralStorage: 0, + pendingEphemeralStorage: 0, + availableEphemeralStorage: 0, + activePersistentStorage: 0, + pendingPersistentStorage: 0, + availablePersistentStorage: 0, + }); return { + activeProviderCount: filteredProviders.length, + activeStorage: stats.activeEphemeralStorage + stats.activePersistentStorage, + pendingStorage: stats.pendingEphemeralStorage + stats.pendingPersistentStorage, + availableStorage: stats.availableEphemeralStorage + stats.availablePersistentStorage, ...stats, totalCPU: stats.activeCPU + stats.pendingCPU + stats.availableCPU, totalGPU: stats.activeGPU + stats.pendingGPU + stats.availableGPU, @@ -96,10 +122,8 @@ export const getProviderList = async () => { }); const distinctProviders = providersWithAttributesAndAuditors.filter((value, index, self) => self.map(x => x.hostUri).lastIndexOf(value.hostUri) === index); - const providerAttributeSchemaQuery = getProviderAttributesSchema(); - const auditorsQuery = getAuditors(); - const [auditors, providerAttributeSchema] = await Promise.all([auditorsQuery, providerAttributeSchemaQuery]); + const [auditors, providerAttributeSchema] = await Promise.all([getAuditors(), getProviderAttributesSchema()]); return distinctProviders.map(x => { const lastSuccessfulSnapshot = providerWithNodes.find(p => p.owner === x.owner)?.lastSuccessfulSnapshot; @@ -133,7 +157,7 @@ export const getProviderDetail = async (address: string): Promise, lastSuccessfulSnapshot?: ProviderSnapshot ): ProviderList => { - const isValidVersion = provider.cosmosSdkVersion ? semver.gte(provider.cosmosSdkVersion, "v0.45.9") : false; + const isValidSdkVersion = provider.cosmosSdkVersion ? semver.gte(provider.cosmosSdkVersion, "v0.45.9") : false; const name = provider.isOnline ? new URL(provider.hostUri).hostname : null; const gpuModels = getDistinctGpuModelsFromNodes(lastSuccessfulSnapshot?.nodes || []); + const stats: ProviderList['stats'] = { + cpu: buildStatsItem('CPU', lastSuccessfulSnapshot, isValidSdkVersion), + gpu: buildStatsItem('GPU', lastSuccessfulSnapshot, isValidSdkVersion), + memory: buildStatsItem('Memory', lastSuccessfulSnapshot, isValidSdkVersion), + storage: { + ephemeral: buildStatsItem('EphemeralStorage', lastSuccessfulSnapshot, isValidSdkVersion), + persistent: buildStatsItem('PersistentStorage', lastSuccessfulSnapshot, isValidSdkVersion), + }, + }; return { owner: provider.owner, @@ -32,29 +41,15 @@ export const mapProviderToList = ( ipCountryCode: provider.ipCountryCode, ipLat: provider.ipLat, ipLon: provider.ipLon, - activeStats: { - cpu: lastSuccessfulSnapshot?.activeCPU, - gpu: lastSuccessfulSnapshot?.activeGPU, - memory: lastSuccessfulSnapshot?.activeMemory, - storage: lastSuccessfulSnapshot?.activeEphemeralStorage + lastSuccessfulSnapshot?.activePersistentStorage - }, - pendingStats: { - cpu: isValidVersion ? lastSuccessfulSnapshot?.pendingCPU : 0, - gpu: isValidVersion ? lastSuccessfulSnapshot?.pendingGPU : 0, - memory: isValidVersion ? lastSuccessfulSnapshot?.pendingMemory : 0, - storage: isValidVersion ? lastSuccessfulSnapshot?.pendingEphemeralStorage + lastSuccessfulSnapshot?.pendingPersistentStorage : 0 - }, - availableStats: { - cpu: isValidVersion ? lastSuccessfulSnapshot?.availableCPU : 0, - gpu: isValidVersion ? lastSuccessfulSnapshot?.availableGPU : 0, - memory: isValidVersion ? lastSuccessfulSnapshot?.availableMemory : 0, - storage: isValidVersion ? lastSuccessfulSnapshot?.availableEphemeralStorage + lastSuccessfulSnapshot?.availablePersistentStorage : 0 - }, + stats, + activeStats: buildLegacyStatsItem(stats, 'active'), + pendingStats: buildLegacyStatsItem(stats, 'pending'), + availableStats: buildLegacyStatsItem(stats, 'pending'), gpuModels: gpuModels, uptime1d: provider.uptime1d, uptime7d: provider.uptime7d, uptime30d: provider.uptime30d, - isValidVersion, + isValidVersion: isValidSdkVersion, isOnline: provider.isOnline, lastOnlineDate: lastSuccessfulSnapshot?.checkDate, isAudited: provider.providerAttributeSignatures.some(a => auditors.some(y => y.address === a.auditor)), @@ -93,6 +88,32 @@ export const mapProviderToList = ( } as ProviderList; }; +type StatsEntry = 'CPU' | 'GPU' | 'Memory' | 'PersistentStorage' | 'EphemeralStorage'; +function buildStatsItem(suffix: T, snapshot: ProviderSnapshot | undefined | null, isValidSdkVersion: boolean): StatsItem { + if (!isValidSdkVersion) { + return { + active: snapshot?.[`active${suffix}`] || 0, + available: 0, + pending: 0, + }; + } + + return { + active: snapshot?.[`active${suffix}`] || 0, + available: snapshot?.[`available${suffix}`] || 0, + pending: snapshot?.[`pending${suffix}`] || 0, + }; +} + +function buildLegacyStatsItem(stats: ProviderList['stats'], type: keyof StatsItem) { + return { + cpu: stats.cpu[type], + gpu: stats.gpu[type], + memory: stats.memory[type], + storage: stats.storage.ephemeral[type] + stats.storage.persistent[type], + }; +} + function getDistinctGpuModelsFromNodes(nodes: ProviderSnapshotNode[]) { const gpuModels = nodes.flatMap(x => x.gpus).map(x => ({ vendor: x.vendor, model: x.name, ram: x.memorySize, interface: x.interface })); const distinctGpuModels = gpuModels.filter( @@ -102,8 +123,8 @@ function getDistinctGpuModelsFromNodes(nodes: ProviderSnapshotNode[]) { return distinctGpuModels; } -export const getProviderAttributeValue = ( - key: keyof ProviderAttributesSchema, +export const getProviderAttributeValue = ( + key: TKey, provider: Provider, providerAttributeSchema: ProviderAttributesSchema ): string | string[] | boolean | number | null => { diff --git a/apps/deploy-web/src/components/providers/NetworkCapacity.tsx b/apps/deploy-web/src/components/providers/NetworkCapacity.tsx index 78202525e..3b199d272 100644 --- a/apps/deploy-web/src/components/providers/NetworkCapacity.tsx +++ b/apps/deploy-web/src/components/providers/NetworkCapacity.tsx @@ -1,13 +1,16 @@ "use client"; import { useIntl } from "react-intl"; -import { ResponsivePie } from "@nivo/pie"; +import { ResponsivePie, ComputedDatum } from "@nivo/pie"; +import {BasicTooltip} from '@nivo/tooltip'; import { useTheme } from "next-themes"; import useTailwind from "@src/hooks/useTailwind"; import { roundDecimal } from "@src/utils/mathHelpers"; import { bytesToShrink } from "@src/utils/unitUtils"; -type Props = { +const getPieChartEntryColor = (datum: ComputedDatum<{ color: string }>) => datum.data.color; + +export interface Props { activeCPU: number; pendingCPU: number; totalCPU: number; @@ -20,52 +23,44 @@ type Props = { activeStorage: number; pendingStorage: number; totalStorage: number; -}; + activeEphemeralStorage: number; + pendingEphemeralStorage: number; + availableEphemeralStorage: number; + activePersistentStorage: number; + pendingPersistentStorage: number; + availablePersistentStorage: number; +} -const NetworkCapacity: React.FunctionComponent = ({ - activeCPU, - pendingCPU, - totalCPU, - activeGPU, - pendingGPU, - totalGPU, - activeMemory, - pendingMemory, - totalMemory, - activeStorage, - pendingStorage, - totalStorage -}) => { - const { resolvedTheme } = useTheme(); - const tw = useTailwind(); +const NetworkCapacity: React.FunctionComponent = (props) => { + const { + activeCPU, + pendingCPU, + totalCPU, + activeGPU, + pendingGPU, + totalGPU, + activeMemory, + pendingMemory, + totalMemory, + activeStorage, + pendingStorage, + totalStorage + } = props; const activeMemoryBytes = activeMemory + pendingMemory; const availableMemoryBytes = totalMemory - (activeMemory + pendingMemory); const activeStorageBytes = activeStorage + pendingStorage; - const availableStorageBytes = totalStorage - (activeStorage + pendingStorage); const _activeMemory = bytesToShrink(activeMemoryBytes); const _totalMemory = bytesToShrink(totalMemory); const _availableMemory = bytesToShrink(availableMemoryBytes); const _activeStorage = bytesToShrink(activeStorageBytes); - const _availableStorage = bytesToShrink(availableStorageBytes); const _totalStorage = bytesToShrink(totalStorage); const cpuData = useData(activeCPU + pendingCPU, totalCPU - activeCPU - pendingCPU); const gpuData = useData(activeGPU + pendingGPU, totalGPU - activeGPU - pendingGPU); const memoryData = useData(activeMemoryBytes, availableMemoryBytes); - const storageData = useData(activeStorageBytes, availableStorageBytes); + const storageData = useStorageData(props); const pieTheme = usePieTheme(); const intl = useIntl(); - const _getColor = bar => getColor(bar.id); - - const colors = { - active: tw.theme.colors["primary"].DEFAULT, - available: resolvedTheme === "dark" ? tw.theme.colors.neutral[800] : tw.theme.colors.neutral[500] - }; - - const getColor = (id: string) => { - return colors[id]; - }; - return (
@@ -81,7 +76,7 @@ const NetworkCapacity: React.FunctionComponent = ({ padAngle={2} cornerRadius={4} activeOuterRadiusOffset={8} - colors={_getColor} + colors={getPieChartEntryColor} borderWidth={0} borderColor={{ from: "color", @@ -95,8 +90,9 @@ const NetworkCapacity: React.FunctionComponent = ({ })} CPU` } enableArcLinkLabels={false} - arcLabelsSkipAngle={10} + arcLabelsSkipAngle={30} theme={pieTheme} + tooltip={TooltipLabel} />
@@ -114,7 +110,7 @@ const NetworkCapacity: React.FunctionComponent = ({ padAngle={2} cornerRadius={4} activeOuterRadiusOffset={8} - colors={_getColor} + colors={getPieChartEntryColor} borderWidth={0} borderColor={{ from: "color", @@ -127,8 +123,9 @@ const NetworkCapacity: React.FunctionComponent = ({ })} GPU` } enableArcLinkLabels={false} - arcLabelsSkipAngle={10} + arcLabelsSkipAngle={30} theme={pieTheme} + tooltip={TooltipLabel} /> @@ -146,7 +143,7 @@ const NetworkCapacity: React.FunctionComponent = ({ padAngle={2} cornerRadius={4} activeOuterRadiusOffset={8} - colors={_getColor} + colors={getPieChartEntryColor} borderWidth={0} borderColor={{ from: "color", @@ -158,8 +155,9 @@ const NetworkCapacity: React.FunctionComponent = ({ : `${roundDecimal(_availableMemory.value, 2)} ${_availableMemory.unit}` } enableArcLinkLabels={false} - arcLabelsSkipAngle={10} + arcLabelsSkipAngle={30} theme={pieTheme} + tooltip={TooltipLabel} /> @@ -177,20 +175,20 @@ const NetworkCapacity: React.FunctionComponent = ({ padAngle={2} cornerRadius={4} activeOuterRadiusOffset={8} - colors={_getColor} + colors={getPieChartEntryColor} borderWidth={0} borderColor={{ from: "color", modifiers: [["darker", 0.2]] }} - valueFormat={value => - value === activeStorageBytes - ? `${roundDecimal(_activeStorage.value, 2)} ${_activeStorage.unit}` - : `${roundDecimal(_availableStorage.value, 2)} ${_availableStorage.unit}` - } + valueFormat={value => { + const formatted = bytesToShrink(value); + return `${roundDecimal(formatted.value, 2)} ${formatted.unit}`; + }} enableArcLinkLabels={false} - arcLabelsSkipAngle={10} + arcLabelsSkipAngle={30} theme={pieTheme} + tooltip={TooltipLabel} /> @@ -199,6 +197,7 @@ const NetworkCapacity: React.FunctionComponent = ({ }; const useData = (active: number, available: number) => { + const { resolvedTheme } = useTheme(); const tw = useTailwind(); return [ @@ -212,13 +211,43 @@ const useData = (active: number, available: number) => { id: "available", label: "Available", value: available, - // TODO - // color: tw.theme.colors["success"].DEFAULT - color: tw.theme.colors.green[600] + color: resolvedTheme === "dark" ? tw.theme.colors.neutral[800] : tw.theme.colors.neutral[500] } ]; }; +function useStorageData(props: Props) { + const { resolvedTheme } = useTheme(); + const tw = useTailwind(); + + return [ + { + id: "active-ephemeral", + label: "Active emphemeral", + color: tw.theme.colors["primary"].DEFAULT, + value: props.activeEphemeralStorage + props.pendingEphemeralStorage, + }, + { + id: "active-persistent", + label: "Active persistent", + color: tw.theme.colors["primary"].visited, + value: props.activePersistentStorage + props.pendingPersistentStorage, + }, + { + id: "available-emphemeral", + label: "Available emphemeral", + color: resolvedTheme === "dark" ? tw.theme.colors.neutral[800] : tw.theme.colors.neutral[500], + value: props.availableEphemeralStorage, + }, + { + id: "available-persistent", + label: "Available persistent", + color: resolvedTheme === "dark" ? tw.theme.colors.neutral[600] : tw.theme.colors.neutral[300], + value: props.availablePersistentStorage, + } + ]; +} + const usePieTheme = () => { const { resolvedTheme } = useTheme(); const tw = useTailwind(); @@ -232,10 +261,19 @@ const usePieTheme = () => { color: resolvedTheme === "dark" ? tw.theme.colors.white : tw.theme.colors.current }, container: { - backgroundColor: resolvedTheme === "dark" ? tw.theme.colors.neutral[700] : tw.theme.colors.white + backgroundColor: resolvedTheme === "dark" ? tw.theme.colors.neutral[700] : tw.theme.colors.white, } } }; }; +const TooltipLabel = ({ datum }: { datum: ComputedDatum }) => ( + +) + export default NetworkCapacity; diff --git a/apps/deploy-web/src/components/providers/ProviderDetail.tsx b/apps/deploy-web/src/components/providers/ProviderDetail.tsx index bfb5758d9..2a31c3763 100644 --- a/apps/deploy-web/src/components/providers/ProviderDetail.tsx +++ b/apps/deploy-web/src/components/providers/ProviderDetail.tsx @@ -1,5 +1,5 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { FormattedDate } from "react-intl"; import { Alert, Card, CardContent, CustomNoDivTooltip, Spinner } from "@akashnetwork/ui/components"; import { cn } from "@akashnetwork/ui/utils"; @@ -13,7 +13,7 @@ import { LabelValue } from "@src/components/shared/LabelValue"; import { useWallet } from "@src/context/WalletProvider"; import { useAllLeases } from "@src/queries/useLeaseQuery"; import { useProviderAttributesSchema, useProviderDetail, useProviderStatus } from "@src/queries/useProvidersQuery"; -import { ApiProviderDetail, ClientProviderDetailWithStatus } from "@src/types/provider"; +import { ApiProviderDetail, ClientProviderDetailWithStatus, StatsItem } from "@src/types/provider"; import { domainName, UrlService } from "@src/utils/urlUtils"; import Layout from "../layout/Layout"; import { CustomNextSeo } from "../shared/CustomNextSeo"; @@ -21,6 +21,7 @@ import { Title } from "../shared/Title"; import { ActiveLeasesGraph } from "./ActiveLeasesGraph"; import ProviderDetailLayout, { ProviderDetailTabs } from "./ProviderDetailLayout"; import { ProviderSpecs } from "./ProviderSpecs"; +import type { Props as NetworkCapacityProps } from './NetworkCapacity'; const NetworkCapacity = dynamic(() => import("./NetworkCapacity"), { ssr: false @@ -69,6 +70,29 @@ export const ProviderDetail: React.FunctionComponent = ({ owner, _provide } }, [leases]); + const networkCapacity = useMemo(() => { + return { + activeCPU: provider.stats.cpu.active / 1000, + pendingCPU: provider.stats.cpu.pending / 1000, + totalCPU: collectTotals(provider.stats.cpu) / 1000, + activeGPU: provider.stats.gpu.active, + pendingGPU: provider.stats.gpu.pending, + totalGPU: collectTotals(provider.stats.gpu), + activeMemory: provider.stats.memory.active, + pendingMemory: provider.stats.memory.pending, + totalMemory: collectTotals(provider.stats.memory), + activeStorage: provider.stats.storage.ephemeral.active + provider.stats.storage.persistent.active, + pendingStorage: provider.stats.storage.ephemeral.pending + provider.stats.storage.persistent.pending, + totalStorage: collectTotals(provider.stats.storage.ephemeral) + collectTotals(provider.stats.storage.persistent), + activeEphemeralStorage: provider.stats.storage.ephemeral.active, + pendingEphemeralStorage: provider.stats.storage.ephemeral.pending, + availableEphemeralStorage: provider.stats.storage.ephemeral.available, + activePersistentStorage: provider.stats.storage.persistent.active, + pendingPersistentStorage: provider.stats.storage.persistent.pending, + availablePersistentStorage: provider.stats.storage.persistent.available, + }; + }, [provider]); + const refresh = () => { getProviderDetail(); getLeases(); @@ -119,23 +143,10 @@ export const ProviderDetail: React.FunctionComponent = ({ owner, _provide )} - {provider && wasRecentlyOnline && ( + {networkCapacity && wasRecentlyOnline && ( <>
- +
@@ -253,3 +264,7 @@ export const ProviderDetail: React.FunctionComponent = ({ owner, _provide ); }; + +function collectTotals(item: StatsItem): number { + return item.active + item.available + item.pending; +} \ No newline at end of file diff --git a/apps/deploy-web/src/components/providers/ProviderList.tsx b/apps/deploy-web/src/components/providers/ProviderList.tsx index 226be36a9..dfd09b0e9 100644 --- a/apps/deploy-web/src/components/providers/ProviderList.tsx +++ b/apps/deploy-web/src/components/providers/ProviderList.tsx @@ -200,20 +200,7 @@ export const ProviderList: React.FunctionComponent = () => { {providers && networkCapacity && (
- +
)} diff --git a/apps/deploy-web/src/types/provider.ts b/apps/deploy-web/src/types/provider.ts index 2f6427b0f..306a09a6b 100644 --- a/apps/deploy-web/src/types/provider.ts +++ b/apps/deploy-web/src/types/provider.ts @@ -208,24 +208,36 @@ export interface ApiProviderList { lastOnlineDate: string; isAudited: boolean; gpuModels: { vendor: string; model: string; ram: string; interface: string }[]; + /** @deprecated use `stats` instead */ activeStats: { cpu: number; gpu: number; memory: number; storage: number; }; + /** @deprecated use `stats` instead */ pendingStats: { cpu: number; gpu: number; memory: number; storage: number; }; + /** @deprecated use `stats` instead */ availableStats: { cpu: number; gpu: number; memory: number; storage: number; }; + stats: { + cpu: StatsItem; + gpu: StatsItem; + memory: StatsItem; + storage: { + ephemeral: StatsItem; + persistent: StatsItem; + }; + }, attributes: Array<{ key: string; value: string; @@ -292,3 +304,9 @@ export interface ApiProviderRegion { description: string; providers: string[]; } + +export interface StatsItem { + active: number; + available: number; + pending: number; +} \ No newline at end of file