From 6dfaf3bc28ab741c680aece89268065c69853477 Mon Sep 17 00:00:00 2001 From: Jigar Patel <97186961+jigar-arc10@users.noreply.github.com> Date: Tue, 3 Dec 2024 03:09:12 +0530 Subject: [PATCH] feat(provider): provider attributes and actions (#451) * added server-access and server-form files * added wallet import page - wip, fixed issue with file upload in server access forms * added become-provider steps ui * server access step added with state support * added provider config and provider attribute screen * added provider process with hoc to prevent access to pages * added progress for becoming prvider for final stage * Code clean up and added navigation logic to homecontainer * package lock updated * more cleanup and remove general warnings * removed unused npm package * fix minor error on api * Added dashboard and actions page * change status api endpoint * added stat line and pie charts * Added console apis to get dashboard data and show appropriate details * fixed actions and changed home component * token varification and refresh token fix * changed wallet connect from wallet status to wallet provider * fixed issue in loading provider status * fixed home loading issue * fixed refresh token, added disabled menu items * fixed build process * feat(provider): added sentry and docker * fix(provider): fixed wallet switching and getting status * feat(provider): reduced number of events in dashboard * feat(provider): added docker compose changes for provider-console * feat(provider): added deployments and deployment detail page * fix(provider): change hours to seconds for calculation purpose) * feat(provider): added auth for deployments and deployment details page * feat(provider): added env and removed settingsprovider * fix(provider): fix lint errors and removed console.logs * fix(provider): become-provider looped, fixed it * fix(provider): router and reset process fixed * fix(provider): removed Get Started button for now * fix(provider): removed unused import in nav * fix(provider): change functions to react fc component * fix(provider): fix lint issues * fix(provider): change functions to react fc component * fix(provider): added docker build and fix build related issues * feat(provider): control machine edit, add from sidebar * feat(provider): added attributes screen * fix(provider): control machine auto connect on page load * fix(provider): fix loading not showing while connecting provider control machine * fix(provider): close drawer on successfull connection * feat(provider): change favicon to akash favicon * feat(provider): provider add, edit and remove and show acitons list page * fix(provider): fix url when provider process finish * fix(provider): merge issues with main * fix(provider): changed to useQuery and fix lint * chore(provider): removed comment --- .../become-provider/ProviderAttributes.tsx | 83 ++++++++++++++----- .../become-provider/WalletImport.tsx | 2 +- .../src/components/layout/Sidebar.tsx | 12 +-- .../shared/ProviderActionDetails.tsx | 5 +- .../components/shared/ProviderActionList.tsx | 4 +- .../[id]}/index.tsx | 22 +++-- .../src/pages/actions/index.tsx | 28 +++++++ .../src/pages/attributes/index.tsx | 33 ++++++++ .../src/store/providerProcessStore.ts | 2 +- .../provider-console/src/utils/sanityUtils.ts | 15 ++++ apps/provider-console/src/utils/urlUtils.ts | 2 + 11 files changed, 169 insertions(+), 39 deletions(-) rename apps/provider-console/src/pages/{action-details => actions/[id]}/index.tsx (56%) create mode 100644 apps/provider-console/src/pages/actions/index.tsx create mode 100644 apps/provider-console/src/pages/attributes/index.tsx create mode 100644 apps/provider-console/src/utils/sanityUtils.ts diff --git a/apps/provider-console/src/components/become-provider/ProviderAttributes.tsx b/apps/provider-console/src/components/become-provider/ProviderAttributes.tsx index b0251c7aa..49277c9f3 100644 --- a/apps/provider-console/src/components/become-provider/ProviderAttributes.tsx +++ b/apps/provider-console/src/components/become-provider/ProviderAttributes.tsx @@ -3,6 +3,9 @@ import React from "react"; import { Controller, SubmitHandler, useFieldArray, useForm } from "react-hook-form"; import { + Alert, + AlertDescription, + AlertTitle, Button, Form, FormControl, @@ -14,21 +17,25 @@ import { SelectContent, SelectItem, SelectTrigger, - Separator -} from "@akashnetwork/ui/components"; + Separator } from "@akashnetwork/ui/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { Plus, Trash } from "iconoir-react"; import { useAtom } from "jotai"; import { z } from "zod"; -import providerProcessStore from "@src/store/providerProcessStore"; +import { useControlMachine } from "@src/context/ControlMachineProvider"; +import providerProcessStore, { ProviderAttribute } from "@src/store/providerProcessStore"; +import restClient from "@src/utils/restClient"; +import { sanitizeMachineAccess } from "@src/utils/sanityUtils"; import { providerAttributesFormValuesSchema } from "../../types/providerAttributes"; import { ResetProviderForm } from "./ResetProviderProcess"; const attributeKeys = Object.keys(providerAttributesFormValuesSchema.shape); interface ProviderAttributesProps { - onComplete: () => void; + existingAttributes?: ProviderAttribute[]; + editMode?: boolean; + onComplete?: () => void; } const providerFormSchema = z.object({ @@ -43,40 +50,68 @@ const providerFormSchema = z.object({ type ProviderFormValues = z.infer; -export const ProviderAttributes: React.FC = ({ onComplete }) => { +export const ProviderAttributes: React.FunctionComponent = ({ onComplete, existingAttributes, editMode }) => { const [providerPricing, setProviderPricing] = useAtom(providerProcessStore.providerProcessAtom); const form = useForm({ resolver: zodResolver(providerFormSchema), defaultValues: { - attributes: [{ key: "", value: "", customKey: "" }] + attributes: existingAttributes + ? existingAttributes.map(attr => ({ + key: attributeKeys.includes(attr.key) ? attr.key : "unknown-attributes", + value: attr.value, + customKey: attributeKeys.includes(attr.key) ? "" : attr.key + })) + : [{ key: "", value: "", customKey: "" }] } }); const { control } = form; - const { fields, append, remove } = useFieldArray({ control, name: "attributes" }); + const { activeControlMachine } = useControlMachine(); + + const [showSuccess, setShowSuccess] = React.useState(false); + const updateProviderAttributesAndProceed: SubmitHandler = async data => { - const updatedProviderPricing = { - ...providerPricing, - attributes: data.attributes.map(attr => ({ - ...attr, - customKey: attr.customKey || "" - })) - }; - setProviderPricing(updatedProviderPricing); - onComplete(); + if (!editMode) { + const updatedProviderPricing = { + ...providerPricing, + attributes: data.attributes.map(attr => ({ + key: attr.key === "unknown-attributes" ? attr.customKey || "" : attr.key || "", + value: attr.value + })) + }; + setProviderPricing(updatedProviderPricing); + onComplete && onComplete(); + } else { + const attributes = data.attributes.map(attr => ({ + key: attr.key === "unknown-attributes" ? attr.customKey || "" : attr.key || "", + value: attr.value + })); + const request = { + control_machine: sanitizeMachineAccess(activeControlMachine), + attributes + }; + + const response = await restClient.post(`/update-provider-attributes`, request); + if (response) { + setShowSuccess(true); + setTimeout(() => setShowSuccess(false), 10000); + } + } }; return (
-

Provider Attributes

-

Please enter your provider attributes.

+

{existingAttributes ? "Edit Provider Attributes" : "Provider Attributes"}

+

+ {existingAttributes ? "Please update your provider attributes." : "Please enter your provider attributes."} +

@@ -154,16 +189,20 @@ export const ProviderAttributes: React.FC = ({ onComple
-
- -
+
{!editMode && }
- +
+ {showSuccess && ( + + Success + Provider attributes updated successfully + + )}
); diff --git a/apps/provider-console/src/components/become-provider/WalletImport.tsx b/apps/provider-console/src/components/become-provider/WalletImport.tsx index c31905623..96d72b418 100644 --- a/apps/provider-console/src/components/become-provider/WalletImport.tsx +++ b/apps/provider-console/src/components/become-provider/WalletImport.tsx @@ -139,7 +139,7 @@ export const WalletImport: React.FC = ({ onComplete }) => { }; await setControlMachine(machineWithAddress); resetProviderProcess(); - router.push(`/action?id=${response.action_id}`); + router.push(`/actions/${response.action_id}`); } else { throw new Error("Invalid response from server"); } diff --git a/apps/provider-console/src/components/layout/Sidebar.tsx b/apps/provider-console/src/components/layout/Sidebar.tsx index c476aae7f..2035d8b98 100644 --- a/apps/provider-console/src/components/layout/Sidebar.tsx +++ b/apps/provider-console/src/components/layout/Sidebar.tsx @@ -57,9 +57,9 @@ export const Sidebar: React.FC = ({ isMobileOpen, handleDrawerToggle, isN { title: "Actions", icon: props => , - url: "#", - activeRoutes: ["#"], - disabled: true + url: UrlService.actions(), + activeRoutes: [UrlService.actions()], + disabled: false }, { title: "Pricing", @@ -71,9 +71,9 @@ export const Sidebar: React.FC = ({ isMobileOpen, handleDrawerToggle, isN { title: "Attributes", icon: props => , - url: "#", - activeRoutes: ["#"], - disabled: true + url: UrlService.attributes(), + activeRoutes: [UrlService.attributes()], + disabled: false }, { title: "Settings", diff --git a/apps/provider-console/src/components/shared/ProviderActionDetails.tsx b/apps/provider-console/src/components/shared/ProviderActionDetails.tsx index b52beb660..59d9fca6c 100644 --- a/apps/provider-console/src/components/shared/ProviderActionDetails.tsx +++ b/apps/provider-console/src/components/shared/ProviderActionDetails.tsx @@ -61,7 +61,10 @@ export const ProviderActionDetails: React.FC<{ actionId: string | null }> = ({ a
{actionDetails?.tasks.map((task, index) => (
-
toggleAccordion(index)}> +
toggleAccordion(index)} + >
{openAccordions[index] ? : } {task.description} diff --git a/apps/provider-console/src/components/shared/ProviderActionList.tsx b/apps/provider-console/src/components/shared/ProviderActionList.tsx index 9a0b8c2bc..ced0c757b 100644 --- a/apps/provider-console/src/components/shared/ProviderActionList.tsx +++ b/apps/provider-console/src/components/shared/ProviderActionList.tsx @@ -56,7 +56,7 @@ export const ProviderActionList: React.FC = ({ actions }; const handleRowClick = (actionId: string) => { - router.push(`/action-details/?id=${actionId}`); + router.push(`/actions/${actionId}`); }; return ( @@ -64,7 +64,7 @@ export const ProviderActionList: React.FC = ({ actions
    {actions.length > 0 ? ( actions.map(action => ( -
  • handleRowClick(action.id)}> +
  • handleRowClick(action.id)}>

    {action.name}

    diff --git a/apps/provider-console/src/pages/action-details/index.tsx b/apps/provider-console/src/pages/actions/[id]/index.tsx similarity index 56% rename from apps/provider-console/src/pages/action-details/index.tsx rename to apps/provider-console/src/pages/actions/[id]/index.tsx index 535545b81..4054d4ed6 100644 --- a/apps/provider-console/src/pages/action-details/index.tsx +++ b/apps/provider-console/src/pages/actions/[id]/index.tsx @@ -1,16 +1,16 @@ "use client"; import React from "react"; -import { useSearchParams } from "next/navigation"; import { Layout } from "@src/components/layout/Layout"; import { ProviderActionDetails } from "@src/components/shared/ProviderActionDetails"; -export default function ActionDetailsPage() { - const searchParams = useSearchParams(); - const actionId = searchParams.get("id"); +type Props = { + id: string | null; +}; - if (!actionId) { +const ActionDetailsPage: React.FC = ({ id }) => { + if (!id) { return (
    Error: No action ID provided
    @@ -21,8 +21,18 @@ export default function ActionDetailsPage() { return (
    - +
    ); +}; + +export default ActionDetailsPage; + +export async function getServerSideProps({ params }) { + return { + props: { + id: params?.id + } + }; } diff --git a/apps/provider-console/src/pages/actions/index.tsx b/apps/provider-console/src/pages/actions/index.tsx new file mode 100644 index 000000000..cc3a5038f --- /dev/null +++ b/apps/provider-console/src/pages/actions/index.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { Layout } from "@src/components/layout/Layout"; +import { ProviderActionList } from "@src/components/shared/ProviderActionList"; +import { Title } from "@src/components/shared/Title"; +import { useProviderActions } from "@src/queries/useProviderQuery"; + +const ActionsList: React.FC = () => { + const { data: actions } = useProviderActions(); + return ( + +
    +
    + User Actions +
    +
    +
    +
    +
    + +
    +
    +
    +
    + ); +}; + +export default ActionsList; diff --git a/apps/provider-console/src/pages/attributes/index.tsx b/apps/provider-console/src/pages/attributes/index.tsx new file mode 100644 index 000000000..3d13f7036 --- /dev/null +++ b/apps/provider-console/src/pages/attributes/index.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useQuery } from "react-query"; + +import { ProviderAttributes } from "@src/components/become-provider/ProviderAttributes"; +import { Layout } from "@src/components/layout/Layout"; +import { useSelectedChain } from "@src/context/CustomChainProvider"; +import consoleClient from "@src/utils/consoleClient"; + +const Attributes: React.FunctionComponent = () => { + const { address } = useSelectedChain(); + const { data: providerDetails, isLoading: isLoadingProviderDetails }: { data: any; isLoading: boolean } = useQuery( + "providerDetails", + () => consoleClient.get(`/v1/providers/${address}`), + { + refetchOnWindowFocus: false, + retry: 3 + } + ); + + return ( + + {isLoadingProviderDetails ? ( +
    Loading...
    + ) : ( +
    + +
    + )} +
    + ); +}; + +export default Attributes; diff --git a/apps/provider-console/src/store/providerProcessStore.ts b/apps/provider-console/src/store/providerProcessStore.ts index c25992bce..826622640 100644 --- a/apps/provider-console/src/store/providerProcessStore.ts +++ b/apps/provider-console/src/store/providerProcessStore.ts @@ -27,7 +27,7 @@ interface ProviderPricing { endpointBidPrice: number } -interface ProviderAttribute { key: string, value: string, customKey: string } +export interface ProviderAttribute { key: string, value: string } interface ProviderProcess { machines: MachineInformation[], diff --git a/apps/provider-console/src/utils/sanityUtils.ts b/apps/provider-console/src/utils/sanityUtils.ts new file mode 100644 index 000000000..72f178825 --- /dev/null +++ b/apps/provider-console/src/utils/sanityUtils.ts @@ -0,0 +1,15 @@ +import { ControlMachineWithAddress } from "@src/types/controlMachine"; + +export function sanitizeMachineAccess(machine: ControlMachineWithAddress | null) { + if (!machine) { + return undefined; + } + return { + hostname: machine.access.hostname, + port: machine.access.port, + username: machine.access.username, + keyfile: machine.access.file || null, + password: machine.access.password || null, + passphrase: machine.access.passphrase || null + }; +} diff --git a/apps/provider-console/src/utils/urlUtils.ts b/apps/provider-console/src/utils/urlUtils.ts index 1b0f1d1a3..86ecc4089 100644 --- a/apps/provider-console/src/utils/urlUtils.ts +++ b/apps/provider-console/src/utils/urlUtils.ts @@ -17,7 +17,9 @@ export const domainName = "https://provider.akash.network"; export class UrlService { static home = () => "/"; static deployments = () => "/deployments"; + static attributes = () => "/attributes"; static getStarted = () => "/get-started"; static privacyPolicy = () => "/privacy-policy"; static termsOfService = () => "/terms-of-service"; + static actions = () => "/actions"; } \ No newline at end of file