add login register and create cluster api
This commit is contained in:
@@ -47,7 +47,7 @@ export default function DashboardLayout() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Layers size={18} />
|
<Layers size={18} />
|
||||||
Create Cluster
|
List Cluster
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<div className="pt-4 border-t border-gray-200">
|
<div className="pt-4 border-t border-gray-200">
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Download, Trash2 } from 'lucide-react'
|
import { Download, Feather, Trash2 } from 'lucide-react'
|
||||||
import type { FormEvent } from 'react'
|
import type { FormEvent } from 'react'
|
||||||
|
|
||||||
interface Cluster {
|
interface Cluster {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
Name: string
|
||||||
|
ControlPlane: string
|
||||||
|
PlatformVersion: string
|
||||||
|
Cpu: string
|
||||||
|
Memory: string
|
||||||
clusterId: string
|
clusterId: string
|
||||||
status: string
|
status: string
|
||||||
version: string
|
version: string
|
||||||
@@ -15,15 +19,30 @@ interface Cluster {
|
|||||||
|
|
||||||
export default function CreateCluster() {
|
export default function CreateCluster() {
|
||||||
const [clusters, setClusters] = useState<Cluster[]>(() => {
|
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')
|
const saved = localStorage.getItem('clusters')
|
||||||
if (saved) {
|
if (saved) {
|
||||||
return JSON.parse(saved)
|
return JSON.parse(saved)
|
||||||
}
|
}
|
||||||
return [
|
return []
|
||||||
{ id: '1', name: 'dev-cluster', clusterId: '680d172c', status: 'Healthy', version: 'v1.28.0', alerts: '0', endpoint: 'https://dev-cluster.example.com' },
|
|
||||||
{ id: '2', name: 'prod-cluster', clusterId: 'd02b06fe', status: 'Healthy', version: 'v1.28.0', alerts: '0', endpoint: 'https://prod-cluster.example.com' },
|
|
||||||
{ id: '3', name: 'test-prod', clusterId: '937261be', status: 'Healthy', version: 'v1.28.0', alerts: '0', endpoint: 'https://test-prod.example.com' },
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
@@ -32,34 +51,51 @@ export default function CreateCluster() {
|
|||||||
namespace: '',
|
namespace: '',
|
||||||
controlPlane: 'Kubernetes (k8s)',
|
controlPlane: 'Kubernetes (k8s)',
|
||||||
kubernetesVersion: 'v1.31.6 (recommended)',
|
kubernetesVersion: 'v1.31.6 (recommended)',
|
||||||
cpu: 1,
|
Cpu: 1,
|
||||||
memory: 2048
|
Memory: 1024
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSubmit = (e: FormEvent) => {
|
const handleSubmit = (e: FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const newCluster: Cluster = {
|
const newCluster: Cluster = {
|
||||||
id: Date.now().toString(),
|
Name: formData.clusterName,
|
||||||
name: formData.clusterName,
|
|
||||||
clusterId: Math.random().toString(36).substr(2, 8),
|
clusterId: Math.random().toString(36).substr(2, 8),
|
||||||
status: 'Creating',
|
status: 'Creating',
|
||||||
version: formData.kubernetesVersion.split(' ')[0],
|
ControlPlane: formData.controlPlane,
|
||||||
alerts: '0',
|
Cpu: formData.Cpu.toString(),
|
||||||
endpoint: `https://${formData.clusterName}.example.com`
|
Memory: formData.Memory.toString(),
|
||||||
|
PlatformVersion: formData.kubernetesVersion.split(' ')[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedClusters = [...clusters, newCluster]
|
fetch('http://localhost:8082/createcluster', {
|
||||||
setClusters(updatedClusters)
|
method: 'POST',
|
||||||
localStorage.setItem('clusters', JSON.stringify(updatedClusters))
|
headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('auth:token') || '' },
|
||||||
setShowModal(false)
|
body: JSON.stringify(newCluster),
|
||||||
setFormData({
|
}).then(async (res) => {
|
||||||
clusterName: '',
|
if (res.ok) {
|
||||||
namespace: '',
|
const data = await res.json()
|
||||||
controlPlane: 'Kubernetes (k8s)',
|
console.log(data)
|
||||||
kubernetesVersion: 'v1.31.6 (recommended)',
|
} else {
|
||||||
cpu: 1,
|
const data = await res.json()
|
||||||
memory: 2048
|
console.log(data)
|
||||||
|
// setError(data.message || 'Login failed')
|
||||||
|
}
|
||||||
|
}).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
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadKubeconfig = (clusterId: string) => {
|
const downloadKubeconfig = (clusterId: string) => {
|
||||||
@@ -138,7 +174,7 @@ users:
|
|||||||
to={`/app/clusters/${cluster.id}`}
|
to={`/app/clusters/${cluster.id}`}
|
||||||
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>
|
||||||
@@ -202,18 +238,7 @@ users:
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
||||||
Namespace
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formData.namespace}
|
|
||||||
onChange={(e) => setFormData({...formData, namespace: 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"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -256,14 +281,14 @@ users:
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
CPU (cores)
|
Cpu (cores)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,8 +301,8 @@ users:
|
|||||||
min="1024"
|
min="1024"
|
||||||
max="16384"
|
max="16384"
|
||||||
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,22 +5,38 @@ import Button from '../components/ui/button'
|
|||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [email, setEmail] = useState('')
|
const [username, setusername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
function onSubmit(e: FormEvent) {
|
function onSubmit(e: FormEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setError(null)
|
setError(null)
|
||||||
const usersString = localStorage.getItem('auth:users')
|
|
||||||
const users: Array<{ email: string; password: string }> = usersString ? JSON.parse(usersString) : []
|
fetch('http://localhost:8082/login', {
|
||||||
const match = users.find((u) => u.email === email && u.password === password)
|
method: 'POST',
|
||||||
if (!match) {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
setError('Invalid credentials')
|
body: JSON.stringify({ username, password }),
|
||||||
return
|
}).then(async (res) => {
|
||||||
}
|
if (res.ok) {
|
||||||
localStorage.setItem('auth:user', JSON.stringify({ email }))
|
const data = await res.json()
|
||||||
navigate('/app')
|
localStorage.setItem('auth:token', data.token)
|
||||||
|
navigate('/app')
|
||||||
|
} else {
|
||||||
|
const data = await res.json()
|
||||||
|
setError(data.message || 'Login failed')
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
setError('Login failed')
|
||||||
|
})
|
||||||
|
// const usersString = localStorage.getItem('auth:users')
|
||||||
|
// const users: Array<{ username: string; password: string }> = usersString ? JSON.parse(usersString) : []
|
||||||
|
// const match = users.find((u) => u.username === username && u.password === password)
|
||||||
|
// if (!match) {
|
||||||
|
// setError('Invalid credentials')
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// localStorage.setItem('auth:user', JSON.stringify({ username }))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,13 +45,13 @@ export default function Login() {
|
|||||||
<h1 className="text-xl font-semibold mb-4">Login</h1>
|
<h1 className="text-xl font-semibold mb-4">Login</h1>
|
||||||
<form className="space-y-4" onSubmit={onSubmit}>
|
<form className="space-y-4" onSubmit={onSubmit}>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium" htmlFor="email">Email</label>
|
<label className="text-sm font-medium" htmlFor="username">username</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="username"
|
||||||
type="email"
|
type="username"
|
||||||
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
value={email}
|
value={username}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setusername(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Button from '../components/ui/button'
|
|||||||
export default function Register() {
|
export default function Register() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
|
const [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [confirm, setConfirm] = useState('')
|
const [confirm, setConfirm] = useState('')
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@@ -13,20 +14,28 @@ export default function Register() {
|
|||||||
function onSubmit(e: FormEvent) {
|
function onSubmit(e: FormEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
if (password !== confirm) {
|
if (password !== confirm) {
|
||||||
setError('Passwords do not match')
|
setError('Passwords do not match')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const usersString = localStorage.getItem('auth:users')
|
|
||||||
const users: Array<{ email: string; password: string }> = usersString ? JSON.parse(usersString) : []
|
fetch('http://localhost:8082/register', {
|
||||||
if (users.some((u) => u.email === email)) {
|
method: 'POST',
|
||||||
setError('Email already registered')
|
headers: { 'Content-Type': 'application/json' },
|
||||||
return
|
body: JSON.stringify({ username, email, password }),
|
||||||
}
|
}).then(async (res) => {
|
||||||
users.push({ email, password })
|
if (res.ok) {
|
||||||
localStorage.setItem('auth:users', JSON.stringify(users))
|
navigate('/login')
|
||||||
localStorage.setItem('auth:user', JSON.stringify({ email }))
|
} else {
|
||||||
navigate('/app')
|
const data = await res.json()
|
||||||
|
// throw new Error(data.message || 'Registration failed')
|
||||||
|
setError(data.message || 'Registration failed')
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
setError('Registration failed')
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,6 +54,17 @@ export default function Register() {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-sm font-medium" htmlFor="username">Username</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
type="username"
|
||||||
|
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium" htmlFor="password">Password</label>
|
<label className="text-sm font-medium" htmlFor="password">Password</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
Reference in New Issue
Block a user