diff --git a/apps/router/src/gateway-ui/Gateway.tsx b/apps/router/src/gateway-ui/Gateway.tsx index e8b9a6c93..46fdd6f3e 100644 --- a/apps/router/src/gateway-ui/Gateway.tsx +++ b/apps/router/src/gateway-ui/Gateway.tsx @@ -1,5 +1,14 @@ import React, { useState } from 'react'; -import { Flex } from '@chakra-ui/react'; +import { + Flex, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Card, + Divider, +} from '@chakra-ui/react'; import { ConnectFederationModal, LightningCard } from './components'; import { FederationsTable } from './components/federations/FederationsTable'; import { Loading } from './components/Loading'; @@ -15,8 +24,10 @@ import { useGatewayContext, useLoadGateway } from '../context/hooks'; import { ErrorMessage } from './components/ErrorMessage'; import { Login } from '@fedimint/ui'; import { GATEWAY_APP_ACTION_TYPE } from '../types/gateway'; +import { useTranslation } from '@fedimint/utils'; export const Gateway = () => { + const { t } = useTranslation(); const { state, dispatch, api, id } = useGatewayContext(); const [showConnectFed, setShowConnectFed] = useState(false); const [walletModalState, setWalletModalState] = useState({ @@ -25,6 +36,7 @@ export const Gateway = () => { type: WalletModalType.Onchain, selectedFederation: null, }); + const [activeTab, setActiveTab] = useState(0); useLoadGateway(); if (state.needsAuth) { @@ -48,29 +60,63 @@ export const Gateway = () => { return ( - {state.balances && ( - - )} - - setShowConnectFed(true)} - setWalletModalState={setWalletModalState} - /> + + + + + + {t('wallet.title')} + + + {t('info-card.card-header')} + + + {t('federation-card.table-title')} + + + + + + {state.balances && ( + + )} + + + + + + setShowConnectFed(true)} + setWalletModalState={setWalletModalState} + /> + + + + + setShowConnectFed(false)} diff --git a/apps/router/src/gateway-ui/components/federations/FederationsTable.tsx b/apps/router/src/gateway-ui/components/federations/FederationsTable.tsx index 357cc7a78..94bdc87f4 100644 --- a/apps/router/src/gateway-ui/components/federations/FederationsTable.tsx +++ b/apps/router/src/gateway-ui/components/federations/FederationsTable.tsx @@ -1,14 +1,5 @@ import React from 'react'; -import { - Card, - CardHeader, - CardBody, - Text, - Flex, - Link, - useTheme, - Button, -} from '@chakra-ui/react'; +import { Text, Flex, Link, useTheme, Button } from '@chakra-ui/react'; import { FederationInfo, MSats } from '@fedimint/types'; import { useTranslation, formatEllipsized, formatValue } from '@fedimint/utils'; import { Table, TableColumn, TableRow } from '@fedimint/ui'; @@ -101,22 +92,13 @@ export const FederationsTable: React.FC = ({ })); return ( - - - - - {t('federation-card.table-title')} - - - - - - - - - - + + + + +
+ ); }; diff --git a/apps/router/src/gateway-ui/components/lightning/LightningCard.tsx b/apps/router/src/gateway-ui/components/lightning/LightningCard.tsx index 44b5efc07..9d2fcae7f 100644 --- a/apps/router/src/gateway-ui/components/lightning/LightningCard.tsx +++ b/apps/router/src/gateway-ui/components/lightning/LightningCard.tsx @@ -9,7 +9,6 @@ import { } from '@chakra-ui/react'; import { LightningMode, Network } from '@fedimint/types'; import { formatEllipsized, getNodeUrl, useTranslation } from '@fedimint/utils'; -import { GatewayCard } from '..'; import { ReactComponent as CopyIcon } from '../../assets/svgs/copy.svg'; import { ReactComponent as LinkIcon } from '../../assets/svgs/linkIcon.svg'; @@ -52,58 +51,56 @@ export const LightningCard = React.memo(function LightningCard({ ); return ( - - - - {formatEllipsized(nodeId, 8)} - - {hasCopied && ( - - {t('common.copied')} - - )} - - {t('federation-card.view-link-on', { host: url.host })} - - - - } - /> + + + {formatEllipsized(nodeId, 8)} + + {hasCopied && ( + + {t('common.copied')} + + )} + + {t('federation-card.view-link-on', { host: url.host })} + + + + } + /> - - - - - - - - + + + + + + + ); }); diff --git a/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx b/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx index 91253f6c3..e8dc05f9b 100644 --- a/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx +++ b/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx @@ -1,7 +1,6 @@ import React, { useMemo } from 'react'; import { Box, Button, Flex, Text, Square } from '@chakra-ui/react'; import { formatValue, useTranslation } from '@fedimint/utils'; -import { GatewayCard } from '../GatewayCard'; import { FederationInfo, GatewayBalances, MSats } from '@fedimint/types'; import { PieChart } from 'react-minimal-pie-chart'; import { FaArrowDown, FaArrowUp } from 'react-icons/fa'; @@ -80,95 +79,93 @@ export const WalletCard = React.memo(function WalletCard({ }, [balanceData, unit]); return ( - - - {/* Balance section */} - - {balanceData.map((item) => ( - - - - - {item.title}: - - - - {item.formattedValue} - - - ))} - - - - {t('wallet.total')}: - - - {totalBalance} + + {/* Balance section */} + + {balanceData.map((item) => ( + + + + + {item.title}: - - - - {/* Pie chart section */} - - + + {item.formattedValue} + + + ))} + + + + {t('wallet.total')}: + + + {totalBalance} + + + - {/* Deposit/Withdraw section */} - - - - + {/* Pie chart section */} + + + + + {/* Deposit/Withdraw section */} + + + - + ); }); diff --git a/apps/router/src/gateway-ui/languages/en.json b/apps/router/src/gateway-ui/languages/en.json index cdaf3cc0f..982b1213f 100644 --- a/apps/router/src/gateway-ui/languages/en.json +++ b/apps/router/src/gateway-ui/languages/en.json @@ -127,7 +127,7 @@ "title": "Lightning Gateway Dashboard" }, "info-card": { - "card-header": "Lightning Node Info", + "card-header": "Node Info", "node-id": "Node ID", "alias": "Alias", "mode": "Mode", diff --git a/apps/router/src/guardian-ui/admin/FederationAdmin.tsx b/apps/router/src/guardian-ui/admin/FederationAdmin.tsx index 562220e85..bae033130 100644 --- a/apps/router/src/guardian-ui/admin/FederationAdmin.tsx +++ b/apps/router/src/guardian-ui/admin/FederationAdmin.tsx @@ -1,20 +1,14 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Flex, Box, Heading, Skeleton } from '@chakra-ui/react'; +import { Flex, Heading, Skeleton } from '@chakra-ui/react'; import { ClientConfig, SignedApiAnnouncement, StatusResponse, } from '@fedimint/types'; -import { GatewaysCard } from '../components/dashboard/gateways/GatewaysCard'; -import { GuardiansCard } from '../components/dashboard/guardians/GuardiansCard'; -import { FederationInfoCard } from '../components/dashboard/admin/FederationInfoCard'; -import { BitcoinNodeCard } from '../components/dashboard/admin/BitcoinNodeCard'; -import { BalanceCard } from '../components/dashboard/admin/BalanceCard'; -import { InviteCode } from '../components/dashboard/admin/InviteCode'; import { FederationTabsCard } from '../components/dashboard/tabs/FederationTabsCard'; -import { BftInfo } from '../components/BftInfo'; import { DangerZone } from '../components/dashboard/danger/DangerZone'; import { useGuardianAdminApi } from '../../context/hooks'; +import { InviteCode } from '../components/dashboard/admin/InviteCode'; const findOurPeerId = ( configPeerIds: number[], @@ -82,49 +76,14 @@ export const FederationAdmin: React.FC = () => { )} - - - - - {config && ( - - - - )} - - - - - - - - - - + {ourPeer && ( )} { - const { t } = useTranslation(); - const api = useGuardianAdminApi(); - const [auditSummary, setAuditSummary] = useState(); - const [unit, setUnit] = useState<'msats' | 'sats' | 'btc'>('msats'); - - useEffect(() => { - const fetchBalance = () => { - api.audit().then(setAuditSummary).catch(console.error); - }; - fetchBalance(); - const interval = setInterval(fetchBalance, 5000); - return () => clearInterval(interval); - }, [api]); - - return ( - - - - - {t('federation-dashboard.balance.label')} - - - - - - - - - - {auditSummary ? ( - - ) : ( - Loading... - )} - - - ); -}; diff --git a/apps/router/src/guardian-ui/components/dashboard/admin/BalanceTable.tsx b/apps/router/src/guardian-ui/components/dashboard/admin/BalanceTable.tsx deleted file mode 100644 index f007b8f6d..000000000 --- a/apps/router/src/guardian-ui/components/dashboard/admin/BalanceTable.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; -import { - Table, - Tbody, - Td, - Th, - Thead, - Tr, - Icon, - Flex, - Box, - Text, - Grid, - GridItem, -} from '@chakra-ui/react'; -import { ReactComponent as CheckIcon } from '../../../assets/svgs/check-circle.svg'; -import { ReactComponent as CloseIcon } from '../../../assets/svgs/x-circle.svg'; -import { AuditSummary, ModuleKind, MSats } from '@fedimint/types'; -import { formatValue } from '@fedimint/utils'; - -interface BalanceTableProps { - auditSummary: AuditSummary; - unit: 'msats' | 'sats' | 'btc'; -} - -export const BalanceTable: React.FC = ({ - auditSummary, - unit, -}) => { - const moduleSummaries = Object.entries(auditSummary.module_summaries).filter( - ([, module]) => module.kind !== ModuleKind.Unknown - ); - - const totalAssets = moduleSummaries.reduce((sum, [, module]) => { - return sum + (module.net_assets > 0 ? module.net_assets : 0); - }, 0) as MSats; - const totalLiabilities = moduleSummaries.reduce((sum, [, module]) => { - return sum + Math.abs(module.net_assets < 0 ? module.net_assets : 0); - }, 0) as MSats; - - return ( - -
- - - - - - - - - {moduleSummaries.map(([key, module]) => ( - - - - - - ))} - - - - - - -
ModuleAssetsLiabilities
{module.kind} - {module.net_assets > 0 - ? formatValue(module.net_assets, unit) - : '0'} - - {module.net_assets < 0 - ? formatValue(Math.abs(module.net_assets) as MSats, unit) - : '0'} -
Total - {formatValue(totalAssets, unit)} - - {formatValue(totalLiabilities, unit)} -
- {/* TODO: make locales for all these */} - {totalAssets - totalLiabilities > 0 && ( - - - - Accumulated Guardian Fees: - - - (Assets - Liabilities) - - - - {`${formatValue(totalAssets, unit)} - ${formatValue( - totalLiabilities, - unit - )}`} - - - - - = {formatValue((totalAssets - totalLiabilities) as MSats, unit)} - - - - - )} - - - - Full Reserve? - - - (Assets {'>='} Liabilities) - - - {totalAssets >= totalLiabilities ? ( - - - {`${formatValue(totalAssets, unit)} >= ${formatValue( - totalLiabilities, - unit - )}`} - - - - ) : ( - - - {`${formatValue(totalLiabilities, unit)} > ${formatValue( - totalAssets, - unit - )}`} - - - - )} - - - - - ); -}; diff --git a/apps/router/src/guardian-ui/components/dashboard/admin/FederationInfoCard.tsx b/apps/router/src/guardian-ui/components/dashboard/admin/FederationInfoCard.tsx deleted file mode 100644 index 11405827b..000000000 --- a/apps/router/src/guardian-ui/components/dashboard/admin/FederationInfoCard.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { - Card, - CardBody, - CardHeader, - Text, - SimpleGrid, - Box, -} from '@chakra-ui/react'; -import { ClientConfig, StatusResponse, Versions } from '@fedimint/types'; -import { useTranslation } from '@fedimint/utils'; -import { KeyValues } from '@fedimint/ui'; -import { useGuardianAdminApi } from '../../../../context/hooks'; - -interface Props { - status: StatusResponse | undefined; - config: ClientConfig | undefined; - latestSession: number | undefined; -} - -export const FederationInfoCard: React.FC = ({ - status, - config, - latestSession, -}) => { - const { t } = useTranslation(); - const api = useGuardianAdminApi(); - const [versions, setVersions] = useState(); - const [blockCount, setBlockCount] = useState(); - - const serverStatus = status?.server || ''; - const apiVersion = versions?.core.api.length - ? `${versions.core.api[0].major}.${versions.core.api[0].minor}` - : ''; - const consensusVersion = - versions?.core.core_consensus !== undefined - ? `${versions.core.core_consensus.major}.${versions.core.core_consensus.minor}` - : ''; - - useEffect(() => { - api.version().then(setVersions).catch(console.error); - }, [api]); - - useEffect(() => { - if (!config) return; - const fetchBlockCount = () => { - api.fetchBlockCount(config).then(setBlockCount).catch(console.error); - }; - fetchBlockCount(); - const interval = setInterval(fetchBlockCount, 5000); - return () => clearInterval(interval); - }, [api, config]); - - const federationInfoKeyValues = useMemo( - () => [ - { - key: 'status', - label: t('federation-dashboard.fed-info.your-status-label'), - value: serverStatus, - }, - { - key: 'blockCount', - label: t('federation-dashboard.fed-info.block-count-label'), - value: blockCount, - }, - { - key: 'apiVersion', - label: t('federation-dashboard.fed-info.api-version-label'), - value: apiVersion, - }, - { - key: 'consensusVersion', - label: t('federation-dashboard.fed-info.consensus-version-label'), - value: consensusVersion, - }, - ], - [t, serverStatus, blockCount, apiVersion, consensusVersion] - ); - - const sessionInfoKeyValues = useMemo( - () => [ - { - key: 'latestSession', - label: t('federation-dashboard.fed-info.session-info.session-height'), - value: latestSession ?? 0, - }, - ], - [t, latestSession] - ); - - return ( - - - - {t('federation-dashboard.fed-info.label')} - - - - - - - - - - - - - - ); -}; diff --git a/apps/router/src/guardian-ui/components/dashboard/admin/InviteCode.tsx b/apps/router/src/guardian-ui/components/dashboard/admin/InviteCode.tsx index 0fc1ed57b..afb2d3432 100644 --- a/apps/router/src/guardian-ui/components/dashboard/admin/InviteCode.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/admin/InviteCode.tsx @@ -33,7 +33,7 @@ export const InviteCode: React.FC = ({ inviteCode }) => { const handleClose = () => setIsOpen(false); return ( - + = ({ inviteCode }) => { - - {t('federation-dashboard.invite-members-prompt')} - ); }; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx index be58788d7..b574976ca 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx @@ -1,8 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Card, - CardBody, - CardHeader, Flex, Tabs, TabList, @@ -10,6 +8,7 @@ import { Tab, TabPanel, Badge, + Divider, } from '@chakra-ui/react'; import { githubLight } from '@uiw/codemirror-theme-github'; import { json } from '@codemirror/lang-json'; @@ -22,14 +21,17 @@ import { ModuleKind, ParsedConsensusMeta, SignedApiAnnouncement, + StatusResponse, } from '@fedimint/types'; import { useTranslation, hexToMeta } from '@fedimint/utils'; import { MetaManager } from './meta/manager/MetaManager'; import { ApiAnnouncements } from './ApiAnnouncements'; - import { ProposedMetas } from './meta/proposals/ProposedMetas'; import { ModuleRpc } from '../../../../types/guardian'; import { useGuardianAdminApi } from '../../../../context/hooks'; +import { BalanceSheet } from './balanceSheet/BalanceSheet'; +import { FederationInfo } from './info/FederationInfo'; +import { FederationStatus } from './status/FederationStatus'; export const DEFAULT_META_KEY = 0; export const POLL_TIMEOUT_MS = 2000; @@ -45,12 +47,16 @@ interface FederationTabsCardProps { config: ClientConfig | undefined; ourPeer: { id: number; name: string }; signedApiAnnouncements: Record; + latestSession: number | undefined; + status: StatusResponse | undefined; } export const FederationTabsCard: React.FC = ({ config, ourPeer, signedApiAnnouncements, + latestSession, + status, }) => { const { t } = useTranslation(); const [metaModuleId, setMetaModuleId] = useState( @@ -172,60 +178,78 @@ export const FederationTabsCard: React.FC = ({ }, [config]); return config ? ( - + - - - - - - {t('federation-dashboard.config.view-meta')} - - - {t('federation-dashboard.config.view-config')} - - - {t('federation-dashboard.api-announcements.label')} - - {pendingProposalsCount > 0 && ( - - - {t( - 'federation-dashboard.config.manage-meta.proposed-meta-label' - )} - - {pendingProposalsCount} - - - - )} - - - - - - + + + + {t('federation-dashboard.info.label')} + + + {t('federation-dashboard.status.label')} + + + {t('federation-dashboard.balance.label')} + + + {t('federation-dashboard.config.view-config')} + + + {t('federation-dashboard.api-announcements.label')} + + + {t('federation-dashboard.config.manage-meta.label')} + + + {pendingProposalsCount > 0 && ( + + + {t( + 'federation-dashboard.config.manage-meta.proposed-meta-label' + )} + + {pendingProposalsCount} + + + + )} + + + - + + + + + + = ({ /> - + {pendingProposalsCount > 0 && ( + + + + )} - +
) : null; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/balanceSheet/BalanceSheet.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/balanceSheet/BalanceSheet.tsx new file mode 100644 index 000000000..eccabe00e --- /dev/null +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/balanceSheet/BalanceSheet.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useState } from 'react'; +import { Button, ButtonGroup, Text, Flex } from '@chakra-ui/react'; +import { AuditSummary } from '@fedimint/types'; +import { useTranslation } from '@fedimint/utils'; +import { BalanceTable } from './BalanceTable'; +import { useGuardianAdminApi } from '../../../../../context/hooks'; + +export const BalanceSheet: React.FC = () => { + const { t } = useTranslation(); + const api = useGuardianAdminApi(); + const [auditSummary, setAuditSummary] = useState(); + const [unit, setUnit] = useState<'msats' | 'sats' | 'btc'>('msats'); + + useEffect(() => { + const fetchBalance = () => { + api.audit().then(setAuditSummary).catch(console.error); + }; + fetchBalance(); + const interval = setInterval(fetchBalance, 5000); + return () => clearInterval(interval); + }, [api]); + + return ( + + + + {t('federation-dashboard.balance.label')} + + + + + + + + {auditSummary ? ( + + ) : ( + Loading... + )} + + ); +}; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/balanceSheet/BalanceTable.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/balanceSheet/BalanceTable.tsx new file mode 100644 index 000000000..089826538 --- /dev/null +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/balanceSheet/BalanceTable.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { + Table, + Tbody, + Td, + Th, + Thead, + Tr, + Icon, + Flex, + Box, + Text, + Grid, + GridItem, +} from '@chakra-ui/react'; +import { ReactComponent as CheckIcon } from '../../../../assets/svgs/check-circle.svg'; +import { ReactComponent as CloseIcon } from '../../../../assets/svgs/x-circle.svg'; +import { AuditSummary, ModuleKind, MSats } from '@fedimint/types'; +import { formatValue } from '@fedimint/utils'; + +interface BalanceTableProps { + auditSummary: AuditSummary; + unit: 'msats' | 'sats' | 'btc'; +} + +export const BalanceTable: React.FC = ({ + auditSummary, + unit, +}) => { + const moduleSummaries = Object.entries(auditSummary.module_summaries).filter( + ([, module]) => module.kind !== ModuleKind.Unknown + ); + + const totalAssets = moduleSummaries.reduce((sum, [, module]) => { + return sum + (module.net_assets > 0 ? module.net_assets : 0); + }, 0) as MSats; + const totalLiabilities = moduleSummaries.reduce((sum, [, module]) => { + return sum + Math.abs(module.net_assets < 0 ? module.net_assets : 0); + }, 0) as MSats; + + return ( + + + + + + + + + + + + {moduleSummaries.map(([key, module]) => ( + + + + + + ))} + + + + + + +
ModuleAssetsLiabilities
{module.kind} + {module.net_assets > 0 + ? formatValue(module.net_assets, unit) + : '0'} + + {module.net_assets < 0 + ? formatValue(Math.abs(module.net_assets) as MSats, unit) + : '0'} +
Total + {formatValue(totalAssets, unit)} + + {formatValue(totalLiabilities, unit)} +
+
+ + + + {totalAssets - totalLiabilities > 0 && ( + + + + Accumulated Fees: + + + + {formatValue( + (totalAssets - totalLiabilities) as MSats, + unit + ) + + ' ' + + unit} + + + + + )} + + + + + Full Reserve? + + + {totalAssets >= totalLiabilities ? ( + + + Yes + + + + ) : ( + + + No + + + + )} + + + + + +
+ ); +}; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/info/FederationInfo.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/info/FederationInfo.tsx new file mode 100644 index 000000000..ab391a30a --- /dev/null +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/info/FederationInfo.tsx @@ -0,0 +1,124 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { SimpleGrid, Box, Skeleton, Flex } from '@chakra-ui/react'; +import { + ClientConfig, + StatusResponse, + Versions, + ModuleConfigs, + ModuleKind, +} from '@fedimint/types'; +import { useTranslation } from '@fedimint/utils'; +import { KeyValues, NetworkIndicator } from '@fedimint/ui'; +import { useGuardianAdminApi } from '../../../../../context/hooks'; +import { BftInfo } from '../../../BftInfo'; + +interface Props { + status: StatusResponse | undefined; + config: ClientConfig | undefined; + latestSession: number | undefined; + modulesConfigs: ModuleConfigs | undefined; +} + +export const FederationInfo: React.FC = ({ + status, + config, + latestSession, + modulesConfigs, +}) => { + const { t } = useTranslation(); + const api = useGuardianAdminApi(); + const [versions, setVersions] = useState(); + const [blockCount, setBlockCount] = useState(); + + const serverStatus = status?.server || ''; + const apiVersion = versions?.core.api.length + ? `${versions.core.api[0].major}.${versions.core.api[0].minor}` + : ''; + const consensusVersion = + versions?.core.core_consensus !== undefined + ? `${versions.core.core_consensus.major}.${versions.core.core_consensus.minor}` + : ''; + + useEffect(() => { + api.version().then(setVersions).catch(console.error); + }, [api]); + + useEffect(() => { + if (!config) return; + const fetchBlockCount = () => { + api.fetchBlockCount(config).then(setBlockCount).catch(console.error); + }; + fetchBlockCount(); + const interval = setInterval(fetchBlockCount, 5000); + return () => clearInterval(interval); + }, [api, config]); + + const walletConfig = modulesConfigs + ? Object.values(modulesConfigs).find( + (config): config is ModuleConfigs[ModuleKind.Wallet] => + config.kind === ModuleKind.Wallet + ) + : undefined; + + const federationInfoKeyValues = useMemo( + () => [ + { + key: 'status', + label: t('federation-dashboard.info.your-status-label'), + value: serverStatus, + }, + { + key: 'sessionHeight', + label: t('federation-dashboard.info.session-info.session-height'), + value: latestSession ?? 0, + }, + { + key: 'version', + label: t('federation-dashboard.info.version-label'), + value: `${consensusVersion} / ${apiVersion}`, + }, + ], + [t, serverStatus, latestSession, consensusVersion, apiVersion] + ); + + const bitcoinInfoKeyValues = useMemo( + () => [ + { + key: 'network', + label: t('federation-dashboard.bitcoin-node.network-label'), + value: walletConfig ? ( + + ) : ( + + ), + }, + { + key: 'blockCount', + label: t('federation-dashboard.info.consensus-block-height-label'), + value: blockCount ?? , + }, + ], + [t, walletConfig, blockCount] + ); + + return ( + + + + + + + + + + {config && ( + + + + )} + + ); +}; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/status/FederationStatus.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/status/FederationStatus.tsx new file mode 100644 index 000000000..b3b11b1c5 --- /dev/null +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/status/FederationStatus.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Flex } from '@chakra-ui/react'; +import { + ClientConfig, + StatusResponse, + SignedApiAnnouncement, +} from '@fedimint/types'; +import { GuardiansStatus } from './GuardiansStatus'; +import { GatewaysStatus } from './GatewaysStatus'; + +interface Props { + status: StatusResponse | undefined; + config: ClientConfig | undefined; + signedApiAnnouncements: Record; +} + +export const FederationStatus: React.FC = ({ + status, + config, + signedApiAnnouncements, +}) => { + return ( + + + + + ); +}; diff --git a/apps/router/src/guardian-ui/components/dashboard/gateways/GatewaysCard.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/status/GatewaysStatus.tsx similarity index 70% rename from apps/router/src/guardian-ui/components/dashboard/gateways/GatewaysCard.tsx rename to apps/router/src/guardian-ui/components/dashboard/tabs/status/GatewaysStatus.tsx index 5ccc95c36..a284495ed 100644 --- a/apps/router/src/guardian-ui/components/dashboard/gateways/GatewaysCard.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/status/GatewaysStatus.tsx @@ -5,9 +5,6 @@ import { AlertIcon, AlertTitle, Box, - Card, - CardBody, - CardHeader, Flex, Link, Text, @@ -16,17 +13,17 @@ import { import { ClientConfig, Gateway, ModuleKind } from '@fedimint/types'; import { Table, TableColumn, TableRow } from '@fedimint/ui'; import { useTranslation, formatEllipsized } from '@fedimint/utils'; -import { ReactComponent as InfoIcon } from '../../../assets/svgs/info.svg'; -import { useGuardianAdminApi } from '../../../../context/hooks'; -import { ModuleRpc } from '../../../../types/guardian'; +import { ReactComponent as InfoIcon } from '../../../../assets/svgs/info.svg'; +import { useGuardianAdminApi } from '../../../../../context/hooks'; +import { ModuleRpc } from '../../../../../types/guardian'; type TableKey = 'nodeId' | 'gatewayId' | 'fee'; -interface GatewaysCardProps { +interface GatewaysStatusProps { config: ClientConfig | undefined; } -export const GatewaysCard: React.FC = ({ config }) => { +export const GatewaysStatus: React.FC = ({ config }) => { const theme = useTheme(); const { t } = useTranslation(); const api = useGuardianAdminApi(); @@ -107,33 +104,29 @@ export const GatewaysCard: React.FC = ({ config }) => { ); return ( - - - - {t('federation-dashboard.gateways.label')} - - - - {gateways.length === 0 ? ( - - - - - - {t('federation-dashboard.gateways.no-gateways-info-title')} - - - {t( - 'federation-dashboard.gateways.no-gateways-info-description' - )} - - - - - ) : ( - - )} - - + + + {t('federation-dashboard.gateways.label')} + + {gateways.length === 0 ? ( + + + + + + {t('federation-dashboard.gateways.no-gateways-info-title')} + + + {t( + 'federation-dashboard.gateways.no-gateways-info-description' + )} + + + + + ) : ( +
+ )} + ); }; diff --git a/apps/router/src/guardian-ui/components/dashboard/guardians/GuardiansCard.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/status/GuardiansStatus.tsx similarity index 87% rename from apps/router/src/guardian-ui/components/dashboard/guardians/GuardiansCard.tsx rename to apps/router/src/guardian-ui/components/dashboard/tabs/status/GuardiansStatus.tsx index ade0eca56..97823b7f6 100644 --- a/apps/router/src/guardian-ui/components/dashboard/guardians/GuardiansCard.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/status/GuardiansStatus.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Card, CardBody, CardHeader, Text } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import { ClientConfig, PeerConnectionStatus, @@ -11,13 +11,13 @@ import { useTranslation } from '@fedimint/utils'; type TableKey = 'idName' | 'status' | 'health' | 'lastContribution' | 'apiUrl'; -interface Props { +interface GuardiansStatusProps { status: StatusResponse | undefined; config: ClientConfig | undefined; signedApiAnnouncements: Record; } -export const GuardiansCard: React.FC = ({ +export const GuardiansStatus: React.FC = ({ status, config, signedApiAnnouncements, @@ -101,15 +101,12 @@ export const GuardiansCard: React.FC = ({ } return ( - - - - {t('federation-dashboard.guardians.label')} - - - -
- - + + + {t('federation-dashboard.guardians.label')} + + +
+ ); }; diff --git a/apps/router/src/languages/ca.json b/apps/router/src/languages/ca.json index bc9cf6e30..908b8de18 100644 --- a/apps/router/src/languages/ca.json +++ b/apps/router/src/languages/ca.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Convida membres o autentica't com a tutor", "invite-members-prompt": "Comparteix això per convidar membres a unir-se a la federació", - "fed-info": { + "info": { "label": "Informació de la Federació", "your-status-label": "Estat", "block-count-label": "Alçada del Bloc de Consens", diff --git a/apps/router/src/languages/de.json b/apps/router/src/languages/de.json index 654365c23..e20a47fd6 100644 --- a/apps/router/src/languages/de.json +++ b/apps/router/src/languages/de.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Laden Sie Mitglieder ein oder authentifizieren Sie sich als Vormund", "invite-members-prompt": "Teilen Sie dies, um Mitglieder einzuladen, dem Verband beizutreten.", - "fed-info": { + "info": { "label": "Föderationsinfo", "your-status-label": "Status", "block-count-label": "Konsens-Blockhöhe", diff --git a/apps/router/src/languages/en.json b/apps/router/src/languages/en.json index 1ec238ff4..1c336f23f 100644 --- a/apps/router/src/languages/en.json +++ b/apps/router/src/languages/en.json @@ -50,10 +50,10 @@ "meta-field-key": "Meta field - {{key}}" }, "federation-dashboard": { - "invite-members": "Invite members or authenticate as a guardian", + "invite-members": "Invite members to join the federation", "invite-members-prompt": "Share this to invite members to join the federation", - "fed-info": { - "label": "Federation Info", + "info": { + "label": "Info", "your-status-label": "Status", "block-count-label": "Consensus Block Height", "api-version-label": "API version", @@ -62,7 +62,12 @@ "session-info": { "session-height": "Session Height", "latest-session": "Latest Session" - } + }, + "version-label": "Version (consensus / api)", + "consensus-block-height-label": "Consensus Bitcoin Block Height" + }, + "status": { + "label": "Status" }, "balance": { "label": "Balance Sheet" diff --git a/apps/router/src/languages/es.json b/apps/router/src/languages/es.json index aa48c971f..200f79e71 100644 --- a/apps/router/src/languages/es.json +++ b/apps/router/src/languages/es.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Invita a miembros o autentícate como un tutor", "invite-members-prompt": "Comparte esto para invitar a miembros a unirse a la federación", - "fed-info": { + "info": { "label": "Información de la Federación", "your-status-label": "Estado", "block-count-label": "Altura de Bloque de Consenso", diff --git a/apps/router/src/languages/fr.json b/apps/router/src/languages/fr.json index a6e77b051..a82f3d8b6 100644 --- a/apps/router/src/languages/fr.json +++ b/apps/router/src/languages/fr.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Invitez des membres ou authentifiez-vous en tant que tuteur", "invite-members-prompt": "Partagez ceci pour inviter des membres à rejoindre la fédération", - "fed-info": { + "info": { "label": "Informations sur la Fédération", "your-status-label": "Statut", "block-count-label": "Hauteur de Bloc Consensus", diff --git a/apps/router/src/languages/hu.json b/apps/router/src/languages/hu.json index 8f12c5783..095dfdef0 100644 --- a/apps/router/src/languages/hu.json +++ b/apps/router/src/languages/hu.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Hívjon meg tagokat vagy hitelesítse magát mint gondviselő.", "invite-members-prompt": "Ossza meg ezt, hogy meghívja a tagokat a szövetséghez csatlakozásra.", - "fed-info": { + "info": { "label": "Szövetségi Információ", "your-status-label": "Státusz", "block-count-label": "Konszenzus Blokk Magasság", diff --git a/apps/router/src/languages/it.json b/apps/router/src/languages/it.json index 65a86f437..a45ba3612 100644 --- a/apps/router/src/languages/it.json +++ b/apps/router/src/languages/it.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Invita membri o autenticati come un tutore", "invite-members-prompt": "Condividi questo per invitare i membri a unirsi alla federazione", - "fed-info": { + "info": { "label": "Informazioni sulla Federazione", "your-status-label": "Stato", "block-count-label": "Consensus Block Height", diff --git a/apps/router/src/languages/ja.json b/apps/router/src/languages/ja.json index 9786031ee..d1cead36e 100644 --- a/apps/router/src/languages/ja.json +++ b/apps/router/src/languages/ja.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "メンバーを招待するか、保護者として認証する", "invite-members-prompt": "これを共有して、連盟にメンバーを招待します。", - "fed-info": { + "info": { "label": "連盟情報", "your-status-label": "ステータス", "block-count-label": "合意ブロック高さ", diff --git a/apps/router/src/languages/ko.json b/apps/router/src/languages/ko.json index f9773c2e2..ba986a13e 100644 --- a/apps/router/src/languages/ko.json +++ b/apps/router/src/languages/ko.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "회원을 초대하거나 보호자로 인증하세요.", "invite-members-prompt": "이것을 공유하여 회원들이 연합에 가입하도록 초대하세요", - "fed-info": { + "info": { "label": "연합 정보", "your-status-label": "상태", "block-count-label": "합의 블록 높이", diff --git a/apps/router/src/languages/pt.json b/apps/router/src/languages/pt.json index a613547cc..4751cef73 100644 --- a/apps/router/src/languages/pt.json +++ b/apps/router/src/languages/pt.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Convide membros ou autentique-se como um guardião", "invite-members-prompt": "Compartilhe isso para convidar membros a se juntarem à federação", - "fed-info": { + "info": { "label": "Informações da Federação", "your-status-label": "Estado", "block-count-label": "Altura do Bloco de Consenso", diff --git a/apps/router/src/languages/ru.json b/apps/router/src/languages/ru.json index 584b4e419..9d3ad311b 100644 --- a/apps/router/src/languages/ru.json +++ b/apps/router/src/languages/ru.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "Пригласите участников или авторизуйтесь как опекун", "invite-members-prompt": "Поделитесь этим, чтобы пригласить участников присоединиться к федерации", - "fed-info": { + "info": { "label": "Информация о Федерации", "your-status-label": "Статус", "block-count-label": "Высота блока по консенсусу", diff --git a/apps/router/src/languages/zh.json b/apps/router/src/languages/zh.json index ebc26dee8..9e5dc0a72 100644 --- a/apps/router/src/languages/zh.json +++ b/apps/router/src/languages/zh.json @@ -52,7 +52,7 @@ "federation-dashboard": { "invite-members": "邀请成员或作为监护人进行身份验证", "invite-members-prompt": "分享此内容以邀请成员加入联盟", - "fed-info": { + "info": { "label": "联邦信息", "your-status-label": "状态", "block-count-label": "共识区块高度", diff --git a/flake.lock b/flake.lock index 98b80fabe..c133ed294 100644 --- a/flake.lock +++ b/flake.lock @@ -27,17 +27,17 @@ ] }, "locked": { - "lastModified": 1719001124, - "narHash": "sha256-JXrMwYlQarZPyjN5UkD4fS9mrHSE1PUa7P//1Z5Sqr0=", + "lastModified": 1727381935, + "narHash": "sha256-G2fOYRZM7bXK5eBb+GK3k/WmO+q5JA/GtFwSPc3kdc8=", "owner": "tadfisher", "repo": "android-nixpkgs", - "rev": "7fa1348249564e43185d3053f579f9fa923d46cc", + "rev": "522d86121cbd413aff922c54f38106ecf8740107", "type": "github" }, "original": { "owner": "tadfisher", "repo": "android-nixpkgs", - "rev": "7fa1348249564e43185d3053f579f9fa923d46cc", + "rev": "522d86121cbd413aff922c54f38106ecf8740107", "type": "github" } }, @@ -119,17 +119,17 @@ "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1727317398, - "narHash": "sha256-NUr1ZpYJozWIej46Oqlf/7feJ4kztYYvX3TEzQ5VoWo=", + "lastModified": 1729024672, + "narHash": "sha256-TXYDQsAxcv9/ltAI9CPzLaIFBeigmV+ZQO7d5AhyE44=", "owner": "fedimint", "repo": "fedimint", - "rev": "1813069d5d18a29f611a423990e8013a7331d2a2", + "rev": "adb5d429fcac40572839a568788795fe621578d8", "type": "github" }, "original": { "owner": "fedimint", - "ref": "refs/tags/v0.4.3", "repo": "fedimint", + "rev": "adb5d429fcac40572839a568788795fe621578d8", "type": "github" } }, @@ -280,17 +280,17 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1719004469, - "narHash": "sha256-TZSHiEJ3qYgA46vikQKT2bwGCEF2LrJVw7cettqa+/g=", + "lastModified": 1727478500, + "narHash": "sha256-nrDYdwIAI1nskNEE/r9rhDJDaouZ4tpSyURfndRsPho=", "owner": "dpc", "repo": "flakebox", - "rev": "12d5ee4f6c47bc01f07ec6f5848a83db265902d3", + "rev": "ee39d59b2c3779e5827f8fa2d269610c556c04c8", "type": "github" }, "original": { "owner": "dpc", "repo": "flakebox", - "rev": "12d5ee4f6c47bc01f07ec6f5848a83db265902d3", + "rev": "ee39d59b2c3779e5827f8fa2d269610c556c04c8", "type": "github" } }, diff --git a/flake.nix b/flake.nix index 6a78733d7..3c62a8db6 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ inputs = { flake-utils.url = "github:numtide/flake-utils"; fedimint.url = - "github:fedimint/fedimint?ref=refs/tags/v0.4.3"; + "github:fedimint/fedimint?rev=adb5d429fcac40572839a568788795fe621578d8"; }; outputs = { self, flake-utils, fedimint }: flake-utils.lib.eachDefaultSystem (system: