diff --git a/src/components/pages/dashboard/ManageInstance/cmp.tsx b/src/components/pages/dashboard/ManageInstance/cmp.tsx index 4eda7261..489480a6 100644 --- a/src/components/pages/dashboard/ManageInstance/cmp.tsx +++ b/src/components/pages/dashboard/ManageInstance/cmp.tsx @@ -15,11 +15,14 @@ export default function ManageInstance() { const { instance, status, + mappedKeys, + crn, + nodeDetails, + handleRetryAllocation, handleCopyHash, handleCopyConnect, handleCopyIpv6, handleDelete, - mappedKeys, } = useManageInstance() const theme = useTheme() @@ -43,7 +46,7 @@ export default function ManageInstance() { <>
-
+
{name}
@@ -69,7 +72,17 @@ export default function ManageInstance() { )}
-
+
+ {!status?.vm_ipv6 && crn && ( + + )}
- {status?.node && ( + {nodeDetails && ( <> @@ -228,7 +241,7 @@ export default function ManageInstance() {
NAME
- {status.node.node_id} + {nodeDetails.name}
@@ -237,12 +250,12 @@ export default function ManageInstance() { diff --git a/src/domain/instance.ts b/src/domain/instance.ts index eaa0091d..1043994f 100644 --- a/src/domain/instance.ts +++ b/src/domain/instance.ts @@ -195,7 +195,7 @@ export class InstanceManager newInstance.node ) { yield - await this.notifyCRNExecution(newInstance.node, entity.id) + await this.notifyCRNExecution(newInstance.node, entity.id, false) } return entity @@ -289,6 +289,7 @@ export class InstanceManager }, } as InstanceStatus } + const query = await fetch( `https://scheduler.api.aleph.sh/api/v0/allocation/${instance.id}`, ) @@ -320,6 +321,43 @@ export class InstanceManager return steps } + async notifyCRNExecution( + node: CRN, + instanceId: string, + retry = true, + ): Promise { + if (!node.address) throw Err.InvalidCRNAddress + + let errorMsg = '' + + for (let i = 0; i < 5; i++) { + try { + // strip trailing slash + const nodeUrl = node.address.replace(/\/$/, '') + const req = await fetch(`${nodeUrl}/control/allocation/notify`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + instance: instanceId, + }), + }) + const resp = await req.json() + if (resp.success) return + + errorMsg = resp.errors[instanceId] + } catch (e) { + errorMsg = (e as Error).message + } finally { + if (!retry) break + await sleep(1000) + } + } + + throw Err.InstanceStartupFailed(node.hash, errorMsg) + } + protected async *addPAYGStreamSteps( newInstance: AddInstance, account?: SuperfluidAccount, @@ -442,38 +480,6 @@ export class InstanceManager }) } - protected async notifyCRNExecution( - node: CRN, - instanceId: string, - ): Promise { - if (!node.address) throw Err.InvalidCRNAddress - - let errorMsg = '' - for (let i = 0; i < 5; i++) { - try { - // strip trailing slash - const nodeUrl = node.address.replace(/\/$/, '') - const req = await fetch(`${nodeUrl}/control/allocation/notify`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - instance: instanceId, - }), - }) - const resp = await req.json() - if (resp.success) return - errorMsg = resp.errors[instanceId] - await sleep(1000) - } catch (e) { - errorMsg = (e as Error).message - await sleep(1000) - } - } - throw Err.InstanceStartupFailed(node.hash, errorMsg) - } - async getDelSteps( instancesOrIds: string | Instance | (string | Instance)[], ): Promise { diff --git a/src/hooks/pages/solutions/manage/useManageInstance.ts b/src/hooks/pages/solutions/manage/useManageInstance.ts index 80406be1..90fb734a 100644 --- a/src/hooks/pages/solutions/manage/useManageInstance.ts +++ b/src/hooks/pages/solutions/manage/useManageInstance.ts @@ -1,5 +1,5 @@ import { useRouter } from 'next/router' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { Instance, InstanceStatus } from '@/domain/instance' import { useInstanceManager } from '@/hooks/common/useManager/useInstanceManager' import { useAppState } from '@/contexts/appState' @@ -18,16 +18,21 @@ import { } from '@/hooks/form/useCheckoutNotification' import Err from '@/helpers/errors' import { BlockchainId } from '@/domain/connect/base' -import { useCopyToClipboardAndNotify } from '@aleph-front/core' +import { useCopyToClipboardAndNotify, useNotification } from '@aleph-front/core' +import { useNodeManager } from '@/hooks/common/useManager/useNodeManager' +import { CRN } from '@/domain/node' export type ManageInstance = { instance?: Instance status?: InstanceStatus + mappedKeys: (SSHKey | undefined)[] + crn?: CRN + nodeDetails?: { name: string; url: string } + handleRetryAllocation: () => void handleCopyHash: () => void handleCopyConnect: () => void handleCopyIpv6: () => void handleDelete: () => void - mappedKeys: (SSHKey | undefined)[] } export function useManageInstance(): ManageInstance { @@ -54,6 +59,7 @@ export function useManageInstance(): ManageInstance { ) const manager = useInstanceManager() + const nodeManager = useNodeManager() const sshKeyManager = useSSHKeyManager() const { next, stop } = useCheckoutNotification({}) @@ -69,26 +75,35 @@ export function useManageInstance(): ManageInstance { getMapped() }, [sshKeyManager, instance]) + const handleEnsureNetwork = useCallback(async () => { + let superfluidAccount + if (!instance) return + + if (instance.payment?.type === PaymentType.superfluid) { + if ( + blockchain !== BlockchainId.AVAX || + !(account instanceof AvalancheAccount) + ) { + handleConnect({ blockchain: BlockchainId.AVAX }) + throw Err.ConnectYourPaymentWallet + } + // @note: refactor in SDK calling init inside this method + superfluidAccount = createFromAvalancheAccount(account) + await superfluidAccount.init() + + return superfluidAccount + } else if (blockchain !== BlockchainId.ETH) { + handleConnect({ blockchain: BlockchainId.ETH }) + throw Err.ConnectYourPaymentWallet + } + }, [account, blockchain, handleConnect, instance]) + const handleDelete = useCallback(async () => { if (!manager) throw Err.ConnectYourWallet if (!instance) throw Err.InstanceNotFound try { - let superfluidAccount - if (instance.payment?.type === PaymentType.superfluid) { - if ( - blockchain !== BlockchainId.AVAX || - !(account instanceof AvalancheAccount) - ) { - handleConnect({ blockchain: BlockchainId.AVAX }) - throw Err.ConnectYourPaymentWallet - } - // @note: refactor in SDK calling init inside this method - superfluidAccount = createFromAvalancheAccount(account) - await superfluidAccount.init() - } else if (blockchain !== BlockchainId.ETH) { - handleConnect({ blockchain: BlockchainId.ETH }) - } + const superfluidAccount = await handleEnsureNetwork() const iSteps = await manager.getDelSteps(instance) const nSteps = iSteps.map((i) => stepsCatalog[i]) @@ -109,25 +124,74 @@ export function useManageInstance(): ManageInstance { } finally { await stop() } - }, [ - manager, - instance, - blockchain, - account, - dispatch, - router, - handleConnect, - next, - stop, - ]) + }, [dispatch, handleEnsureNetwork, instance, manager, next, router, stop]) + + const noti = useNotification() + + const [crn, setCRN] = useState() + + useEffect(() => { + async function load() { + try { + if (!nodeManager) throw new Error() + if (!instance) throw new Error() + if (instance.payment?.type !== PaymentType.superfluid) throw new Error() + + const { receiver } = instance.payment || {} + if (!receiver) throw new Error() + + const node = await nodeManager.getCRNByStreamRewardAddress(receiver) + setCRN(node) + } catch { + setCRN(undefined) + } + } + load() + }, [instance, nodeManager]) + + const handleRetryAllocation = useCallback(async () => { + if (!manager) throw Err.ConnectYourWallet + if (!instance) throw Err.InstanceNotFound + + try { + await handleEnsureNetwork() + if (!crn) throw Err.ConnectYourPaymentWallet + + await manager.notifyCRNExecution(crn, instance.id) + } catch (e) { + noti?.add({ + variant: 'error', + title: 'Error', + text: (e as Error)?.message, + }) + } + }, [crn, handleEnsureNetwork, instance, manager, noti]) + + const nodeDetails = useMemo(() => { + if (status?.node) { + return { + name: status.node.node_id, + url: status.node.url, + } + } + if (crn) { + return { + name: crn.name || crn.hash, + url: crn.address || '', + } + } + }, [crn, status?.node]) return { instance, status, + mappedKeys, + crn, + nodeDetails, + handleRetryAllocation, handleCopyHash, handleCopyConnect, handleCopyIpv6, handleDelete, - mappedKeys, } }