Files
ds2api/webui/src/components/VercelSync.jsx

194 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react'
export default function VercelSync({ onMessage }) {
const [vercelToken, setVercelToken] = useState('')
const [projectId, setProjectId] = useState('')
const [teamId, setTeamId] = useState('')
const [loading, setLoading] = useState(false)
const [result, setResult] = useState(null)
const [preconfig, setPreconfig] = useState(null)
// 自动加载预配置的 Vercel 信息
useEffect(() => {
const loadPreconfig = async () => {
try {
const res = await fetch('/admin/vercel/config')
if (res.ok) {
const data = await res.json()
setPreconfig(data)
if (data.project_id) setProjectId(data.project_id)
if (data.team_id) setTeamId(data.team_id)
}
} catch (e) {
console.error('加载 Vercel 预配置失败:', e)
}
}
loadPreconfig()
}, [])
const handleSync = async () => {
// 如果预配置了 token使用特殊标记让后端使用预配置的 token
const tokenToUse = preconfig?.has_token && !vercelToken ? '__USE_PRECONFIG__' : vercelToken
if (!tokenToUse && !preconfig?.has_token) {
onMessage('error', '请填写 Vercel Token')
return
}
if (!projectId) {
onMessage('error', '请填写 Project ID')
return
}
setLoading(true)
setResult(null)
try {
const res = await fetch('/admin/vercel/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
vercel_token: vercelToken,
project_id: projectId,
team_id: teamId || undefined,
}),
})
const data = await res.json()
if (res.ok) {
setResult(data)
onMessage('success', data.message)
} else {
onMessage('error', data.detail || '同步失败')
}
} catch (e) {
onMessage('error', '网络错误')
} finally {
setLoading(false)
}
}
return (
<div className="section">
<div className="card">
<div className="card-title" style={{ marginBottom: '1rem' }}> Vercel 同步</div>
<div className="alert alert-info" style={{ marginBottom: '1rem' }}>
<strong>说明</strong> Vercel 30-60
</div>
<div className="form-group">
<label className="form-label">
Vercel Token
<a
href="https://vercel.com/account/tokens"
target="_blank"
rel="noopener noreferrer"
style={{ marginLeft: '0.5rem', fontSize: '0.8rem' }}
>
获取 Token
</a>
</label>
<input
type="password"
className="form-input"
placeholder="输入 Vercel API Token"
value={vercelToken}
onChange={e => setVercelToken(e.target.value)}
/>
</div>
<div className="form-group">
<label className="form-label">
Project ID
<span style={{ marginLeft: '0.5rem', fontSize: '0.8rem', color: 'var(--text-secondary)' }}>
(可在 Vercel 项目设置中找到)
</span>
</label>
<input
type="text"
className="form-input"
placeholder="prj_xxxxxxxxxxxx 或项目名称"
value={projectId}
onChange={e => setProjectId(e.target.value)}
/>
</div>
<div className="form-group">
<label className="form-label">
Team ID可选
<span style={{ marginLeft: '0.5rem', fontSize: '0.8rem', color: 'var(--text-secondary)' }}>
(个人项目无需填写)
</span>
</label>
<input
type="text"
className="form-input"
placeholder="team_xxxxxxxxxxxx"
value={teamId}
onChange={e => setTeamId(e.target.value)}
/>
</div>
<button
className="btn btn-primary"
onClick={handleSync}
disabled={loading}
style={{ width: '100%' }}
>
{loading ? (
<>
<span className="loading"></span>
同步中...
</>
) : (
'🚀 同步到 Vercel 并重新部署'
)}
</button>
</div>
{result && (
<div className="card">
<div className="card-title" style={{ marginBottom: '1rem' }}>同步结果</div>
<div className={`alert ${result.success ? 'alert-success' : 'alert-error'}`}>
{result.message}
</div>
{result.deployment_url && (
<p>
部署地址
<a
href={`https://${result.deployment_url}`}
target="_blank"
rel="noopener noreferrer"
style={{ color: 'var(--accent)' }}
>
{result.deployment_url}
</a>
</p>
)}
{result.manual_deploy_required && (
<p style={{ color: 'var(--warning)' }}>
需要手动在 Vercel 控制台触发重新部署
</p>
)}
</div>
)}
<div className="card">
<div className="card-title" style={{ marginBottom: '1rem' }}>📖 使用说明</div>
<ol style={{ paddingLeft: '1.5rem', color: 'var(--text-secondary)' }}>
<li style={{ marginBottom: '0.5rem' }}>
前往 <a href="https://vercel.com/account/tokens" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Vercel Token 页面</a> Token
</li>
<li style={{ marginBottom: '0.5rem' }}>
Vercel 项目设置中找到 Project IDSettings General Project ID
</li>
<li style={{ marginBottom: '0.5rem' }}>
如果是团队项目还需要填写 Team ID
</li>
<li style={{ marginBottom: '0.5rem' }}>
点击同步按钮配置将自动更新到 Vercel 环境变量并触发重新部署
</li>
</ol>
</div>
</div>
)
}