fix apis
This commit is contained in:
@@ -1,136 +1,232 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Download, Feather, Trash2 } from 'lucide-react'
|
import { Download, Feather, Trash2, Loader2, Cpu } from 'lucide-react'
|
||||||
import type { FormEvent } from 'react'
|
import type { FormEvent } from 'react'
|
||||||
|
import { fromTheme } from 'tailwind-merge'
|
||||||
|
|
||||||
interface Cluster {
|
interface Cluster {
|
||||||
id: string
|
|
||||||
Name: string
|
Name: string
|
||||||
|
ClusterID: string
|
||||||
|
Status: string
|
||||||
|
Version: string
|
||||||
|
HealthCheck: string
|
||||||
ControlPlane: string
|
ControlPlane: string
|
||||||
PlatformVersion: string
|
PlatformVersion: string
|
||||||
|
Alert: string
|
||||||
|
EndPoint: string
|
||||||
Cpu : string
|
Cpu : string
|
||||||
Memory: string
|
Memory: string
|
||||||
clusterId: string
|
|
||||||
status: string
|
|
||||||
version: string
|
|
||||||
alerts: string
|
|
||||||
endpoint: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateCluster() {
|
export default function CreateCluster() {
|
||||||
const [clusters, setClusters] = useState<Cluster[]>(() => {
|
const [clusters, setClusters] = useState<Cluster[]>([])
|
||||||
fetch('http://localhost:8082/clusters', {
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false)
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
|
const [clusterToDelete, setClusterToDelete] = useState<string>('')
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
clusterName: '',
|
||||||
|
namespace: '',
|
||||||
|
controlPlane: 'k8s',
|
||||||
|
PlatformVersion: 'v1.31.6 (recommended)',
|
||||||
|
Cpu: 1,
|
||||||
|
Memory: 1024
|
||||||
|
})
|
||||||
|
|
||||||
|
const pollingIntervalRef = useRef<number | null>(null)
|
||||||
|
|
||||||
|
// Function to fetch clusters
|
||||||
|
const fetchClusters = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:8082/clusters', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `${localStorage.getItem('auth:token') || ''}`
|
'Authorization': `${localStorage.getItem('auth:token') || ''}`
|
||||||
},
|
},
|
||||||
}).then(async (res) => {
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json()
|
|
||||||
setClusters(data.clusters || [])
|
|
||||||
return data.clusters || []
|
|
||||||
//localStorage.setItem('clusters', JSON.stringify(data.clusters || []))
|
|
||||||
} else {
|
|
||||||
const data = await res.json()
|
|
||||||
console.error(data.message || 'Failed to fetch clusters')
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
console.error('Failed to fetch clusters')
|
|
||||||
})
|
|
||||||
const saved = localStorage.getItem('clusters')
|
|
||||||
if (saved) {
|
|
||||||
return JSON.parse(saved)
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [showModal, setShowModal] = useState(false)
|
if (response.ok) {
|
||||||
const [formData, setFormData] = useState({
|
const data = await response.json()
|
||||||
|
setClusters(data || [])
|
||||||
|
localStorage.setItem('clusters', JSON.stringify(data || []))
|
||||||
|
|
||||||
|
// Check if any cluster is still progressing
|
||||||
|
const hasProgressingClusters = data.some((cluster: Cluster) => cluster.Status === 'Progressing' || cluster.Status === '' || cluster.Status === 'Missing' || cluster.Status === 'Pendding')
|
||||||
|
|
||||||
|
// Start or stop polling based on cluster status
|
||||||
|
if (hasProgressingClusters) {
|
||||||
|
startPolling()
|
||||||
|
} else {
|
||||||
|
stopPolling()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const data = await response.json()
|
||||||
|
console.error(data.message || 'Failed to fetch clusters')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch clusters', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start polling for clusters with Progressing status
|
||||||
|
const startPolling = () => {
|
||||||
|
if (pollingIntervalRef.current) {
|
||||||
|
return // Already polling
|
||||||
|
}
|
||||||
|
|
||||||
|
pollingIntervalRef.current = setInterval(() => {
|
||||||
|
fetchClusters()
|
||||||
|
}, 3000) // Poll every 3 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop polling
|
||||||
|
const stopPolling = () => {
|
||||||
|
if (pollingIntervalRef.current) {
|
||||||
|
clearInterval(pollingIntervalRef.current)
|
||||||
|
pollingIntervalRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load clusters on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
fetchClusters()
|
||||||
|
|
||||||
|
// Cleanup polling on component unmount
|
||||||
|
return () => {
|
||||||
|
stopPolling()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
const newCluster = {
|
||||||
|
Name: formData.clusterName,
|
||||||
|
ClusterID: Math.random().toString(36).substr(2, 8),
|
||||||
|
ControlPlane: formData.controlPlane,
|
||||||
|
Status: 'Progressing',
|
||||||
|
Cpu: formData.Cpu.toString(),
|
||||||
|
Memory: formData.Memory.toString(),
|
||||||
|
PlatformVersion: formData.PlatformVersion.split(' ')[0],
|
||||||
|
HealthCheck: '',
|
||||||
|
Alert: '',
|
||||||
|
EndPoint: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:8082/createcluster', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': localStorage.getItem('auth:token') || ''
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newCluster),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
console.log('Cluster created successfully:', data)
|
||||||
|
|
||||||
|
// Close modal and reset form
|
||||||
|
setShowModal(false)
|
||||||
|
setFormData({
|
||||||
clusterName: '',
|
clusterName: '',
|
||||||
namespace: '',
|
namespace: '',
|
||||||
controlPlane: 'Kubernetes (k8s)',
|
controlPlane: 'Kubernetes (k8s)',
|
||||||
kubernetesVersion: 'v1.31.6 (recommended)',
|
PlatformVersion: 'v1.31.6 (recommended)',
|
||||||
Cpu: 1,
|
Cpu: 1,
|
||||||
Memory: 1024
|
Memory: 1024
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSubmit = (e: FormEvent) => {
|
// Refresh cluster list and start polling
|
||||||
e.preventDefault()
|
await fetchClusters()
|
||||||
const newCluster: Cluster = {
|
} else {
|
||||||
Name: formData.clusterName,
|
const data = await response.json()
|
||||||
clusterId: Math.random().toString(36).substr(2, 8),
|
console.error('Failed to create cluster:', data.message)
|
||||||
status: 'Creating',
|
// You can add error handling here (show toast notification, etc.)
|
||||||
ControlPlane: formData.controlPlane,
|
}
|
||||||
Cpu: formData.Cpu.toString(),
|
} catch (error) {
|
||||||
Memory: formData.Memory.toString(),
|
console.error('Error creating cluster:', error)
|
||||||
PlatformVersion: formData.kubernetesVersion.split(' ')[0]
|
// You can add error handling here (show toast notification, etc.)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('http://localhost:8082/createcluster', {
|
const downloadKubeconfig = async (clusterName: string) => {
|
||||||
method: 'POST',
|
try {
|
||||||
headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('auth:token') || '' },
|
const response = await fetch(`http://localhost:8082/connect?Name=${encodeURIComponent(clusterName)}`, {
|
||||||
body: JSON.stringify(newCluster),
|
method: 'GET',
|
||||||
}).then(async (res) => {
|
headers: {
|
||||||
if (res.ok) {
|
'Content-Type': 'application/json',
|
||||||
const data = await res.json()
|
'Authorization': `${localStorage.getItem('auth:token') || ''}`
|
||||||
console.log(data)
|
},
|
||||||
} else {
|
|
||||||
const data = await res.json()
|
|
||||||
console.log(data)
|
|
||||||
// setError(data.message || 'Login failed')
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
//setError('Login failed')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// const updatedClusters = [...clusters, newCluster]
|
if (response.ok) {
|
||||||
// setClusters(updatedClusters)
|
const kubeconfig = await response.text()
|
||||||
// localStorage.setItem('clusters', JSON.stringify(updatedClusters))
|
|
||||||
// setShowModal(false)
|
|
||||||
// setFormData({
|
|
||||||
// clusterName: '',
|
|
||||||
// namespace: '',
|
|
||||||
// controlPlane: 'Kubernetes (k8s)',
|
|
||||||
// kubernetesVersion: 'v1.31.6 (recommended)',
|
|
||||||
// Cpu: 1,
|
|
||||||
// Memory: 2048
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadKubeconfig = (clusterId: string) => {
|
|
||||||
const kubeconfig = `apiVersion: v1
|
|
||||||
kind: Config
|
|
||||||
clusters:
|
|
||||||
- name: ${clusterId}
|
|
||||||
cluster:
|
|
||||||
server: https://${clusterId}.example.com
|
|
||||||
contexts:
|
|
||||||
- name: ${clusterId}
|
|
||||||
context:
|
|
||||||
cluster: ${clusterId}
|
|
||||||
user: admin
|
|
||||||
current-context: ${clusterId}
|
|
||||||
users:
|
|
||||||
- name: admin
|
|
||||||
user:
|
|
||||||
token: your-token-here`
|
|
||||||
|
|
||||||
const blob = new Blob([kubeconfig], { type: 'text/yaml' })
|
const blob = new Blob([kubeconfig], { type: 'text/yaml' })
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = url
|
a.href = url
|
||||||
a.download = `kubeconfig-${clusterId}.yaml`
|
a.download = `kubeconfig-${clusterName}.yaml`
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a)
|
||||||
a.click()
|
a.click()
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
|
} else {
|
||||||
|
console.error('Failed to download kubeconfig')
|
||||||
|
// You can add error handling here (show toast notification, etc.)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading kubeconfig:', error)
|
||||||
|
// You can add error handling here (show toast notification, etc.)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteCluster = (clusterId: string) => {
|
const confirmDeleteCluster = (clusterName: string) => {
|
||||||
const updatedClusters = clusters.filter(cluster => cluster.clusterId !== clusterId)
|
setClusterToDelete(clusterName)
|
||||||
setClusters(updatedClusters)
|
setShowDeleteModal(true)
|
||||||
localStorage.setItem('clusters', JSON.stringify(updatedClusters))
|
}
|
||||||
|
|
||||||
|
const deleteCluster = async () => {
|
||||||
|
if (!clusterToDelete) return
|
||||||
|
|
||||||
|
setIsDeleting(true)
|
||||||
|
setShowDeleteModal(false)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:8082/deletecluster?Name=${encodeURIComponent(clusterToDelete)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `${localStorage.getItem('auth:token') || ''}`
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('Cluster deleted successfully')
|
||||||
|
// Refresh the cluster list to reflect the deletion and start polling
|
||||||
|
pollingIntervalRef.current = setInterval(() => {
|
||||||
|
fetchClusters()
|
||||||
|
}, 3000) // Poll every 3 seconds
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const data = await response.json()
|
||||||
|
console.error('Failed to delete cluster:', data.message)
|
||||||
|
// You can add error handling here (show toast notification, etc.)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting cluster:', error)
|
||||||
|
// You can add error handling here (show toast notification, etc.)
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false)
|
||||||
|
setClusterToDelete('')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -161,44 +257,47 @@ users:
|
|||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cluster ID</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cluster ID</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Version</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Version</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Alerts</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Health Check</th>
|
||||||
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Alert</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Endpoint</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Endpoint</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{clusters.map((cluster) => (
|
{clusters.map((cluster) => (
|
||||||
<tr key={cluster.id} className="hover:bg-gray-50">
|
<tr key={cluster.ClusterID} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<Link
|
<Link
|
||||||
to={`/app/clusters/${cluster.id}`}
|
to={`/app/clusters/${cluster.ClusterID}`}
|
||||||
className="text-sm font-medium text-blue-600 hover:text-blue-900"
|
className="text-sm font-medium text-blue-600 hover:text-blue-900"
|
||||||
>
|
>
|
||||||
{cluster.Name}
|
{cluster.Name}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.clusterId}</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.ClusterID}</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||||
cluster.status === 'Healthy' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
|
cluster.Status === 'Healthy' ? 'bg-green-100 text-green-800' :
|
||||||
|
cluster.Status === 'Progressing' ? 'bg-blue-100 text-blue-800' : 'bg-yellow-100 text-yellow-800'
|
||||||
}`}>
|
}`}>
|
||||||
{cluster.status}
|
{cluster.Status}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.version}</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.Version}</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.alerts}</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.HealthCheck}</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.endpoint}</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.Alert}</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{cluster.EndPoint}</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => downloadKubeconfig(cluster.clusterId)}
|
onClick={() => downloadKubeconfig(cluster.Name)}
|
||||||
className="p-2 text-blue-600 hover:text-blue-900 hover:bg-blue-50 rounded-md transition-colors"
|
className="p-2 text-blue-600 hover:text-blue-900 hover:bg-blue-50 rounded-md transition-colors"
|
||||||
title="Download kubeconfig"
|
title="Download kubeconfig"
|
||||||
>
|
>
|
||||||
<Download size={16} />
|
<Download size={16} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteCluster(cluster.clusterId)}
|
onClick={() => confirmDeleteCluster(cluster.Name)}
|
||||||
className="p-2 text-red-600 hover:text-red-900 hover:bg-red-50 rounded-md transition-colors"
|
className="p-2 text-red-600 hover:text-red-900 hover:bg-red-50 rounded-md transition-colors"
|
||||||
title="Delete cluster"
|
title="Delete cluster"
|
||||||
>
|
>
|
||||||
@@ -216,7 +315,16 @@ users:
|
|||||||
{/* Create Cluster Modal */}
|
{/* Create Cluster Modal */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto relative">
|
||||||
|
{/* Loading Overlay */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center z-10 rounded-lg">
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-blue-600" />
|
||||||
|
<p className="text-sm text-gray-600">Creating cluster...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="px-6 py-4 border-b border-gray-200">
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<h2 className="text-xl font-semibold">Create Cluster</h2>
|
<h2 className="text-xl font-semibold">Create Cluster</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,8 +342,9 @@ users:
|
|||||||
type="text"
|
type="text"
|
||||||
value={formData.clusterName}
|
value={formData.clusterName}
|
||||||
onChange={(e) => setFormData({...formData, clusterName: e.target.value})}
|
onChange={(e) => setFormData({...formData, clusterName: e.target.value})}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -253,7 +362,8 @@ users:
|
|||||||
<select
|
<select
|
||||||
value={formData.controlPlane}
|
value={formData.controlPlane}
|
||||||
onChange={(e) => setFormData({...formData, controlPlane: e.target.value})}
|
onChange={(e) => setFormData({...formData, controlPlane: e.target.value})}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<option>Kubernetes (k8s)</option>
|
<option>Kubernetes (k8s)</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -263,9 +373,10 @@ users:
|
|||||||
Kubernetes Version
|
Kubernetes Version
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.kubernetesVersion}
|
value={formData.PlatformVersion}
|
||||||
onChange={(e) => setFormData({...formData, kubernetesVersion: e.target.value})}
|
onChange={(e) => setFormData({...formData, PlatformVersion: e.target.value})}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<option>v1.31.6 (recommended)</option>
|
<option>v1.31.6 (recommended)</option>
|
||||||
<option>v1.30.0</option>
|
<option>v1.30.0</option>
|
||||||
@@ -289,7 +400,8 @@ users:
|
|||||||
max="8"
|
max="8"
|
||||||
value={formData.Cpu}
|
value={formData.Cpu}
|
||||||
onChange={(e) => setFormData({...formData, Cpu: parseInt(e.target.value)})}
|
onChange={(e) => setFormData({...formData, Cpu: parseInt(e.target.value)})}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -303,7 +415,8 @@ users:
|
|||||||
step="1024"
|
step="1024"
|
||||||
value={formData.Memory}
|
value={formData.Memory}
|
||||||
onChange={(e) => setFormData({...formData, Memory: parseInt(e.target.value)})}
|
onChange={(e) => setFormData({...formData, Memory: parseInt(e.target.value)})}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,21 +427,75 @@ users:
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowModal(false)}
|
onClick={() => setShowModal(false)}
|
||||||
className="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300"
|
className="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-6 py-2 bg-orange-500 text-white rounded-md hover:bg-orange-600"
|
disabled={isLoading}
|
||||||
|
className="px-6 py-2 bg-orange-500 text-white rounded-md hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Create Cluster
|
{isLoading ? (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
'Create Cluster'
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Delete Confirmation Modal */}
|
||||||
|
{showDeleteModal && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
|
||||||
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h2 className="text-xl font-semibold">Delete Cluster</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6">
|
||||||
|
<p className="text-gray-600 mb-6">
|
||||||
|
Are you sure you want to delete the cluster <span className="font-semibold">"{clusterToDelete}"</span>?
|
||||||
|
This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setShowDeleteModal(false)
|
||||||
|
setClusterToDelete('')
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={deleteCluster}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700"
|
||||||
|
>
|
||||||
|
Delete Cluster
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Global Loading Overlay for Delete */}
|
||||||
|
{isDeleting && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg shadow-xl p-8 flex flex-col items-center space-y-4">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-red-600" />
|
||||||
|
<p className="text-sm text-gray-600">Deleting cluster...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user