diff --git a/package.json b/package.json index ed9349c..4fe0d84 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,11 @@ "web" ], "dependencies": { - "concurrently": "^7.2.1" + "concurrently": "^7.2.1", + "path": "^0.12.7" }, - "version": "0.1.292" + "version": "0.1.292", + "devDependencies": { + "url-loader": "^4.1.1" + } } diff --git a/web/craco.config.js b/web/craco.config.js index 461fd75..e0b9a99 100644 --- a/web/craco.config.js +++ b/web/craco.config.js @@ -1,12 +1,12 @@ -const webpack = require("webpack"); -const path = require("path"); -const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); +const webpack = require('webpack'); +const path = require('path'); +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); module.exports = { webpack: { plugins: [ new webpack.ProvidePlugin({ - Buffer: ["buffer", "Buffer"], + Buffer: ['buffer', 'Buffer'], }), new MonacoWebpackPlugin({ languages: ['yaml'], @@ -20,20 +20,20 @@ module.exports = { }, }, ], - }) + }), ], configure: { externals: { - "node:crypto": "crypto", + 'node:crypto': 'crypto', }, resolve: { alias: { - perf_hooks: path.resolve(__dirname, "src/perf_hooks.ts"), - fetch: path.resolve(__dirname, "src/fetch.ts") + perf_hooks: path.resolve(__dirname, 'src/perf_hooks.ts'), + fetch: path.resolve(__dirname, 'src/fetch.ts'), }, - extensions: [".tsx", ".ts", ".js"], + extensions: ['.tsx', '.ts', '.js', '.jsx', '.json', '.png', '.jpg', '.jpeg', '.gif'], fallback: { - buffer: require.resolve("buffer"), + buffer: require.resolve('buffer'), crypto: false, events: false, path: false, @@ -45,9 +45,8 @@ module.exports = { ({ module, details }) => { // Here we check if warnings are coming from node_modules and are type od source-map // Than we remove it from console because we don't have any impact on those warnings - // All other warnings that are coming from App wil yield to dev console - return module?.resource?.includes("node_modules") && - details?.includes("source-map-loader") + // All other warnings that are coming from App will yield to the dev console + return module?.resource?.includes('node_modules') && details?.includes('source-map-loader'); }, ], }, diff --git a/web/package.json b/web/package.json index 89960b3..ab4975c 100644 --- a/web/package.json +++ b/web/package.json @@ -14,7 +14,7 @@ "@akashnetwork/akashjs": "^0.4.5", "@cosmjs/launchpad": "^0.27.1", "@cosmjs/proto-signing": "0.25.4", - "@craco/craco": "^6.4.5", + "@craco/craco": "^6.4.4", "@emeraldpay/hashicon-react": "^0.5.2", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", diff --git a/web/src/App.tsx b/web/src/App.tsx index 909c907..8d18408 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -21,12 +21,13 @@ const MyDeployments = lazy(() => import('./pages/MyDeployments')); const UpdateDeployment = lazy(() => import('./pages/UpdateDeployment')); const CustomApp = lazy(() => import('./pages/CustomApp')); const Provider = lazy(() => import('./pages/Provider')); +const Landing = lazy(() => import('./pages/Landing')); const Welcome = () => { const navigate = useNavigate(); useEffect(() => { - navigate('/landing/node-deployment'); + navigate('/landing'); }, []); return <>; @@ -44,8 +45,9 @@ const AppRouter = () => {
+ } /> } /> - }/> + } /> } /> } /> @@ -117,11 +119,13 @@ export default function App() { return ( - - - - }> + + + + } + > diff --git a/web/src/Landing-Metadata/landing.ts b/web/src/Landing-Metadata/landing.ts new file mode 100644 index 0000000..bfcd52b --- /dev/null +++ b/web/src/Landing-Metadata/landing.ts @@ -0,0 +1,109 @@ +import img1 from './landingIcons/first_img.png'; +import img2 from './landingIcons/www.png'; +import img3 from './landingIcons/chip.png'; +import img33 from './landingIcons/code.png'; +import img4 from './landingIcons/last_guide.png'; +import img5 from './landingIcons/sdl_2.png'; +import img6 from './landingIcons/sdl_22.png'; + +interface Tile { + title: string; + description: string; + image: string; + buttonText: string; + route: string; + icon: string; + buttonEnabled: boolean; + buttonClass?: string; +} + +interface CategoryTiles { + introText: string; + tiles: Tile[]; +} + +interface Metadata { + version: string; + categoriesTiles: CategoryTiles; + sdlGuideTiles: { + introText: string; + introDescription: string; + tiles: { + step: string; + text: string; + image: string; + }[]; + }; +} + +export const metadata: Metadata = { + version: '0.0.1', + categoriesTiles: { + introText: 'What would you like to do today?', + tiles: [ + { + title: 'Deploy a Blockchain Node', + description: + 'Easy and low cost hosting for your blockchain nodes (RPC servers, Validators and more)', + buttonText: 'Choose a Template', + route: '/landing/node-deployment', + icon: 'xrayView', + image: img1, + buttonEnabled: true, + }, + { + title: 'Host a Website or Web Service', + description: + 'Low cost, decentralized equivalents of the services provided by mainstream cloud providers. Host websites, blogsites, databases and more.', + buttonText: 'Coming Soon', + route: '', + icon: 'www', + image: img2, + buttonEnabled: true, + buttonClass: 'coming-soon-btn-2', + }, + { + title: 'Deploy an AI/ ML Model', + description: + 'Popular AI & ML models, deployed in just a few clicks. Includes Stable Diffusion, GPT4All, Alpaca and more.', + buttonText: 'Coming Soon', + route: '', + icon: 'electronicsChip', + image: img3, + buttonEnabled: true, + }, + { + title: 'Custom Application', + description: + 'Define your unique deployment requirements and preferences with SDL and deploy with ease on the flexible and reliable Akash network.', + buttonText: 'Import SDL', + route: '', + icon: 'electronicsChip', + image: img33, + buttonEnabled: true, + }, + ], + }, + sdlGuideTiles: { + introText: 'How it works?', + introDescription: + 'There are 3 main steps to deploying on Akash. Check out our detailed help for more.', + tiles: [ + { + step: '01', + text: 'Start with a template or your own custom application (SDL)', + image: img6, + }, + { + step: '02', + text: 'Choose a provider based on your preferences and desired price', + image: img5, + }, + { + step: '03', + text: 'View & manage your deployed application', + image: img4, + }, + ], + }, +}; diff --git a/web/src/Landing-Metadata/landingIcons/chip.png b/web/src/Landing-Metadata/landingIcons/chip.png new file mode 100644 index 0000000..8286a7e Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/chip.png differ diff --git a/web/src/Landing-Metadata/landingIcons/code.png b/web/src/Landing-Metadata/landingIcons/code.png new file mode 100644 index 0000000..0db22e5 Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/code.png differ diff --git a/web/src/Landing-Metadata/landingIcons/first_img.png b/web/src/Landing-Metadata/landingIcons/first_img.png new file mode 100644 index 0000000..1783629 Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/first_img.png differ diff --git a/web/src/Landing-Metadata/landingIcons/last_guide.png b/web/src/Landing-Metadata/landingIcons/last_guide.png new file mode 100644 index 0000000..576dcdb Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/last_guide.png differ diff --git a/web/src/Landing-Metadata/landingIcons/sdl_2.png b/web/src/Landing-Metadata/landingIcons/sdl_2.png new file mode 100644 index 0000000..6ca6df1 Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/sdl_2.png differ diff --git a/web/src/Landing-Metadata/landingIcons/sdl_22.png b/web/src/Landing-Metadata/landingIcons/sdl_22.png new file mode 100644 index 0000000..5ae1855 Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/sdl_22.png differ diff --git a/web/src/Landing-Metadata/landingIcons/www.png b/web/src/Landing-Metadata/landingIcons/www.png new file mode 100644 index 0000000..68989be Binary files /dev/null and b/web/src/Landing-Metadata/landingIcons/www.png differ diff --git a/web/src/components/Deployment/index.tsx b/web/src/components/Deployment/index.tsx index 2bf1dd5..fc38374 100644 --- a/web/src/components/Deployment/index.tsx +++ b/web/src/components/Deployment/index.tsx @@ -24,7 +24,7 @@ import { getRpcNode } from '../../hooks/useRpcNode'; import { QueryDeploymentResponse as Beta3Deployment } from '@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta3/query'; import { QueryDeploymentResponse as Beta2Deployment } from '@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta2/query'; -import DeploymentActionButton from './DeploymentActionButton'; +// import DeploymentActionButton from './DeploymentActionButton'; const Deployment: React.FC = () => { const { dseq } = useParams(); @@ -271,8 +271,8 @@ const Deployment: React.FC = () => { )} {deployment?.deployment && !deploymentIncomplete && ( - = () => { aria-controls="menu-appbar" aria-haspopup="true" startIcon={} - > + Update Deployment - + - = () => { aria-controls="menu-appbar" aria-haspopup="true" startIcon={} - > Update Deployment - + = () => { color="secondary" aria-label="re-deploy" sx={{ - justifyContent: 'left' + justifyContent: 'left', }} startIcon={} > diff --git a/web/src/components/DeploymentStepper/index.tsx b/web/src/components/DeploymentStepper/index.tsx index 7444dc5..62d7559 100644 --- a/web/src/components/DeploymentStepper/index.tsx +++ b/web/src/components/DeploymentStepper/index.tsx @@ -55,7 +55,8 @@ const DeploymentStepper: React.FC = () => { const [errorMessage, setErrorMessage] = React.useState(); const [myDeployments, setMyDeployments] = useRecoilState(myDeploymentsAtom); const [, setDeploymentRefresh] = useRecoilState(deploymentDataStale); - const { mutate: mxCreateDeployment, isLoading: deploymentProgressVisible } = useMutation(createDeployment); + const { mutate: mxCreateDeployment, isLoading: deploymentProgressVisible } = + useMutation(createDeployment); const { mutate: mxCreateLease, isLoading: leaseProgressVisible } = useMutation(createLease); const { mutate: mxSendManifest, isLoading: manifestSending } = useMutation(sendManifest); @@ -115,26 +116,29 @@ const DeploymentStepper: React.FC = () => { const _sdl = sdl ? sdl : cachedDetails.sdl; if (lease) { - mxSendManifest({ address: keplr.accounts[0].address, lease, sdl: _sdl}, { - onSuccess: (result) => { + mxSendManifest( + { address: keplr.accounts[0].address, lease, sdl: _sdl }, + { + onSuccess: (result) => { if (result) { logging.success('Manifest sending: successful'); setDeploymentRefresh(true); navigate(`/my-deployments/${dseq}`); } }, - onError: (error) => { - logging.log(`Failed to send manifest: ${error}`); - }, - onSettled: () => { - navigate(`/my-deployments/${dseq}`); + onError: (error) => { + logging.log(`Failed to send manifest: ${error}`); + }, + onSettled: () => { + navigate(`/my-deployments/${dseq}`); + }, } - }); + ); } }, onError: (error) => { logging.log(`Failed to create lease: ${error}`); - } + }, }); }; @@ -185,7 +189,8 @@ const DeploymentStepper: React.FC = () => { setCardMessage('Creating deployment'); try { - const result = mxCreateDeployment({ sdl: value.sdl, depositor: value.depositor }, + const result = mxCreateDeployment( + { sdl: value.sdl, depositor: value.depositor }, { onSuccess: async (result) => { if (result && result.deploymentId) { @@ -202,7 +207,7 @@ const DeploymentStepper: React.FC = () => { // head to the bid selection page navigate(`/configure-deployment/${result.deploymentId.dseq}`); } - } + }, } ); } catch (error) { @@ -251,43 +256,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} - /> - )} - {activeStep.currentCard === 1 && folderName && ( - - )} - {activeStep.currentCard === 2 && folderName && templateId && ( - handlePreflightCheck(intent, values.sdl)} - /> - )} - {activeStep.currentCard === 3 && } - {activeStep.currentCard === 4 && deploymentId && ( - }> - acceptBid(bidId)} + + {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)} + /> + + )} + + )} ); }} diff --git a/web/src/components/MonacoYamlEditor/index.tsx b/web/src/components/MonacoYamlEditor/index.tsx index 238a24c..e61d3b5 100644 --- a/web/src/components/MonacoYamlEditor/index.tsx +++ b/web/src/components/MonacoYamlEditor/index.tsx @@ -18,6 +18,7 @@ interface MonacoYamlEditorProps { closeReviewModal: () => void; onSaveButtonClick: (value: SDLSpec) => void; disabled: boolean; + onSave: (sdl: any) => void; // Add onSave prop } export const MonacoYamlEditor: React.FC = ({ diff --git a/web/src/components/SdlConfiguration/SdlConfiguration.tsx b/web/src/components/SdlConfiguration/SdlConfiguration.tsx index 1ae7add..6ae67cd 100644 --- a/web/src/components/SdlConfiguration/SdlConfiguration.tsx +++ b/web/src/components/SdlConfiguration/SdlConfiguration.tsx @@ -37,6 +37,7 @@ interface SdlConfigurationProps { configurationType: SdlConfigurationType; progressVisible?: boolean; cardMessage?: string | undefined; + onSave: (sdl: any) => void; } export const SdlConfiguration: React.FC = ({ @@ -47,6 +48,7 @@ export const SdlConfiguration: React.FC = ({ configurationType, progressVisible, cardMessage, + onSave, }) => { const forbidEditing = configurationType === SdlConfigurationType.Update; @@ -200,6 +202,7 @@ export const SdlConfiguration: React.FC = ({ reviewSdl={reviewSdl} closeReviewModal={closeReviewModal} disabled={forbidEditing} + onSave={onSave} // Pass the onSave prop /> diff --git a/web/src/components/SdlConfiguration/SdllEditor.tsx b/web/src/components/SdlConfiguration/SdllEditor.tsx index a7f8bef..6178a35 100644 --- a/web/src/components/SdlConfiguration/SdllEditor.tsx +++ b/web/src/components/SdlConfiguration/SdllEditor.tsx @@ -9,6 +9,7 @@ interface SdlEditorProps { closeReviewModal: () => void; disabled?: boolean; callback?: (sdl: any) => void; + onSave: (sdl: any) => void; } export const SdlEditor: React.FC = ({ @@ -16,6 +17,7 @@ export const SdlEditor: React.FC = ({ closeReviewModal, disabled, callback, + onSave, // Add the onSave prop }) => { return ( @@ -35,8 +37,13 @@ export const SdlEditor: React.FC = ({ if (callback) { return callback(value); } + + if (onSave) { + return onSave(value); + } form.setFieldValue(field.name, value); }} + onSave={onSave} /> ); }} diff --git a/web/src/components/TileCard/TileCard.tsx b/web/src/components/TileCard/TileCard.tsx new file mode 100644 index 0000000..2e6d376 --- /dev/null +++ b/web/src/components/TileCard/TileCard.tsx @@ -0,0 +1,233 @@ +/* eslint-disable quotes */ +import React, { Suspense, useState, useCallback } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { SdlEditor } from '../../components/SdlConfiguration/SdllEditor'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { Box, Button, Card, CardActions, CardContent, Typography } from '@mui/material'; +import styled from '@emotion/styled'; +import { Formik } from 'formik'; +import { + deploymentDataStale, + deploymentSdl, + keplrState, + myDeployments as myDeploymentsAtom, +} from '../../recoil/atoms'; +interface Props { + item: { + route: string; + title: string; + image: string; + description: string; + buttonText: string; + buttonClass?: string; + }; +} + +import { initialValues, InitialValuesProps, SDLSpec } from '../SdlConfiguration/settings'; +import { myDeploymentFormat } from '../../_helpers/my-deployment-utils'; +import { Deployment } from '@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta2/deployment'; +import { useMutation } from 'react-query'; +import { createDeployment, createLease, sendManifest } from '../../api/mutations'; + +const steps = ['Featured Apps', 'Select', 'Configure', 'Review', 'Deploy']; + +export interface DeploymentStepperProps { + dseq?: string; + leaseId?: string; +} + +function TileCard(props: Props) { + const { title, image, description, buttonText, buttonClass } = props.item; + const keplr = useRecoilValue(keplrState); + const navigate = useNavigate(); + const [deploymentId, setDeploymentId] = React.useState<{ owner: string; dseq: string }>(); + const { dseq } = useParams(); + const [sdl, setSdl] = useRecoilState(deploymentSdl); + const [cardMessage, setCardMessage] = useState(''); + const [activeStep, setActiveStep] = useState({ currentCard: 0 }); + + const [open, setOpen] = React.useState(false); + const [errorTitle, setErrorTitle] = React.useState(); + const [errorMessage, setErrorMessage] = React.useState(); + const [myDeployments, setMyDeployments] = useRecoilState(myDeploymentsAtom); + const [setDeploymentRefresh] = useRecoilState(deploymentDataStale); + const { mutate: mxCreateDeployment, isLoading: deploymentProgressVisible } = + useMutation(createDeployment); + + const bull = ( + + • + + ); + + React.useEffect(() => { + if (dseq) { + setDeploymentId({ + owner: keplr.accounts[0].address, + dseq, + }); + setActiveStep({ currentCard: 4 }); + return; + } + }, [dseq, keplr]); + + const handleSdlEditorSave = (sdl: any) => { + navigate('/new-deployment/custom-sdl', { state: { sdl: sdl } }); + }; + + const handleDeployment = (key: string, deployment: any) => { + const newDeployments: { [key: string]: Deployment } = { ...myDeployments }; + newDeployments[key] = deployment; + setMyDeployments(newDeployments); + }; + + const [reviewSdl, setReviewSdl] = useState(false); + const closeReviewModal = useCallback(() => setReviewSdl(false), []); + + const handleImportSDL = () => { + setReviewSdl(true); + }; + + // TODO: this should be changed to use the logging system, and not throw + // additional exceptions. + const handleError = async (maybeError: unknown, method: string) => { + const error = + maybeError && Object.prototype.hasOwnProperty.call(maybeError, 'message') + ? (maybeError as Error) + : { message: 'Unknown error' }; + + let title = 'Error'; + let message = 'An error occurred while sending your request.'; + if (method === 'acceptBid') { + title = 'Error Select Provider'; + message = 'An error occurred while selecting a provider.'; + } + if (method === 'createDeployment') { + title = 'Error Create Deployment'; + message = 'An error occurred while trying to deploy.'; + if (error.message.includes('Query failed with (6)')) { + message = 'There was an RPC error. This may happen during upgrades to the Akash Network.'; + } + } + setErrorTitle(title); + setErrorMessage(message); + setCardMessage(''); + setOpen(true); + throw new Error(`${method}: ${error.message}`); + }; + + return ( + { + // the onSubmit method is called from the component PreflightCheck. + setCardMessage('Creating deployment'); + + 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 item by dseq (deprecate ?) + localStorage.setItem(_deployment.key, JSON.stringify(_deployment.data)); + + // head to the bid selection page + navigate(`/configure-deployment/${result.deploymentId.dseq}`); + } + }, + } + ); + } catch (error) { + await handleError(error, 'createDeployment'); + } + }} + > + {({ setFieldValue, values }) => ( + <> +
+
+
+
+ +

{title}

+
+
+

{description}

+ {buttonText === 'Import SDL' && ( +
+ + + +
+ )} + + {buttonText === 'Choose a Template' && ( +
+ + + +
+ )} + + {buttonText === 'Coming Soon' && ( +
+ + + +
+ )} + + +
+
+
+
+ + )} +
+ ); +} + +export default TileCard; + +const TemplateBtn = styled.div` + display: flex; + justify-content: center; + margin-top: 40px; + Button { + width: 100%; + padding: 8px, 16px, 8px, 16px; + } +`; diff --git a/web/src/pages/ConfigureApp.tsx b/web/src/pages/ConfigureApp.tsx index 1dd91c2..85a774f 100644 --- a/web/src/pages/ConfigureApp.tsx +++ b/web/src/pages/ConfigureApp.tsx @@ -37,6 +37,11 @@ export const ConfigureApp: React.FC = ({ keepPreviousData: true, }); + const handleSave = (sdl: any) => { + // Update the form values with the saved SDL + form.setFieldValue('sdl', sdl); + }; + useEffect(() => { // don't override the value if it's already set if (form.values?.sdl?.version) return; @@ -70,6 +75,7 @@ export const ConfigureApp: React.FC = ({ configurationType={SdlConfigurationType.Create} progressVisible={progressVisible} cardMessage={cardMessage} + onSave={handleSave} // Add the onSave prop actionItems={() => (