feat: LokiDeploy 初始提交 - 轻量级 K8s Loki 部署平台
Go(Gin) + React(Vite + shadcn/ui + Tailwind) 前后端分离,全中文 UI - 集群管理:Kubeconfig / Token 双接入,kubectl + helm 部署 - 主机管理:SSH 密钥 / 密码双认证,CPU/内存/磁盘使用率监控,SSH 错误中文友好提示 - 存储配置、部署管理、系统设置 - Docker 镜像内置 kubectl + helm,开箱即用 - run.sh 一键启停(start/stop/restart/status) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
2a93de600d
134 files changed
+19595
No files matched your search
@@ -0,0 +1,166 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { Activity, Eye, EyeOff, Shield, ServerOff } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { authApi } from '@/srv/api'
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState('admin')
|
||||
const [password, setPassword] = useState('admin123')
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [backendStatus, setBackendStatus] = useState<'checking' | 'online' | 'offline'>('checking')
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
// Check if already logged in
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
navigate('/')
|
||||
return
|
||||
}
|
||||
// Quick backend check
|
||||
checkBackend()
|
||||
}, [navigate])
|
||||
|
||||
const checkBackend = async () => {
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000)
|
||||
await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{}',
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
// Even a 400 error means backend is up
|
||||
setBackendStatus('online')
|
||||
} catch {
|
||||
setBackendStatus('offline')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const result = await authApi.login(username, password)
|
||||
localStorage.setItem('token', result.token)
|
||||
localStorage.setItem('user', JSON.stringify(result.user))
|
||||
navigate('/')
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : '登录失败'
|
||||
if (message.includes('Cannot connect')) {
|
||||
setError('无法连接后端服务,请确认后端已启动(端口 8080)。')
|
||||
setBackendStatus('offline')
|
||||
} else if (message.includes('invalid') || message.includes('password') || message.includes('密码')) {
|
||||
setError('用户名或密码错误')
|
||||
} else {
|
||||
setError(message)
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-emerald-500 mb-4">
|
||||
<Activity className="w-9 h-9 text-white" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">LokiDeploy</h1>
|
||||
<p className="text-sm text-slate-500 mt-1">Kubernetes Loki 日志部署平台</p>
|
||||
</div>
|
||||
|
||||
{/* Backend Status */}
|
||||
{backendStatus === 'offline' && (
|
||||
<div className="mb-4 p-4 rounded-xl bg-amber-50 border border-amber-200 flex items-start gap-3">
|
||||
<ServerOff className="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-amber-800">后端服务未启动</p>
|
||||
<p className="text-xs text-amber-700 mt-1">
|
||||
无法连接 API 服务,请先启动后端:
|
||||
</p>
|
||||
<code className="block mt-2 px-3 py-2 bg-amber-100 rounded text-xs font-mono text-amber-900">
|
||||
./run.sh
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Login Card */}
|
||||
<div className="bg-white dark:bg-slate-900 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-800 p-8">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Shield className="w-5 h-5 text-emerald-500" />
|
||||
<h2 className="text-lg font-semibold text-slate-800 dark:text-white">登录</h2>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-red-600 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">用户名</Label>
|
||||
<Input
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入用户名"
|
||||
required
|
||||
className="h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
className="h-10 pr-10"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full h-10 bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? '登录中...' : '登录'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-slate-100 dark:border-slate-800">
|
||||
<p className="text-xs text-center text-slate-500">
|
||||
默认账号:admin / admin123
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user