上传完整的WebUI前端仓库

This commit is contained in:
墨梓柒
2026-01-13 06:24:35 +08:00
parent a9187dc312
commit 812296590e
184 changed files with 47854 additions and 1 deletions

View File

@@ -0,0 +1,189 @@
import { useEffect, useState } from 'react'
import { Loader2, CheckCircle2, AlertCircle } from 'lucide-react'
import { Progress } from '@/components/ui/progress'
/**
* @deprecated 请使用新的 RestartOverlay 组件
* import { RestartOverlay } from '@/components/restart-overlay'
*/
interface RestartingOverlayProps {
onRestartComplete?: () => void
onRestartFailed?: () => void
}
/**
* @deprecated 请使用新的 RestartOverlay 组件
* import { RestartOverlay } from '@/components/restart-overlay'
*/
export function RestartingOverlay({ onRestartComplete, onRestartFailed }: RestartingOverlayProps) {
const [progress, setProgress] = useState(0)
const [status, setStatus] = useState<'restarting' | 'checking' | 'success' | 'failed'>('restarting')
const [elapsedTime, setElapsedTime] = useState(0)
const [checkAttempts, setCheckAttempts] = useState(0)
useEffect(() => {
// 进度条动画
const progressInterval = setInterval(() => {
setProgress((prev) => {
if (prev >= 90) return prev
return prev + 1
})
}, 200)
// 计时器
const timerInterval = setInterval(() => {
setElapsedTime((prev) => prev + 1)
}, 1000)
// 等待3秒后开始检查状态给后端重启时间
const initialDelay = setTimeout(() => {
setStatus('checking')
startHealthCheck()
}, 3000)
return () => {
clearInterval(progressInterval)
clearInterval(timerInterval)
clearTimeout(initialDelay)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const startHealthCheck = () => {
const maxAttempts = 60 // 最多尝试60次约2分钟
const checkHealth = async () => {
try {
setCheckAttempts((prev) => prev + 1)
const response = await fetch('/api/webui/system/status', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
signal: AbortSignal.timeout(3000), // 3秒超时
})
if (response.ok) {
// 重启成功
setProgress(100)
setStatus('success')
setTimeout(() => {
onRestartComplete?.()
}, 1500)
} else {
throw new Error('Status check failed')
}
} catch {
// 继续尝试
if (checkAttempts < maxAttempts) {
setTimeout(checkHealth, 2000) // 2秒后重试
} else {
// 超过最大尝试次数
setStatus('failed')
onRestartFailed?.()
}
}
}
checkHealth()
}
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins}:${secs.toString().padStart(2, '0')}`
}
return (
<div className="fixed inset-0 bg-background/95 backdrop-blur-sm z-50 flex items-center justify-center">
<div className="max-w-md w-full mx-4 space-y-8">
{/* 图标和状态 */}
<div className="flex flex-col items-center space-y-4">
{status === 'restarting' && (
<>
<Loader2 className="h-16 w-16 text-primary animate-spin" />
<h2 className="text-2xl font-bold"></h2>
<p className="text-muted-foreground text-center">
...
</p>
</>
)}
{status === 'checking' && (
<>
<Loader2 className="h-16 w-16 text-primary animate-spin" />
<h2 className="text-2xl font-bold"></h2>
<p className="text-muted-foreground text-center">
... ( {checkAttempts}/60)
</p>
</>
)}
{status === 'success' && (
<>
<CheckCircle2 className="h-16 w-16 text-green-500" />
<h2 className="text-2xl font-bold"></h2>
<p className="text-muted-foreground text-center">
...
</p>
</>
)}
{status === 'failed' && (
<>
<AlertCircle className="h-16 w-16 text-destructive" />
<h2 className="text-2xl font-bold"></h2>
<p className="text-muted-foreground text-center">
</p>
</>
)}
</div>
{/* 进度条 */}
{status !== 'failed' && (
<div className="space-y-2">
<Progress value={progress} className="h-2" />
<div className="flex justify-between text-sm text-muted-foreground">
<span>{progress}%</span>
<span>: {formatTime(elapsedTime)}</span>
</div>
</div>
)}
{/* 提示信息 */}
<div className="bg-muted/50 rounded-lg p-4 space-y-2">
<p className="text-sm text-muted-foreground">
{status === 'restarting' && '🔄 配置已保存,正在重启主程序...'}
{status === 'checking' && '⏳ 正在等待服务恢复,请勿关闭页面...'}
{status === 'success' && '✅ 配置已生效,服务运行正常'}
{status === 'failed' && '⚠️ 如果长时间无响应,请尝试手动重启'}
</p>
</div>
{/* 失败时的操作按钮 */}
{status === 'failed' && (
<div className="flex gap-2">
<button
onClick={() => window.location.reload()}
className="flex-1 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
</button>
<button
onClick={() => {
setStatus('checking')
setCheckAttempts(0)
startHealthCheck()
}}
className="flex-1 px-4 py-2 bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/90"
>
</button>
</div>
)}
</div>
</div>
)
}