Skip to content

Commit

Permalink
feat: add allocation retry button on payg instances detail page
Browse files Browse the repository at this point in the history
  • Loading branch information
amalcaraz committed Jun 4, 2024
1 parent 5cc35c3 commit b3ab5f2
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 70 deletions.
27 changes: 20 additions & 7 deletions src/components/pages/dashboard/ManageInstance/cmp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ export default function ManageInstance() {
const {
instance,
status,
mappedKeys,
crn,
nodeDetails,
handleRetryAllocation,
handleCopyHash,
handleCopyConnect,
handleCopyIpv6,
handleDelete,
mappedKeys,
} = useManageInstance()

const theme = useTheme()
Expand All @@ -43,7 +46,7 @@ export default function ManageInstance() {
<>
<section tw="px-0 pt-20 pb-6 md:py-10">
<Container>
<div tw="flex justify-between pb-5">
<div tw="flex justify-between pb-5 flex-wrap gap-4 flex-col md:flex-row">
<div tw="flex items-center">
<Icon name="alien-8bit" tw="mr-4" className="text-main0" />
<div className="tp-body2">{name}</div>
Expand All @@ -69,7 +72,17 @@ export default function ManageInstance() {
)}
</Label>
</div>
<div>
<div tw="flex gap-4 flex-col md:flex-row">
{!status?.vm_ipv6 && crn && (
<Button
kind="functional"
variant="warning"
size="md"
onClick={handleRetryAllocation}
>
Reallocate
</Button>
)}
<Button
kind="functional"
variant="warning"
Expand Down Expand Up @@ -217,7 +230,7 @@ export default function ManageInstance() {
</div>
</div>

{status?.node && (
{nodeDetails && (
<>
<Separator />

Expand All @@ -228,7 +241,7 @@ export default function ManageInstance() {
<div tw="my-5">
<div className="tp-info text-main0">NAME</div>
<div>
<Text>{status.node.node_id}</Text>
<Text>{nodeDetails.name}</Text>
</div>
</div>

Expand All @@ -237,12 +250,12 @@ export default function ManageInstance() {
<div>
<a
className="tp-body1 fs-16"
href={status.node.url}
href={nodeDetails.url}
target="_blank"
referrerPolicy="no-referrer"
>
<IconText iconName="square-up-right">
<Text>{ellipseText(status.node.url, 80)}</Text>
<Text>{ellipseText(nodeDetails.url, 80)}</Text>
</IconText>
</a>
</div>
Expand Down
72 changes: 39 additions & 33 deletions src/domain/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -289,6 +289,7 @@ export class InstanceManager
},
} as InstanceStatus
}

const query = await fetch(
`https://scheduler.api.aleph.sh/api/v0/allocation/${instance.id}`,
)
Expand Down Expand Up @@ -320,6 +321,43 @@ export class InstanceManager
return steps
}

async notifyCRNExecution(
node: CRN,
instanceId: string,
retry = true,
): Promise<void> {
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,
Expand Down Expand Up @@ -442,38 +480,6 @@ export class InstanceManager
})
}

protected async notifyCRNExecution(
node: CRN,
instanceId: string,
): Promise<void> {
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<CheckoutStepType[]> {
Expand Down
124 changes: 94 additions & 30 deletions src/hooks/pages/solutions/manage/useManageInstance.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 {
Expand All @@ -54,6 +59,7 @@ export function useManageInstance(): ManageInstance {
)

const manager = useInstanceManager()
const nodeManager = useNodeManager()
const sshKeyManager = useSSHKeyManager()
const { next, stop } = useCheckoutNotification({})

Expand All @@ -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])
Expand All @@ -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<CRN>()

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,
}
}

0 comments on commit b3ab5f2

Please sign in to comment.