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 { Download, Feather, Trash2 } from 'lucide-react'
|
||||
import { Download, Feather, Trash2, Loader2, Cpu } from 'lucide-react'
|
||||
import type { FormEvent } from 'react'
|
||||
import { fromTheme } from 'tailwind-merge'
|
||||
|
||||
interface Cluster {
|
||||
id: string
|
||||
Name: string
|
||||
ClusterID: string
|
||||
Status: string
|
||||
Version: string
|
||||
HealthCheck: string
|
||||
ControlPlane: string
|
||||
PlatformVersion: string
|
||||
Cpu: string
|
||||
Alert: string
|
||||
EndPoint: string
|
||||
Cpu : string
|
||||
Memory: string
|
||||
clusterId: string
|
||||
status: string
|
||||
version: string
|
||||
alerts: string
|
||||
endpoint: string
|
||||
}
|
||||
|
||||
export default function CreateCluster() {
|
||||
const [clusters, setClusters] = useState<Cluster[]>(() => {
|
||||
fetch('http://localhost:8082/clusters', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'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 [clusters, setClusters] = useState<Cluster[]>([])
|
||||
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: 'Kubernetes (k8s)',
|
||||
kubernetesVersion: 'v1.31.6 (recommended)',
|
||||
controlPlane: 'k8s',
|
||||
PlatformVersion: 'v1.31.6 (recommended)',
|
||||
Cpu: 1,
|
||||
Memory: 1024
|
||||
})
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
const newCluster: Cluster = {
|
||||
Name: formData.clusterName,
|
||||
clusterId: Math.random().toString(36).substr(2, 8),
|
||||
status: 'Creating',
|
||||
ControlPlane: formData.controlPlane,
|
||||
Cpu: formData.Cpu.toString(),
|
||||
Memory: formData.Memory.toString(),
|
||||
PlatformVersion: formData.kubernetesVersion.split(' ')[0]
|
||||
const pollingIntervalRef = useRef<number | null>(null)
|
||||
|
||||
// Function to fetch clusters
|
||||
const fetchClusters = async () => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:8082/clusters', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${localStorage.getItem('auth:token') || ''}`
|
||||
},
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
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
|
||||
}
|
||||
|
||||
fetch('http://localhost:8082/createcluster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('auth:token') || '' },
|
||||
body: JSON.stringify(newCluster),
|
||||
}).then(async (res) => {
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
console.log(data)
|
||||
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: '',
|
||||
namespace: '',
|
||||
controlPlane: 'Kubernetes (k8s)',
|
||||
PlatformVersion: 'v1.31.6 (recommended)',
|
||||
Cpu: 1,
|
||||
Memory: 1024
|
||||
})
|
||||
|
||||
// Refresh cluster list and start polling
|
||||
await fetchClusters()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
console.log(data)
|
||||
// setError(data.message || 'Login failed')
|
||||
const data = await response.json()
|
||||
console.error('Failed to create cluster:', data.message)
|
||||
// You can add error handling here (show toast notification, etc.)
|
||||
}
|
||||
}).catch(() => {
|
||||
//setError('Login failed')
|
||||
})
|
||||
|
||||
// const updatedClusters = [...clusters, newCluster]
|
||||
// setClusters(updatedClusters)
|
||||
// localStorage.setItem('clusters', JSON.stringify(updatedClusters))
|
||||
// setShowModal(false)
|
||||
// setFormData({
|
||||
// clusterName: '',
|
||||
// namespace: '',
|
||||
// controlPlane: 'Kubernetes (k8s)',
|
||||
// kubernetesVersion: 'v1.31.6 (recommended)',
|
||||
// Cpu: 1,
|
||||
// Memory: 2048
|
||||
// })
|
||||
} catch (error) {
|
||||
console.error('Error creating cluster:', error)
|
||||
// You can add error handling here (show toast notification, etc.)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
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 downloadKubeconfig = async (clusterName: string) => {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:8082/connect?Name=${encodeURIComponent(clusterName)}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${localStorage.getItem('auth:token') || ''}`
|
||||
},
|
||||
})
|
||||
|
||||
const blob = new Blob([kubeconfig], { type: 'text/yaml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `kubeconfig-${clusterId}.yaml`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
if (response.ok) {
|
||||
const kubeconfig = await response.text()
|
||||
|
||||
const blob = new Blob([kubeconfig], { type: 'text/yaml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `kubeconfig-${clusterName}.yaml`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
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 updatedClusters = clusters.filter(cluster => cluster.clusterId !== clusterId)
|
||||
setClusters(updatedClusters)
|
||||
localStorage.setItem('clusters', JSON.stringify(updatedClusters))
|
||||
const confirmDeleteCluster = (clusterName: string) => {
|
||||
setClusterToDelete(clusterName)
|
||||
setShowDeleteModal(true)
|
||||
}
|
||||
|
||||
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 (
|
||||
@@ -161,49 +257,52 @@ 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">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">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">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{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">
|
||||
<Link
|
||||
to={`/app/clusters/${cluster.id}`}
|
||||
to={`/app/clusters/${cluster.ClusterID}`}
|
||||
className="text-sm font-medium text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
{cluster.Name}
|
||||
</Link>
|
||||
</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">
|
||||
<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}
|
||||
</span>
|
||||
</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.endpoint}</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">
|
||||
<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' :
|
||||
cluster.Status === 'Progressing' ? 'bg-blue-100 text-blue-800' : 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{cluster.Status}
|
||||
</span>
|
||||
</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.HealthCheck}</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">
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => downloadKubeconfig(cluster.clusterId)}
|
||||
className="p-2 text-blue-600 hover:text-blue-900 hover:bg-blue-50 rounded-md transition-colors"
|
||||
title="Download kubeconfig"
|
||||
>
|
||||
<Download size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteCluster(cluster.clusterId)}
|
||||
className="p-2 text-red-600 hover:text-red-900 hover:bg-red-50 rounded-md transition-colors"
|
||||
title="Delete cluster"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => downloadKubeconfig(cluster.Name)}
|
||||
className="p-2 text-blue-600 hover:text-blue-900 hover:bg-blue-50 rounded-md transition-colors"
|
||||
title="Download kubeconfig"
|
||||
>
|
||||
<Download size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => confirmDeleteCluster(cluster.Name)}
|
||||
className="p-2 text-red-600 hover:text-red-900 hover:bg-red-50 rounded-md transition-colors"
|
||||
title="Delete cluster"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -216,7 +315,16 @@ users:
|
||||
{/* Create Cluster Modal */}
|
||||
{showModal && (
|
||||
<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">
|
||||
<h2 className="text-xl font-semibold">Create Cluster</h2>
|
||||
</div>
|
||||
@@ -234,8 +342,9 @@ users:
|
||||
type="text"
|
||||
value={formData.clusterName}
|
||||
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
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -253,7 +362,8 @@ users:
|
||||
<select
|
||||
value={formData.controlPlane}
|
||||
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>
|
||||
</select>
|
||||
@@ -263,9 +373,10 @@ users:
|
||||
Kubernetes Version
|
||||
</label>
|
||||
<select
|
||||
value={formData.kubernetesVersion}
|
||||
onChange={(e) => setFormData({...formData, kubernetesVersion: 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"
|
||||
value={formData.PlatformVersion}
|
||||
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 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<option>v1.31.6 (recommended)</option>
|
||||
<option>v1.30.0</option>
|
||||
@@ -289,7 +400,8 @@ users:
|
||||
max="8"
|
||||
value={formData.Cpu}
|
||||
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>
|
||||
@@ -303,7 +415,8 @@ users:
|
||||
step="1024"
|
||||
value={formData.Memory}
|
||||
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>
|
||||
@@ -314,21 +427,75 @@ users:
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
<button
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user