From cb98bbe0355ffd2a4f6615011e41316e6186da9b Mon Sep 17 00:00:00 2001 From: uwla Date: Fri, 10 May 2024 17:17:20 -0300 Subject: [PATCH] feat: enables manual balancing of channels User can manually balance channels using a slicer. The channels are shown in a modal after clicking a button. NOTE: this is a prototype, not working yet. --- src/components/designer/AutoMineButton.tsx | 6 +- .../designer/BalanceChannelsButton.tsx | 95 + src/components/designer/SyncButton.tsx | 13 +- src/components/network/NetworkActions.tsx | 58 +- src/store/models/lightning.ts | 1 + src/store/models/network.ts | 49 + src/utils/network.ts | 14 + yarn.lock | 3075 ++++++++--------- 8 files changed, 1574 insertions(+), 1737 deletions(-) create mode 100644 src/components/designer/BalanceChannelsButton.tsx diff --git a/src/components/designer/AutoMineButton.tsx b/src/components/designer/AutoMineButton.tsx index d23ff51dea..182f243f06 100644 --- a/src/components/designer/AutoMineButton.tsx +++ b/src/components/designer/AutoMineButton.tsx @@ -1,15 +1,15 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FieldTimeOutlined } from '@ant-design/icons'; import styled from '@emotion/styled'; -import { Button, Dropdown, Tooltip, MenuProps } from 'antd'; +import { Button, Dropdown, MenuProps, Tooltip } from 'antd'; import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { usePrefixedTranslation } from 'hooks'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useStoreActions, useStoreState } from 'store'; import { AutoMineMode, Network } from 'types'; const Styled = { Button: styled(Button)` - margin-left: 8px; + width: 100%; `, RemainingBar: styled.div` transition: width 400ms ease-in-out; diff --git a/src/components/designer/BalanceChannelsButton.tsx b/src/components/designer/BalanceChannelsButton.tsx new file mode 100644 index 0000000000..872b180669 --- /dev/null +++ b/src/components/designer/BalanceChannelsButton.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from 'react'; +import styled from '@emotion/styled'; +import { Button, Col, Modal, Row, Slider } from 'antd'; +import { LightningNode } from 'shared/types'; +import { LightningNodeChannel } from 'lib/lightning/types'; +import { useStoreActions, useStoreState } from 'store'; +import { Network } from 'types'; + +const Styled = { + Button: styled(Button)` + width: 100%; + `, +}; + +interface Props { + network: Network; +} + +const AutoBalanceButton: React.FC = ({ network }) => { + const { getChannels } = useStoreActions(s => s.lightning); + // const { notify } = useStoreActions(s => s.app); + const { links } = useStoreState(s => s.designer.activeChart); + const [visible, setVisible] = useState(false); + const [info, setInfo] = useState([] as any); + + const showModal = () => setVisible(true); + const hideModal = () => setVisible(false); + + // Store all channels in an array and build a map nodeName->node. + // const lnNodes = network.nodes.lightning; + async function updateInfo() { + const channels = [] as LightningNodeChannel[]; + const id2Node = {} as Record; + const promisesToAwait = [] as Promise[]; + + for (const node of network.nodes.lightning) { + promisesToAwait.push( + getChannels(node).then((nodeChannels: LightningNodeChannel[]) => { + channels.push(...nodeChannels); + id2Node[node.name] = node; + }), + ); + } + await Promise.all(promisesToAwait); + + const info = []; + for (const channel of channels) { + const { uniqueId: id, localBalance, remoteBalance } = channel; + if (!links[id]) continue; + const from = links[id].from.nodeId; + const to = links[id].to.nodeId; + info.push({ id, to, from, localBalance, remoteBalance }); + } + setInfo(info); + } + + useEffect(() => { + updateInfo(); + }, []); + + return ( + <> + Balance channels + + {info.map((t: any) => { + const { to, from, id, remoteBalance, localBalance } = t; + return ( +
+ + + {from} +
+ {localBalance} + + + {to} +
+ {remoteBalance} + +
+ +
+ ); + })} +
+ + ); +}; + +export default AutoBalanceButton; diff --git a/src/components/designer/SyncButton.tsx b/src/components/designer/SyncButton.tsx index 205a2c3d2e..ebac8b1eb0 100644 --- a/src/components/designer/SyncButton.tsx +++ b/src/components/designer/SyncButton.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { useAsyncCallback } from 'react-async-hook'; import { ReloadOutlined } from '@ant-design/icons'; import styled from '@emotion/styled'; @@ -9,17 +9,16 @@ import { Network } from 'types'; const Styled = { Button: styled(Button)` - margin-left: 8px; - font-size: 18px; - padding: 2px 0 !important; + width: 100%; `, }; interface Props { network: Network; + children?: ReactNode; } -const SyncButton: React.FC = ({ network }) => { +const SyncButton: React.FC = ({ children, network }) => { const { l } = usePrefixedTranslation('cmps.designer.SyncButton'); const { notify } = useStoreActions(s => s.app); const { syncChart } = useStoreActions(s => s.designer); @@ -42,7 +41,9 @@ const SyncButton: React.FC = ({ network }) => { icon={} onClick={syncChartAsync.execute} loading={syncChartAsync.loading} - /> + > + {children} + ); }; diff --git a/src/components/network/NetworkActions.tsx b/src/components/network/NetworkActions.tsx index ad64a5eb48..a4650f08fb 100644 --- a/src/components/network/NetworkActions.tsx +++ b/src/components/network/NetworkActions.tsx @@ -1,3 +1,4 @@ +import React, { ReactNode, useCallback } from 'react'; import { CloseOutlined, ExportOutlined, @@ -11,20 +12,23 @@ import { import styled from '@emotion/styled'; import { Button, Divider, Dropdown, MenuProps, Tag } from 'antd'; import { ButtonType } from 'antd/lib/button'; -import AutoMineButton from 'components/designer/AutoMineButton'; -import { useMiningAsync } from 'hooks/useMiningAsync'; -import SyncButton from 'components/designer/SyncButton'; import { usePrefixedTranslation } from 'hooks'; -import React, { ReactNode, useCallback } from 'react'; +import { useMiningAsync } from 'hooks/useMiningAsync'; import { Status } from 'shared/types'; import { useStoreState } from 'store'; import { Network } from 'types'; import { getNetworkBackendId } from 'utils/network'; +import AutoMineButton from 'components/designer/AutoMineButton'; +import BalanceChannelsButton from 'components/designer/BalanceChannelsButton'; +import SyncButton from 'components/designer/SyncButton'; const Styled = { Button: styled(Button)` margin-left: 0; `, + ButtonBlock: styled(Button)` + width: 100%; + `, Dropdown: styled(Dropdown)` margin-left: 12px; `, @@ -110,26 +114,52 @@ const NetworkActions: React.FC = ({ } }, []); - const items: MenuProps['items'] = [ + const networkActions: MenuProps['items'] = [ { key: 'rename', label: l('menuRename'), icon: }, { key: 'export', label: l('menuExport'), icon: }, { key: 'delete', label: l('menuDelete'), icon: }, ]; + const QuickMineButton = () => ( + } + > + {l('mineBtn')} + + ); + + const networkQuickActions: MenuProps['items'] = [ + { + key: 'sync', + label: Synchronize, + }, + { + key: 'balance', + label: , + }, + { + key: 'quickMine', + label: , + }, + { + key: 'autoMine', + label: , + }, + ]; + return ( <> {bitcoinNode.status === Status.Started && nodeState?.chainInfo && ( <> height: {nodeState.chainInfo.blocks} - - - + + )} @@ -146,7 +176,7 @@ const NetworkActions: React.FC = ({