s1
This commit is contained in:
433
src/components/BuyServices/NewKubernetisUtho.jsx
Normal file
433
src/components/BuyServices/NewKubernetisUtho.jsx
Normal file
@@ -0,0 +1,433 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Button } from '../ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "../ui/select";
|
||||
import { Input } from "../ui/input";
|
||||
import { Label } from "../ui/label";
|
||||
import Loader from "../ui/loader";
|
||||
import { useToast } from "../ui/toast";
|
||||
import { Switch } from "../ui/switch";
|
||||
|
||||
export default function NewKubernetesService() {
|
||||
const { showToast } = useToast();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isFetchingData, setIsFetchingData] = useState(true);
|
||||
const [plans, setPlans] = useState([]);
|
||||
const [vpcs, setVpcs] = useState([]);
|
||||
const UTHO_API_KEY = "Bearer IoNXhkRJsQPyOEqFMceSfzuKaDLrpxUCATgZjiVdvYlBHbwWmGtn";
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
datacenter: "mumbai",
|
||||
plan: "",
|
||||
name: "",
|
||||
k8s_version: "1.27",
|
||||
nodes: 1,
|
||||
storage: "50",
|
||||
public_ip: true,
|
||||
auto_scaling: false,
|
||||
min_nodes: 1,
|
||||
max_nodes: 3,
|
||||
vpc: "",
|
||||
subnet: ""
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
setIsFetchingData(true);
|
||||
try {
|
||||
const [plansResponse, vpcsResponse] = await Promise.all([
|
||||
fetch("https://api.utho.com/v2/plans", {
|
||||
headers: {
|
||||
"Authorization": UTHO_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}),
|
||||
fetch("https://api.utho.com/v2/vpc", {
|
||||
headers: {
|
||||
"Authorization": UTHO_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
const plansData = await plansResponse.json();
|
||||
const vpcsData = await vpcsResponse.json();
|
||||
console.log('vpcsData.vpc', vpcsData.vpc)
|
||||
setPlans(plansData.plans || []);
|
||||
setVpcs(vpcsData.vpc || []);
|
||||
|
||||
// Set default VPC and subnet if available
|
||||
if (vpcsData.vpc?.length > 0) {
|
||||
const firstVpc = vpcsData.vpc[0];
|
||||
const firstSubnet = firstVpc.subnets?.[0]?.id || "";
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
vpc: firstVpc.id,
|
||||
subnet: firstSubnet
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Initial data fetch error:", error);
|
||||
showToast({
|
||||
title: "Error",
|
||||
description: "Failed to load initial configuration data",
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setIsFetchingData(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
}, []);
|
||||
|
||||
const handleVpcChange = (vpcId) => {
|
||||
const selectedVpc = vpcs.find(vpc => vpc.id === vpcId);
|
||||
const firstSubnet = selectedVpc?.subnet?.[0]?.id || "";
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
vpc: vpcId,
|
||||
subnet: firstSubnet
|
||||
}));
|
||||
};
|
||||
|
||||
const getCurrentSubnets = () => {
|
||||
const selectedVpc = vpcs.find(vpc => vpc.id === formData.vpc);
|
||||
return selectedVpc?.subnets || [];
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
if (!formData.vpc || !formData.subnet) {
|
||||
throw new Error("Please select both VPC and Subnet to proceed");
|
||||
}
|
||||
|
||||
const payload = {
|
||||
datacenter: formData.datacenter,
|
||||
plan: formData.plan,
|
||||
name: formData.name,
|
||||
k8s_version: formData.k8s_version,
|
||||
nodes: formData.nodes,
|
||||
storage: formData.storage,
|
||||
public_ip: formData.public_ip,
|
||||
vpc: formData.vpc,
|
||||
subnet: formData.subnet
|
||||
};
|
||||
|
||||
if (formData.auto_scaling) {
|
||||
payload.auto_scaling = true;
|
||||
payload.min_nodes = formData.min_nodes;
|
||||
payload.max_nodes = formData.max_nodes;
|
||||
}
|
||||
|
||||
const response = await fetch("https://api.utho.com/v2/kubernetes/deploy", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": UTHO_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || "Failed to deploy Kubernetes cluster");
|
||||
}
|
||||
|
||||
if (data.status === "success") {
|
||||
showToast({
|
||||
title: "Success",
|
||||
description: `Cluster ${data.cluster_id || data.id} is being deployed`,
|
||||
variant: "success"
|
||||
});
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
name: "",
|
||||
plan: "",
|
||||
nodes: 1
|
||||
}));
|
||||
} else {
|
||||
throw new Error(data.message || "Failed to deploy Kubernetes cluster");
|
||||
}
|
||||
} catch (error) {
|
||||
showToast({
|
||||
title: "Deployment Failed",
|
||||
description: error.message,
|
||||
variant: "destructive"
|
||||
});
|
||||
console.error("Deployment error:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value
|
||||
}));
|
||||
};
|
||||
|
||||
if (isFetchingData) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Loader />
|
||||
<span className="ml-2">Loading configuration...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const currentSubnets = getCurrentSubnets();
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-2xl mx-auto my-4">
|
||||
<CardHeader>
|
||||
<CardTitle>Deploy New Kubernetes Cluster</CardTitle>
|
||||
<CardDescription>
|
||||
Configure your Kubernetes cluster with the required parameters
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Name, Datacenter, and Kubernetes Version fields */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Cluster Name *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
placeholder="my-cluster"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="datacenter">Data Center *</Label>
|
||||
<Select
|
||||
name="datacenter"
|
||||
value={formData.datacenter}
|
||||
onValueChange={(value) => setFormData({...formData, datacenter: value})}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select location" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="mumbai">Mumbai</SelectItem>
|
||||
<SelectItem value="noida">Noida</SelectItem>
|
||||
<SelectItem value="delhi">Delhi</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="k8s_version">Kubernetes Version *</Label>
|
||||
<Select
|
||||
name="k8s_version"
|
||||
value={formData.k8s_version}
|
||||
onValueChange={(value) => setFormData({...formData, k8s_version: value})}
|
||||
required
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select version" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1.27">1.27 (Latest)</SelectItem>
|
||||
<SelectItem value="1.26">1.26</SelectItem>
|
||||
<SelectItem value="1.25">1.25</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Node Plan and Count */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="plan">Node Plan *</Label>
|
||||
<Select
|
||||
name="plan"
|
||||
value={formData.plan}
|
||||
onValueChange={(value) => setFormData({...formData, plan: value})}
|
||||
required
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a plan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{plans.map(plan => (
|
||||
<SelectItem key={plan.id} value={plan.id}>
|
||||
{`${plan.cpu} vCPU, ${Math.floor(parseInt(plan.ram)/1024)}GB RAM, ${plan.disk}GB Disk`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nodes">Initial Node Count *</Label>
|
||||
<Input
|
||||
id="nodes"
|
||||
name="nodes"
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
value={formData.nodes}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VPC and Subnet Selection */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="vpc">VPC *</Label>
|
||||
<Select
|
||||
name="vpc"
|
||||
value={formData.vpc}
|
||||
onValueChange={handleVpcChange}
|
||||
required
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select VPC" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{vpcs.map(vpc => (
|
||||
<SelectItem key={vpc.id} value={vpc.id}>
|
||||
{vpc.name || vpc.id}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="subnet">Subnet *</Label>
|
||||
<Select
|
||||
name="subnet"
|
||||
value={formData.subnet}
|
||||
onValueChange={(value) => setFormData({...formData, subnet: value})}
|
||||
required
|
||||
disabled={!formData.vpc || currentSubnets.length === 0}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={currentSubnets.length === 0 ? "No subnets available" : "Select subnet"}>
|
||||
{formData.subnet || (currentSubnets.length === 0 ? "No subnets available" : "Select subnet")}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{currentSubnets.map(subnet => (
|
||||
<SelectItem key={subnet.id} value={subnet.id}>
|
||||
{subnet.name || subnet.id} ({subnet.network}/{subnet.size})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{currentSubnets.length === 0 && formData.vpc && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
No subnets found in this VPC. Please create a subnet first.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Storage and Advanced Options */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="storage">Storage per Node (GB) *</Label>
|
||||
<Select
|
||||
name="storage"
|
||||
value={formData.storage}
|
||||
onValueChange={(value) => setFormData({...formData, storage: value})}
|
||||
required
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select storage" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="50">50 GB</SelectItem>
|
||||
<SelectItem value="100">100 GB</SelectItem>
|
||||
<SelectItem value="200">200 GB</SelectItem>
|
||||
<SelectItem value="500">500 GB</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Advanced Options</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="public_ip"
|
||||
checked={formData.public_ip}
|
||||
onCheckedChange={(checked) => setFormData({...formData, public_ip: checked})}
|
||||
/>
|
||||
<Label htmlFor="public_ip">Enable Public IP for Nodes</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="auto_scaling"
|
||||
checked={formData.auto_scaling}
|
||||
onCheckedChange={(checked) => setFormData({...formData, auto_scaling: checked})}
|
||||
/>
|
||||
<Label htmlFor="auto_scaling">Enable Auto Scaling</Label>
|
||||
</div>
|
||||
|
||||
{formData.auto_scaling && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 border rounded-lg">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="min_nodes">Minimum Nodes</Label>
|
||||
<Input
|
||||
id="min_nodes"
|
||||
name="min_nodes"
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
value={formData.min_nodes}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="max_nodes">Maximum Nodes</Label>
|
||||
<Input
|
||||
id="max_nodes"
|
||||
name="max_nodes"
|
||||
type="number"
|
||||
min={formData.min_nodes}
|
||||
max="20"
|
||||
value={formData.max_nodes}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || !formData.vpc || !formData.subnet}
|
||||
className="w-full md:w-auto"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader className="mr-2 h-4 w-4" />
|
||||
Deploying...
|
||||
</>
|
||||
) : "Deploy Kubernetes Cluster"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user