Skip to content

Commit

Permalink
Min AKT and minor bug fixes (#679)
Browse files Browse the repository at this point in the history
* feat(provider): added popup for min akt needed for provider

* fix(provider): change become-provider to create-provider

* fix(provider): lint fixed

* chore: changed min akt requirement for create provider screen

* feat(provider): added api for updating url

* chore: fix small bugs in loading pages for non-providers

* chore: added control-plane info and default attributes

* fix: fixed small bug

* fix(provider): fixed issue with types

* fix: min 5 akt
  • Loading branch information
jigar-arc10 authored Jan 20, 2025
1 parent 9c6cddf commit 68063f1
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
Separator
Separator,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "@akashnetwork/ui/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { Plus, Trash } from "iconoir-react";
Expand All @@ -33,6 +37,8 @@ import { ResetProviderForm } from "./ResetProviderProcess";

const attributeKeys = Object.keys(providerAttributesFormValuesSchema.shape);

const DEFAULT_ATTRIBUTES = ['host', 'tier'];

interface ProviderAttributesProps {
existingAttributes?: ProviderAttribute[];
editMode?: boolean;
Expand All @@ -52,17 +58,23 @@ const providerFormSchema = z.object({
type ProviderFormValues = z.infer<typeof providerFormSchema>;

export const ProviderAttributes: React.FunctionComponent<ProviderAttributesProps> = ({ onComplete, existingAttributes, editMode }) => {
const [providerPricing, setProviderPricing] = useAtom(providerProcessStore.providerProcessAtom);
const [providerProcess, setProviderProcess] = useAtom(providerProcessStore.providerProcessAtom);
const organizationName = providerProcess.config?.organization;

const form = useForm<ProviderFormValues>({
resolver: zodResolver(providerFormSchema),
defaultValues: {
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: "" }]
key: attributeKeys.includes(attr.key) ? attr.key : "unknown-attributes",
value: attr.value,
customKey: attributeKeys.includes(attr.key) ? "" : attr.key
}))
: [
{ key: "host", value: "akash", customKey: "" },
{ key: "tier", value: "community", customKey: "" },
{ key: "organization", value: organizationName || "", customKey: "" }
]
}
});

Expand All @@ -78,14 +90,14 @@ export const ProviderAttributes: React.FunctionComponent<ProviderAttributesProps

const updateProviderAttributesAndProceed: SubmitHandler<ProviderFormValues> = async data => {
if (!editMode) {
const updatedProviderPricing = {
...providerPricing,
const updatedProviderProcess = {
...providerProcess,
attributes: data.attributes.map(attr => ({
key: attr.key === "unknown-attributes" ? attr.customKey || "" : attr.key || "",
value: attr.value
}))
};
setProviderPricing(updatedProviderPricing);
setProviderProcess(updatedProviderProcess);
onComplete && onComplete();
} else {
const attributes = data.attributes.map(attr => ({
Expand Down Expand Up @@ -124,30 +136,48 @@ export const ProviderAttributes: React.FunctionComponent<ProviderAttributesProps
{fields.map((field, index) => {
const selectedKeys = form.watch("attributes").map(attr => attr.key);
const availableKeys = attributeKeys.filter(key => !selectedKeys.includes(key) || key === field.key || key === "unknown-attributes");
const isDefaultAttribute = DEFAULT_ATTRIBUTES.includes(field.key);

return (
<div key={field.id} className="mb-2 flex space-x-2">
<Controller
control={form.control}
name={`attributes.${index}.key`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Select value={field.value} onValueChange={value => field.onChange(value)}>
<SelectTrigger>{field.value || "Select Key"}</SelectTrigger>
<SelectContent>
{availableKeys.map(key => (
<SelectItem key={key} value={key}>
{key}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<TooltipProvider>
<Controller
control={form.control}
name={`attributes.${index}.key`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Tooltip>
<TooltipTrigger asChild>
<div>
<Select
value={field.value}
onValueChange={value => field.onChange(value)}
disabled={isDefaultAttribute}
>
<SelectTrigger>{field.value || "Select Key"}</SelectTrigger>
<SelectContent>
{availableKeys.map(key => (
<SelectItem key={key} value={key}>
{key}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</TooltipTrigger>
{isDefaultAttribute && (
<TooltipContent>
<p>This is a default attribute and cannot be modified</p>
</TooltipContent>
)}
</Tooltip>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TooltipProvider>
{form.watch(`attributes.${index}.key`) === "unknown-attributes" && (
<FormField
control={form.control}
Expand All @@ -168,13 +198,34 @@ export const ProviderAttributes: React.FunctionComponent<ProviderAttributesProps
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="Value" {...field} />
<Tooltip>
<TooltipTrigger asChild>
<div>
<Input
placeholder="Value"
{...field}
disabled={isDefaultAttribute}
/>
</div>
</TooltipTrigger>
{isDefaultAttribute && (
<TooltipContent>
<p>This is a default attribute and cannot be modified</p>
</TooltipContent>
)}
</Tooltip>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="button" variant="outline" size="icon" onClick={() => remove(index)}>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => remove(index)}
disabled={isDefaultAttribute}
>
<Trash className="h-4 w-4" />
</Button>
</div>
Expand Down
139 changes: 135 additions & 4 deletions apps/provider-console/src/components/become-provider/ServerAccess.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
"use client";
import React, { useCallback, useState } from "react";
import { Button, Input, Separator } from "@akashnetwork/ui/components";
import { Button, Input, Popup, Separator } from "@akashnetwork/ui/components";

import { useWallet } from "@src/context/WalletProvider";
import { ServerForm } from "./ServerForm";

interface ServerAccessProps {
onComplete: () => void;
}

interface NodeCounts {
controlPlane: number;
workerNodes: number;
}

interface ServerTypeInfo {
isControlPlane: boolean;
nodeNumber: number;
}

export const ServerAccess: React.FC<ServerAccessProps> = ({ onComplete }) => {
const [numberOfServers, setNumberOfServers] = useState(1);
const [activateServerForm, setActivateServerForm] = useState(false);
const [currentServer, setCurrentServer] = useState(0);
const [showBalancePopup, setShowBalancePopup] = useState(false);
const [showNodeDistribution, setShowNodeDistribution] = useState(false);

const { walletBalances } = useWallet();
const MIN_BALANCE = 5_000_000;
const hasEnoughBalance = (walletBalances?.uakt || 0) >= MIN_BALANCE;

React.useEffect(() => {
if (!hasEnoughBalance) {
setShowBalancePopup(true);
}
}, [hasEnoughBalance]);

const handleServerFormSubmit = useCallback(() => {
if (currentServer + 1 >= numberOfServers) {
Expand All @@ -25,9 +48,59 @@ export const ServerAccess: React.FC<ServerAccessProps> = ({ onComplete }) => {
setNumberOfServers(value);
}, []);

const calculateNodeDistribution = useCallback((totalNodes: number): NodeCounts => {
if (totalNodes <= 3) {
return { controlPlane: 1, workerNodes: totalNodes - 1 };
}
if (totalNodes <= 5) {
return { controlPlane: 3, workerNodes: totalNodes - 3 };
}

const baseControlPlane = 3;
const additionalPairs = Math.floor((totalNodes - 1) / 50);
const controlPlane = Math.min(baseControlPlane + additionalPairs * 2, 11); // Cap at 11 control plane nodes
return { controlPlane, workerNodes: totalNodes - controlPlane };
}, []);

const handleNextClick = useCallback(() => {
if (!hasEnoughBalance) {
setShowBalancePopup(true);
return;
}
setShowNodeDistribution(true);
}, [hasEnoughBalance]);

const handleDistributionNext = useCallback(() => {
setShowNodeDistribution(false);
setActivateServerForm(true);
}, []);

const handleClosePopup = useCallback(() => {
setShowBalancePopup(false);
}, []);

const getCurrentServerType = useCallback(
(serverIndex: number): ServerTypeInfo => {
const { controlPlane } = calculateNodeDistribution(numberOfServers);

if (serverIndex < controlPlane) {
return {
isControlPlane: true,
nodeNumber: serverIndex + 1
};
}

return {
isControlPlane: false,
nodeNumber: serverIndex - controlPlane + 1
};
},
[calculateNodeDistribution, numberOfServers]
);

return (
<div className="flex flex-col items-center pt-10">
{!activateServerForm ? (
{!activateServerForm && !showNodeDistribution ? (
<div className="space-y-6">
<div className="flex items-center space-x-4">
<h3 className="text-xl font-bold">Server Count</h3>
Expand All @@ -50,13 +123,71 @@ export const ServerAccess: React.FC<ServerAccessProps> = ({ onComplete }) => {
<div className="flex w-full justify-between">
<div className="flex justify-start"></div>
<div className="flex justify-end">
<Button onClick={() => setActivateServerForm(true)}>Next</Button>
<Button onClick={handleNextClick}>Next</Button>
</div>
</div>
</div>
) : showNodeDistribution ? (
<div className="space-y-6">
<div className="flex gap-6">
<div className="rounded-lg border p-6 text-center">
<p className="mb-4 text-3xl font-bold">{calculateNodeDistribution(numberOfServers).controlPlane}</p>
<h3 className="mb-2 text-xl font-bold">Control Plane Nodes</h3>
<p className="text-sm">Manages the cluster operations & run your workloads</p>
</div>
<div className="rounded-lg border p-6 text-center">
<p className="mb-4 text-3xl font-bold">{calculateNodeDistribution(numberOfServers).workerNodes}</p>
<h3 className="mb-2 text-xl font-bold">Worker Nodes</h3>
<p className="text-sm">Runs your workloads</p>
</div>
</div>
<div className="flex w-full justify-between">
<Button variant="ghost" onClick={() => setShowNodeDistribution(false)}>
Back
</Button>
<Button onClick={handleDistributionNext}>Next</Button>
</div>
</div>
) : (
<ServerForm key={currentServer} currentServerNumber={currentServer} onComplete={handleServerFormSubmit} />
<ServerForm key={currentServer} currentServerNumber={currentServer} onComplete={handleServerFormSubmit} {...getCurrentServerType(currentServer)} />
)}

<Popup
fullWidth
open={showBalancePopup}
variant="custom"
actions={[
{
label: "Close",
color: "primary",
variant: "ghost",
side: "left",
onClick: handleClosePopup
}
]}
onClose={handleClosePopup}
maxWidth="xs"
enableCloseOnBackdropClick
title="Insufficient Balance"
>
<div>
<div className="pb-2">
<p>
You need at least <strong>5 AKT</strong> to become a provider.
<br />
Every lease created on the Akash network requires <strong>0.5 AKT</strong> to be locked in escrow.
<br />
Please ensure you have enough funds to cover your resources.
</p>
</div>
<Separator />
<div>
<p className="pt-2">
You currently have <strong>{(walletBalances?.uakt || 0) / 1000000} AKT</strong>.
</p>
</div>
</div>
</Popup>
</div>
);
};
Loading

0 comments on commit 68063f1

Please sign in to comment.