diff --git a/web/.eslintrc.yml b/web/.eslintrc.yml index 90c50f2..0906aff 100644 --- a/web/.eslintrc.yml +++ b/web/.eslintrc.yml @@ -26,5 +26,6 @@ rules: semi: - error - always + typescript-eslint/no-explicit-any: "off" react/react-in-jsx-scope: "off" react/no-unescaped-entities: "off" diff --git a/web/package.json b/web/package.json index 30b50db..56ca008 100644 --- a/web/package.json +++ b/web/package.json @@ -11,7 +11,7 @@ "css": "npx tailwindcss -i ./src/style/app.scss -o ./src/style/app.css --watch" }, "dependencies": { - "@akashnetwork/akashjs": "^0.4.7", + "@akashnetwork/akashjs": "^0.4.11", "@cosmjs/launchpad": "^0.27.1", "@cosmjs/proto-signing": "0.25.4", "@craco/craco": "^6.4.4", @@ -92,4 +92,4 @@ ] }, "typings": "./src/react-app-env.d.ts" -} +} \ No newline at end of file diff --git a/web/src/_helpers/async-for-each.ts b/web/src/_helpers/async-for-each.ts deleted file mode 100644 index e67e35f..0000000 --- a/web/src/_helpers/async-for-each.ts +++ /dev/null @@ -1,13 +0,0 @@ -type asyncFn = (elem: T, index: number, array: T[]) => Promise; - -function noop(result: T) { - return result; -} - -export async function asyncForEach(arr: Array, callback: asyncFn) { - return asyncMap(arr, callback).then(noop); -} - -export async function asyncMap(arr: Array, callback: asyncFn) { - return Promise.all(arr.map(callback)); -} diff --git a/web/src/_helpers/async-utils.ts b/web/src/_helpers/async-utils.ts new file mode 100644 index 0000000..632934c --- /dev/null +++ b/web/src/_helpers/async-utils.ts @@ -0,0 +1,66 @@ +type AsyncFunction = (...params: any[]) => Promise; +type AsyncMapFunction = (elem: T, index: number, array: T[]) => Promise; +type ErrorHandler = (e: any) => void; + +function noop(result: T) { + return result; +} + +export async function asyncForEach(arr: Array, callback: AsyncMapFunction) { + return asyncMap(arr, callback).then(noop); +} + +export async function asyncMap(arr: Array, callback: AsyncMapFunction) { + return Promise.all(arr.map(callback)); +} + +/** + * Wrapper for setTimeout that returns a promise. + * + * @param timeout The delay in milliseconds + * @param action The function to call after the delay + * + * @returns Promise that resolves after the delay + */ +export function schedule(timeout: number, action: TFn) { + return new Promise>((resolve, reject) => { + const fire = () => action().then(resolve, reject); + setTimeout(fire, timeout); + }); +} + +/** + * Returns a function that will accept an error, call the notify function + * with that error, and then schedule a retry using the provided function. + * + * @param fn The function to call after the delay + * @param delay The delay between catch the error and retrying + * @param notify A function to pass the captured error too + * + * @returns A function suitable for use as an error handler + */ +function retryHandler(fn: any, delay: number, notify: ErrorHandler) { + const attempt = () => fn(); + + return (e: any) => { + notify(e); + return schedule(delay, attempt); + }; +} + +/** + * Schedule retries for a function that returns a promise. + * + * @param attempt Function that returns a promise + * @param delays Array of delays between retries + * @param notify Function to call with the error (optional) + * + * @returns New that will only fail if all retries fail + */ +export function retry(attempt: any, delays: number[], notify = noop) { + const addRetry = (promise: Promise, delay: number) => ( + promise.catch(retryHandler(attempt, delay, notify)) + ); + + return delays.reduce(addRetry, attempt()); +} diff --git a/web/src/_helpers/sandbox-chain.ts b/web/src/_helpers/sandbox-chain.ts new file mode 100644 index 0000000..d5c4370 --- /dev/null +++ b/web/src/_helpers/sandbox-chain.ts @@ -0,0 +1,41 @@ +export default { + 'rpc': 'https://rpc.sandbox-01.aksh.pw', + 'rest': 'https://api.sandbox-01.aksh.pw', + 'chainId': 'sandbox-01', + 'chainName': 'Akash Sandbox', + 'chainSymbolImageUrl': 'https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/akashnet/chain.png', + 'stakeCurrency': { + 'coinDenom': 'AKT', + 'coinMinimalDenom': 'uakt', + 'coinDecimals': 6, + 'coinGeckoId': 'akash-network' + }, + 'bip44': { + 'coinType': 118 + }, + 'bech32Config': { + 'bech32PrefixAccAddr': 'akash', + 'bech32PrefixAccPub': 'akashpub', + 'bech32PrefixValAddr': 'akashvaloper', + 'bech32PrefixValPub': 'akashvaloperpub', + 'bech32PrefixConsAddr': 'akashvalcons', + 'bech32PrefixConsPub': 'akashvalconspub' + }, + 'currencies': [ + { + 'coinDenom': 'AKT', + 'coinMinimalDenom': 'uakt', + 'coinDecimals': 6, + 'coinGeckoId': 'akash-network' + } + ], + 'feeCurrencies': [ + { + 'coinDenom': 'AKT', + 'coinMinimalDenom': 'uakt', + 'coinDecimals': 6, + 'coinGeckoId': 'akash-network' + } + ], + 'features': [] +}; \ No newline at end of file diff --git a/web/src/_helpers/chain.ts b/web/src/_helpers/testnet-chain.ts similarity index 100% rename from web/src/_helpers/chain.ts rename to web/src/_helpers/testnet-chain.ts diff --git a/web/src/api/rpc/beta2/deployments.tsx b/web/src/api/rpc/beta2/deployments.tsx index 71a03bd..b632e8b 100644 --- a/web/src/api/rpc/beta2/deployments.tsx +++ b/web/src/api/rpc/beta2/deployments.tsx @@ -41,11 +41,12 @@ import { fetchRpcNodeStatus } from './rpc'; import { LeaseStatus } from '../../../types'; import logging from '../../../logging'; import { getRpcNode } from '../../../hooks/useRpcNode'; +import { retry } from '../../../_helpers/async-utils'; // 5AKT aka 5000000uakt export const defaultInitialDeposit = 5000000; -function getTypeUrl(type: T) { +function getTypeUrl(type: T) { return `/${type.$type}`; } @@ -399,8 +400,6 @@ export async function sendManifest(address: string, lease: Lease, sdl: any) { const providerFetch = mtlsFetch(cert, provider.provider.hostUri); const manifest = Manifest(sdl, 'beta2', true); - console.log(manifest); - let jsonStr = JSON.stringify(manifest); jsonStr = jsonStr.replaceAll('"quantity":{"val', '"size":{"val'); @@ -408,32 +407,24 @@ export async function sendManifest(address: string, lease: Lease, sdl: any) { jsonStr = jsonStr.replaceAll('"readOnly":', '"mount":'); jsonStr = jsonStr.replaceAll('"readOnlyTmp":', '"readOnly":'); - return new Promise((resolve, reject) => { - const attemptSend = (retry: number) => { - return providerFetch(url, { - method: 'PUT', - body: jsonStr, - }).then((result) => { - if (result.ok) { - resolve(result); - return; - } - - if (retry > 0) { - // logging.warn('Sending manifest failed. Retrying...'); - setTimeout(() => attemptSend(retry - 1), 1000); - } else { - // logging.warn('Sending manifest failed.'); - result.text().then(reject); - } - }, (error) => { - logging.error('Error sending manifest to provider. This is likey an issue with the provider.'); - console.error(error); - }); - }; - - attemptSend(3); - }); + const attemptSend = () => { + return providerFetch(url, { + method: 'PUT', + body: jsonStr, + }).then((result) => { + if (result.ok) { + return result; + } + + return Promise.reject(result); + }); + }; + + return retry(attemptSend, [1000, 3000, 5000]) + .catch((error) => { + logging.error('Error sending manifest to provider. This is likely an issue with the provider.'); + console.error(error); + }); } export async function newDeploymentData( diff --git a/web/src/api/rpc/beta3/deployments.tsx b/web/src/api/rpc/beta3/deployments.tsx index e11beba..8237e48 100644 --- a/web/src/api/rpc/beta3/deployments.tsx +++ b/web/src/api/rpc/beta3/deployments.tsx @@ -41,11 +41,12 @@ import { fetchRpcNodeStatus } from './rpc'; import { LeaseStatus } from '../../../types'; import logging from '../../../logging'; import { getRpcNode } from '../../../hooks/useRpcNode'; +import { retry } from '../../../_helpers/async-utils'; // 5AKT aka 5000000uakt export const defaultInitialDeposit = 5000000; -function getTypeUrl(type: T) { +function getTypeUrl(type: T) { return `/${type.$type}`; } @@ -314,6 +315,8 @@ export async function createDeployment( }), }; + console.log(msg); + const tx = await client.signAndBroadcast(account.address, [msg], 'auto', 'Creating the deployment'); return { @@ -400,29 +403,24 @@ export async function sendManifest(address: string, lease: Lease, sdl: any) { const providerFetch = mtlsFetch(cert, provider.provider.hostUri); const jsonStr = ManifestYaml(sdl, 'beta3'); - return new Promise((resolve, reject) => { - const attemptSend = (retry: number) => { - return providerFetch(url, { - method: 'PUT', - body: jsonStr, - }).then((result) => { - if (result.ok) { - resolve(result); - return; - } - - if (retry > 0) { - // logging.warn('Sending manifest failed. Retrying...'); - setTimeout(() => attemptSend(retry - 1), 1000); - } else { - // logging.warn('Sending manifest failed.'); - result.text().then(reject); - } - }); - }; - - attemptSend(3); - }); + const attemptSend = () => { + return providerFetch(url, { + method: 'PUT', + body: jsonStr, + }).then((result) => { + if (result.ok) { + return result; + } + + return Promise.reject(result); + }); + }; + + return retry(attemptSend, [1000, 3000, 5000]) + .catch((error: any) => { + logging.error('Error sending manifest to provider. This is likely an issue with the provider.'); + console.error(error); + }); } export async function newDeploymentData( diff --git a/web/src/components/Deployment/DeploymentActionButton.tsx b/web/src/components/Deployment/DeploymentActionButton.tsx index 11a2b20..f3f4de3 100644 --- a/web/src/components/Deployment/DeploymentActionButton.tsx +++ b/web/src/components/Deployment/DeploymentActionButton.tsx @@ -2,33 +2,36 @@ import React from 'react'; import { Button, Stack, Link, Tooltip, ButtonProps } from '@mui/material'; type DeploymentActionButtonProps = { - tooltipTitle: string; - tooltip: React.ReactNode; - linkTo: string; - children: React.ReactNode; + tooltipTitle: string; + tooltip: React.ReactNode; + linkTo: string; + condition: boolean; + children: React.ReactNode; } & ButtonProps; -const ConditionalTooltip = ({ children, condition, ...rest}: any) => { - return condition - ? {children} - : {children}; +const ConditionalTooltip = ({ children, condition, ...rest }: any) => { + return condition + ? {children} + : {children}; }; const DeploymentActionButton: React.FC = (props) => { - const { - tooltipTitle, - tooltip, - linkTo, - children, - ...rest - } = props; + const { + tooltipTitle, + tooltip, + linkTo, + children, + condition, + ...rest + } = props; - return + return - )} - {deployment?.deployment && !deploymentIncomplete && ( - - } - > - Update Deployment - - - +
Actions
+ {deployment?.deployment && deploymentIncomplete && ( + - - {ReDeployTooltip} -
- } + > + Re-Deploy + + + {ReDeployTooltip} +
+ + Clone Deployment + + + )} + {deployment?.deployment && deployment?.deployment?.state === 1 && ( + setRefresh(true)} > - Clone Deployment - - - )} - {deployment?.deployment && deployment?.deployment?.state === 1 && ( - setRefresh(true)} - > - Delete Deployment - - )} + Delete Deployment + + )} +
Info
diff --git a/web/src/components/DeploymentStepper/index.tsx b/web/src/components/DeploymentStepper/index.tsx index 62d7559..4aa8e1e 100644 --- a/web/src/components/DeploymentStepper/index.tsx +++ b/web/src/components/DeploymentStepper/index.tsx @@ -55,7 +55,7 @@ const DeploymentStepper: React.FC = () => { const [errorMessage, setErrorMessage] = React.useState(); const [myDeployments, setMyDeployments] = useRecoilState(myDeploymentsAtom); const [, setDeploymentRefresh] = useRecoilState(deploymentDataStale); - const { mutate: mxCreateDeployment, isLoading: deploymentProgressVisible } = + const { mutateAsync: mxCreateDeployment, isLoading: deploymentProgressVisible } = useMutation(createDeployment); const { mutate: mxCreateLease, isLoading: leaseProgressVisible } = useMutation(createLease); const { mutate: mxSendManifest, isLoading: manifestSending } = useMutation(sendManifest); @@ -174,7 +174,7 @@ const DeploymentStepper: React.FC = () => { setErrorMessage(message); setCardMessage(''); setOpen(true); - throw new Error(`${method}: ${error.message}`); + // throw new Error(`${method}: ${error.message}`); }; return ( @@ -187,32 +187,24 @@ const DeploymentStepper: React.FC = () => { // it uses the useFormikContext hook. // const { submitForm } = useFormikContext(); setCardMessage('Creating deployment'); + mxCreateDeployment({ sdl: value.sdl, depositor: value.depositor }) + .then(async (result) => { + if (result && result.deploymentId) { + setDeploymentId(result.deploymentId); + setSdl(value.sdl); - try { - const result = mxCreateDeployment( - { sdl: value.sdl, depositor: value.depositor }, - { - onSuccess: async (result) => { - if (result && result.deploymentId) { - setDeploymentId(result.deploymentId); - setSdl(value.sdl); + // set deployment to localStorage object using Atom + const _deployment = await myDeploymentFormat(result, value); + handleDeployment(_deployment.key, JSON.stringify(_deployment.data)); - // set deployment to localStorage object using Atom - const _deployment = await myDeploymentFormat(result, value); - handleDeployment(_deployment.key, JSON.stringify(_deployment.data)); + // set deployment to localStorage item by dseq (deprecate ?) + localStorage.setItem(_deployment.key, JSON.stringify(_deployment.data)); - // set deployment to localStorage item by dseq (deprecate ?) - localStorage.setItem(_deployment.key, JSON.stringify(_deployment.data)); - - // head to the bid selection page - navigate(`/configure-deployment/${result.deploymentId.dseq}`); - } - }, + // head to the bid selection page + navigate(`/configure-deployment/${result.deploymentId.dseq}`); } - ); - } catch (error) { - await handleError(error, 'createDeployment'); - } + }) + .catch((error) => handleError(error, 'createDeployment')); }} > {({ setFieldValue, values }) => { @@ -256,48 +248,48 @@ const DeploymentStepper: React.FC = () => { {activeStep.currentCard === steps.length ? null : !progressVisible && ( - - {activeStep.currentCard === 0 && ( - { - selectFolder(folderName); - }} - callback={(sdl) => - navigate('/new-deployment/custom-sdl', { state: { sdl: sdl } }) - } - setFieldValue={setFieldValue} - onSave={function (sdl: any): void { - throw new Error('Function not implemented.'); - }} - /> - )} - {activeStep.currentCard === 1 && folderName && ( - - )} - {activeStep.currentCard === 2 && folderName && templateId && ( - - handlePreflightCheck(intent, values.sdl) - } + + {activeStep.currentCard === 0 && ( + { + selectFolder(folderName); + }} + callback={(sdl) => + navigate('/new-deployment/custom-sdl', { state: { sdl: sdl } }) + } + setFieldValue={setFieldValue} + onSave={function (sdl: any): void { + throw new Error('Function not implemented.'); + }} + /> + )} + {activeStep.currentCard === 1 && folderName && ( + + )} + {activeStep.currentCard === 2 && folderName && templateId && ( + + handlePreflightCheck(intent, values.sdl) + } + /> + )} + {activeStep.currentCard === 3 && } + {activeStep.currentCard === 4 && deploymentId && ( + }> + acceptBid(bidId)} /> - )} - {activeStep.currentCard === 3 && } - {activeStep.currentCard === 4 && deploymentId && ( - }> - acceptBid(bidId)} - /> - - )} - - )} + + )} + + )} ); }} @@ -308,7 +300,7 @@ const DeploymentStepper: React.FC = () => { title={errorTitle || ''} message={errorMessage || ''} /> - + ); }; diff --git a/web/src/components/MeasurementControl/InputNumbers.tsx b/web/src/components/MeasurementControl/InputNumbers.tsx index 65083ef..7c206af 100644 --- a/web/src/components/MeasurementControl/InputNumbers.tsx +++ b/web/src/components/MeasurementControl/InputNumbers.tsx @@ -33,7 +33,7 @@ export const InputNumber: React.FC = ({ setFieldValue, disable { diff --git a/web/src/components/SdlConfiguration/Gpu.tsx b/web/src/components/SdlConfiguration/Gpu.tsx index 67e2f2a..a942fa0 100644 --- a/web/src/components/SdlConfiguration/Gpu.tsx +++ b/web/src/components/SdlConfiguration/Gpu.tsx @@ -5,6 +5,28 @@ import { AddNewButton, AddNewButtonWrapper, FieldWrapper, PlusSign, SdlSectionWr import { Button, Box, Stack, Typography, List, ListItem, OutlinedInput } from '@mui/material'; import { IconTrash } from '../Icons'; +const GPU_VENDORS = [ + 'nvidia', + 'amd', +]; + +const GPU_MODELS = { + nvidia: [ + 'a100', + 'a30', + 'a40', + 'a6000', + 'a40', + ], + amd: [ + 'mi100', + 'mi50', + 'mi60', + 'mi25', + 'mi8', + ] +}; + type GpuUnitProps = { currentProfile: string; disabled: boolean; @@ -81,11 +103,7 @@ const GpuAttributes: React.FC = ({ currentProfile, disabled }) => return ( - Example filters: - - /vendor/nvidia/model/a6000 (nVidia A6000 only) - /vendor/amd/model/* (any AMD GPU) - + Select the GPU vendors/models that you'd like to use for your deployment. {attributes.map((attribute, index) => ( diff --git a/web/src/components/SdlConfiguration/HttpOptions.tsx b/web/src/components/SdlConfiguration/HttpOptions.tsx index be78dce..2585f9b 100644 --- a/web/src/components/SdlConfiguration/HttpOptions.tsx +++ b/web/src/components/SdlConfiguration/HttpOptions.tsx @@ -88,7 +88,7 @@ export const HttpOptions: React.FC = ({ {httpOptions.map(([key, label, description, defaultValue]) => ( - + {/* label */} diff --git a/web/src/components/SdlConfiguration/Ports.tsx b/web/src/components/SdlConfiguration/Ports.tsx index 8a04d91..d545a46 100644 --- a/web/src/components/SdlConfiguration/Ports.tsx +++ b/web/src/components/SdlConfiguration/Ports.tsx @@ -11,6 +11,7 @@ import { FieldWrapper, Input, SdlSectionWrapper, + TableTitle, VariableWrapper, } from './styling'; @@ -46,10 +47,10 @@ export const Ports: React.FC = ({ serviceName, services, updatePage render={(arrayHelpers: any) => ( - Port - As - Host - Accept + Port + As + Host + Accept {services[serviceName]?.expose?.map((port, index) => ( @@ -173,17 +174,4 @@ export const Ports: React.FC = ({ serviceName, services, updatePage const HostFiledWithButton = styled.div` display: flex; column-gap: 10px; -`; - -const PortTitle = styled.div` - font-weight: 500; - font-size: 14px; - color: #3d4148; - padding-right: 10px; - width: 176.5px; - box-sizing: border-box; - - &:last-child { - width: auto; - } -`; +`; \ No newline at end of file diff --git a/web/src/components/SdlConfiguration/Storage.tsx b/web/src/components/SdlConfiguration/Storage.tsx index c2e57db..b2e9ce9 100644 --- a/web/src/components/SdlConfiguration/Storage.tsx +++ b/web/src/components/SdlConfiguration/Storage.tsx @@ -1,4 +1,4 @@ -import { FormControl, IconButton, MenuItem, Select, Tab, Tabs, Tooltip } from '@mui/material'; +import { Box, FormControl, IconButton, MenuItem, Select, Stack, Tab, Tabs, Tooltip } from '@mui/material'; import { Field, FieldArray } from 'formik'; import { MeasurementControl } from '../MeasurementControl'; import React from 'react'; @@ -13,7 +13,9 @@ import { VariableWrapper, TrashIcon, PlusSign, + TableTitle, } from './styling'; +import { randomInt } from 'crypto'; interface TabPanelProps { children?: React.ReactNode; @@ -21,21 +23,19 @@ interface TabPanelProps { value: number; } -const validateStorage = (value: any) => { - let error; - const strippedValue = value?.slice(0, -2); - if (strippedValue && strippedValue <= 0) { - error = 'Storage must be a positive value greater than zero'; +const validateStorage = (value: string) => { + const size = value.slice(0, -2); + const intValue = parseInt(size, 10); + + if (isNaN(intValue) || intValue <= 0) { + return 'Storage must be a positive value greater than zero'; } - return error; }; -const validateStorageData = (value: any) => { - let error; - if (!value) { - error = 'This value can\'t be blank'; +const validateStorageData = (value: string) => { + if (value === '') { + return 'This value can\'t be blank'; } - return error; }; type StorageProfile = { @@ -78,7 +78,7 @@ export const Storage: React.FC = ({ sx={{ marginBottom: '16px', borderBottom: '1px solid #D1D5DB' }} > - + = ({ /> + {/* Persistent storage */} + + Name + Mount + Size + Type + ( @@ -180,7 +187,14 @@ export const Storage: React.FC = ({ {profiles.compute[currentProfile]?.resources.storage?.map((storage, index) => { return ( storage?.attributes && ( - + = ({ )} - + ) ); })} @@ -293,6 +307,14 @@ export const Storage: React.FC = ({ variant="outlined" size="small" onClick={() => { + const sdl = arrayHelpers.form.values.sdl as SDLSpec; + + if (sdl.services[serviceName]?.params?.storage === undefined) { + sdl.services[serviceName].params = { + storage: {}, + }; + } + arrayHelpers.insert( profiles.compute[currentProfile]?.resources.storage?.length + 1 ?? 0, { @@ -357,4 +379,4 @@ const StorageType = styled.p` font-size: 14px; line-height: 20px; color: #6b7280; -`; +`; \ No newline at end of file diff --git a/web/src/components/SdlConfiguration/styling.tsx b/web/src/components/SdlConfiguration/styling.tsx index b9630fd..e82e684 100644 --- a/web/src/components/SdlConfiguration/styling.tsx +++ b/web/src/components/SdlConfiguration/styling.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; import { css } from '@emotion/react'; -import { Button } from '@mui/material'; +import { Box, Button } from '@mui/material'; import PlusIcon from '../../assets/images/plus-icon.svg'; import Trash from '../../assets/images/icon-trash.svg'; @@ -102,3 +102,10 @@ export const SdlSectionWrapper = styled.div` border-radius: 6px; padding: 20px; `; + +export const TableTitle = styled(Box)` + font-weight: 500; + font-size: 14px; + color: #3d4148; + box-sizing: border-box; +`; diff --git a/web/src/hooks/useDeploymentData.ts b/web/src/hooks/useDeploymentData.ts index 2e18746..549dbb2 100644 --- a/web/src/hooks/useDeploymentData.ts +++ b/web/src/hooks/useDeploymentData.ts @@ -63,7 +63,6 @@ export default function useDeploymentData(owner: string) { const getLeaseStatus = useCallback( (lease: any) => { if (certificate.$type === 'TLS Certificate') { - // console.log('fetching status for lease', lease); return lease && fetchLeaseStatus(lease, rpcEndpoint); } }, @@ -86,57 +85,58 @@ export default function useDeploymentData(owner: string) { // Map it all together, and your mother's brother is named Robert Promise.all( - deploymentsQuery.deployments.map(async (query) => { - let name = ''; - let url = ''; - const lease = getDeploymentLease(query.deployment); - const status = await getLeaseStatus(lease); - const leaseInfo = leaseCalculator( - query?.deployment as any, - query?.escrowAccount as any, - lease as any, - akt?.current_price || 0 - ); - let updatable = 0; - // Doing this cause dseq sometimes comes as plain object and not Long type, - // and if that happen can't crate dseq string - const dseq = new Long( - query.deployment?.deploymentId?.dseq?.low || 0, - query.deployment?.deploymentId?.dseq?.high, - query.deployment?.deploymentId?.dseq?.unsigned - )?.toString(); - - const appCache = dseq ? localStorage.getItem(dseq) : null; - - const application = appCache ? JSON.parse(appCache) : null; - - if (application !== null && application.name !== '') { - name = application.name; - } else { - name = uniqueName(keplr?.accounts[0]?.address, dseq); - } - if (status && status.services) { - url = Object.values(status.services) - .map((service) => (service as any).uris[0]) - .join(', '); - } - if (application !== null && query.deployment?.state === 1 && application.sdl !== undefined) { - updatable = 1; - } else { - updatable = 0; - } - // Table row object - return { - name, - dseq, - url, - lease: leaseInfo - ? `Time Left: ${leaseInfo ? leaseInfo.timeLeft : 'N/A'}` - : 'Time Left: 0 days', - status: query.deployment?.state || 0, - updatable: updatable, - }; - }) + deploymentsQuery.deployments + .map(async (query) => { + let name = ''; + let url = ''; + const lease = getDeploymentLease(query.deployment); + const status = await getLeaseStatus(lease); + const leaseInfo = leaseCalculator( + query?.deployment as any, + query?.escrowAccount as any, + lease as any, + akt?.current_price || 0 + ); + let updatable = 0; + // Doing this cause dseq sometimes comes as plain object and not Long type, + // and if that happen can't crate dseq string + const dseq = new Long( + query.deployment?.deploymentId?.dseq?.low || 0, + query.deployment?.deploymentId?.dseq?.high, + query.deployment?.deploymentId?.dseq?.unsigned + )?.toString(); + + const appCache = dseq ? localStorage.getItem(dseq) : null; + + const application = appCache ? JSON.parse(appCache) : null; + + if (application !== null && application.name !== '') { + name = application.name; + } else { + name = uniqueName(keplr?.accounts[0]?.address, dseq); + } + if (status && status.services) { + url = Object.values(status.services) + .map((service) => (service as any).uris[0]) + .join(', '); + } + if (application !== null && query.deployment?.state === 1 && application.sdl !== undefined) { + updatable = 1; + } else { + updatable = 0; + } + // Table row object + return { + name, + dseq, + url, + lease: leaseInfo + ? `Time Left: ${leaseInfo ? leaseInfo.timeLeft : 'N/A'}` + : 'Time Left: 0 days', + status: query.deployment?.state || 0, + updatable: updatable, + }; + }) ).then(setDeploymentsData); }, [status, deploymentsQuery, getDeploymentLease, getLeaseStatus]); diff --git a/web/src/hooks/useRpcNode.ts b/web/src/hooks/useRpcNode.ts index 46c7937..3f7bd63 100644 --- a/web/src/hooks/useRpcNode.ts +++ b/web/src/hooks/useRpcNode.ts @@ -14,6 +14,11 @@ export const testnetRpcSettings = { networkType: 'testnet', }; +export const sandboxRpcSettings = { + rpcNode: 'https://rpc.sandbox-01.aksh.pw/', + chainId: 'akashnet-2', + networkType: 'testnet', +}; export type RpcSettings = typeof defaultRpcSettings; diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 5beb83b..0781406 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, SyntheticEvent, useCallback, useMemo } from 'react'; +import React, { ChangeEvent, useCallback, useMemo } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { Alert, @@ -32,14 +32,13 @@ import { useMutation, useQuery } from 'react-query'; import { useWallet } from '../hooks/useWallet'; import { queryCertificates } from '../api/queries'; import { QueryCertificatesResponse } from '@akashnetwork/akashjs/build/protobuf/akash/cert/v1beta2/query'; -import { RpcSettings, defaultRpcSettings, testnetRpcSettings, useRpcNode } from '../hooks/useRpcNode'; +import { RpcSettings, defaultRpcSettings, sandboxRpcSettings, testnetRpcSettings, useRpcNode } from '../hooks/useRpcNode'; import { Input } from '../components/SdlConfiguration/styling'; import { isRpcNodeStatus } from '../api/rpc/beta2/rpc'; -import chainInfo from '../_helpers/chain'; +import testnetChainInfo from '../_helpers/testnet-chain'; +import sandboxChainInfo from '../_helpers/sandbox-chain'; import { createCertificate, revokeCertificate } from '../api/mutations'; -import { set } from 'lodash'; import logging from '../logging'; -import { getRpc } from '@akashnetwork/akashjs/build/rpc'; type SortableCertificate = { available: boolean; @@ -90,6 +89,10 @@ const defaultRpcNodes = [ rpcNode: testnetRpcSettings.rpcNode, name: 'Testnet', }, + { + rpcNode: sandboxRpcSettings.rpcNode, + name: 'Sandbox' + }, { rpcNode: '', name: 'Custom', @@ -100,9 +103,16 @@ const isCustomRpcNode = (rpcNode: string) => { return !defaultRpcNodes.find((node) => node.rpcNode === rpcNode); }; -const AddCustomChainButton: React.FC = () => { +const AddCustomChainButton: React.FC<{ chainId: string }> = ({ chainId }) => { + const chainConfigs = new Map([ + ['testnet-02', testnetChainInfo], + ['sandbox-01', sandboxChainInfo], + ]); + const addCustomChain = () => { - if (window && window.keplr && window.keplr.experimentalSuggestChain) { + const chainInfo = chainConfigs.get(chainId); + + if (window && window.keplr && window.keplr.experimentalSuggestChain && chainInfo) { window.keplr.experimentalSuggestChain(chainInfo); } }; @@ -602,9 +612,9 @@ const Settings: React.FC> = () => { ) : ( <> - {candidateRpcSettings?.networkType == 'testnet' &&
- -
} + {candidateRpcSettings && candidateRpcSettings.networkType !== 'mainnet' &&
+ +
}