mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-12 04:07:42 +08:00
feat: Localize UI strings to Chinese and add new API documentation.
This commit is contained in:
@@ -133,7 +133,7 @@ export default function App() {
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
|
||||
<p className="text-muted-foreground animate-pulse">检查登录状态...</p>
|
||||
<p className="text-muted-foreground animate-pulse">正在检查登录状态...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -184,7 +184,7 @@ export default function App() {
|
||||
</div>
|
||||
<span>DS2API</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground mt-2 font-semibold tracking-[0.1em] uppercase opacity-60 px-1">V1.0.0 Admin Panel</p>
|
||||
<p className="text-[10px] text-muted-foreground mt-2 font-semibold tracking-[0.1em] uppercase opacity-60 px-1">V1.0.0 管理面板</p>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 px-3 space-y-1 overflow-y-auto pt-2">
|
||||
@@ -216,20 +216,20 @@ export default function App() {
|
||||
<div className="p-4 border-t border-border bg-card">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between text-sm px-1">
|
||||
<span className="text-muted-foreground font-semibold text-[10px] uppercase tracking-wider">System Status</span>
|
||||
<span className="text-muted-foreground font-semibold text-[10px] uppercase tracking-wider">系统状态</span>
|
||||
<span className="flex items-center gap-1.5 text-[10px] font-bold text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
||||
ONLINE
|
||||
在线
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="bg-background rounded-lg p-3 border border-border shadow-sm">
|
||||
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">Accounts</div>
|
||||
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">账号</div>
|
||||
<div className="text-lg font-bold text-foreground leading-tight">{config.accounts?.length || 0}</div>
|
||||
</div>
|
||||
<div className="bg-background rounded-lg p-3 border border-border shadow-sm">
|
||||
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">API Keys</div>
|
||||
<div className="text-lg font-bold text-foreground leading-tight">{config.keys?.length || 0}</div>
|
||||
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">密钥</div>
|
||||
<div className="text-lg font-bold text-foreground">{config.keys?.length || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
body: JSON.stringify({ key: newKey.trim() }),
|
||||
})
|
||||
if (res.ok) {
|
||||
onMessage('success', 'API Key added successfully')
|
||||
onMessage('success', 'API 密钥添加成功')
|
||||
setNewKey('')
|
||||
setShowAddKey(false)
|
||||
onRefresh()
|
||||
@@ -66,14 +66,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
onMessage('error', data.detail || 'Failed to add')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error')
|
||||
onMessage('error', '网络错误')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteKey = async (key) => {
|
||||
if (!confirm('Are you sure you want to delete this API Key?')) return
|
||||
if (!confirm('确定要删除此 API 密钥吗?')) return
|
||||
try {
|
||||
const res = await apiFetch(`/admin/keys/${encodeURIComponent(key)}`, { method: 'DELETE' })
|
||||
if (res.ok) {
|
||||
@@ -100,7 +100,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
body: JSON.stringify(newAccount),
|
||||
})
|
||||
if (res.ok) {
|
||||
onMessage('success', 'Account added successfully')
|
||||
onMessage('success', '账号添加成功')
|
||||
setNewAccount({ email: '', mobile: '', password: '' })
|
||||
setShowAddAccount(false)
|
||||
onRefresh()
|
||||
@@ -109,14 +109,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
onMessage('error', data.detail || 'Failed to add')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error')
|
||||
onMessage('error', '网络错误')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAccount = async (id) => {
|
||||
if (!confirm('Are you sure you want to delete this account?')) return
|
||||
if (!confirm('确定要删除此账号吗?')) return
|
||||
try {
|
||||
const res = await apiFetch(`/admin/accounts/${encodeURIComponent(id)}`, { method: 'DELETE' })
|
||||
if (res.ok) {
|
||||
@@ -149,7 +149,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
}
|
||||
|
||||
const validateAllAccounts = async () => {
|
||||
if (!confirm('Validate ALL accounts? This might take a while.')) return
|
||||
if (!confirm('校验所有账号?这可能需要一些时间。')) return
|
||||
const accounts = config.accounts || []
|
||||
if (accounts.length === 0) return
|
||||
|
||||
@@ -203,7 +203,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
}
|
||||
|
||||
const testAllAccounts = async () => {
|
||||
if (!confirm('Test API connectivity for ALL accounts?')) return
|
||||
if (!confirm('测试所有账号的 API 连通性?')) return
|
||||
const accounts = config.accounts || []
|
||||
if (accounts.length === 0) return
|
||||
|
||||
@@ -248,30 +248,30 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<div className="absolute right-0 top-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<CheckCircle2 className="w-16 h-16" />
|
||||
</div>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">Available</p>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">可用</p>
|
||||
<div className="mt-2 flex items-baseline gap-2">
|
||||
<span className="text-3xl font-bold text-foreground">{queueStatus.available}</span>
|
||||
<span className="text-xs text-muted-foreground">accounts</span>
|
||||
<span className="text-xs text-muted-foreground">个账号</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-card border border-border rounded-xl p-4 flex flex-col justify-between shadow-sm relative overflow-hidden group">
|
||||
<div className="absolute right-0 top-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<Server className="w-16 h-16" />
|
||||
</div>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">In Use</p>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">正在使用</p>
|
||||
<div className="mt-2 flex items-baseline gap-2">
|
||||
<span className="text-3xl font-bold text-foreground">{queueStatus.in_use}</span>
|
||||
<span className="text-xs text-muted-foreground">threads</span>
|
||||
<span className="text-xs text-muted-foreground">线程</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-card border border-border rounded-xl p-4 flex flex-col justify-between shadow-sm relative overflow-hidden group">
|
||||
<div className="absolute right-0 top-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<ShieldCheck className="w-16 h-16" />
|
||||
</div>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">Total Pool</p>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">账号池总数</p>
|
||||
<div className="mt-2 flex items-baseline gap-2">
|
||||
<span className="text-3xl font-bold text-foreground">{queueStatus.total}</span>
|
||||
<span className="text-xs text-muted-foreground">accounts</span>
|
||||
<span className="text-xs text-muted-foreground">个账号</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -290,7 +290,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium text-sm shadow-sm"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Key
|
||||
添加密钥
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -329,7 +329,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
className="flex items-center px-3 py-2 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-xs font-medium border border-border disabled:opacity-50"
|
||||
>
|
||||
{testingAll ? <span className="animate-spin mr-2">⟳</span> : <Play className="w-3 h-3 mr-2" />}
|
||||
Test All
|
||||
测试全部
|
||||
</button>
|
||||
<button
|
||||
onClick={validateAllAccounts}
|
||||
@@ -337,14 +337,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
className="flex items-center px-3 py-2 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-xs font-medium border border-border disabled:opacity-50"
|
||||
>
|
||||
{validatingAll ? <span className="animate-spin mr-2">⟳</span> : <CheckCircle2 className="w-3 h-3 mr-2" />}
|
||||
Validate All
|
||||
校验全部
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAddAccount(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium text-sm shadow-sm"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Account
|
||||
添加账号
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -455,9 +455,9 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button onClick={() => setShowAddKey(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">Cancel</button>
|
||||
<button onClick={() => setShowAddKey(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">取消</button>
|
||||
<button onClick={addKey} disabled={loading} className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium disabled:opacity-50">
|
||||
{loading ? 'Adding...' : 'Add Key'}
|
||||
{loading ? '添加中...' : '添加密钥'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -508,9 +508,9 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button onClick={() => setShowAddAccount(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">Cancel</button>
|
||||
<button onClick={() => setShowAddAccount(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">取消</button>
|
||||
<button onClick={addAccount} disabled={loading} className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium disabled:opacity-50">
|
||||
{loading ? 'Adding...' : 'Add Account'}
|
||||
{loading ? '添加中...' : '添加账号'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,8 +19,10 @@ import {
|
||||
import clsx from 'clsx'
|
||||
|
||||
const MODELS = [
|
||||
{ id: "deepseek-chat", name: "deepseek-chat", icon: MessageSquare, desc: "通用高智商模型 (V3)", color: "text-amber-500" },
|
||||
{ id: "deepseek-reasoner", name: "deepseek-reasoner", icon: Cpu, desc: "深度推理/思维链 (R1)", color: "text-amber-600" },
|
||||
{ id: "deepseek-chat", name: "deepseek-chat", icon: MessageSquare, desc: "非思考模型", color: "text-amber-500" },
|
||||
{ id: "deepseek-reasoner", name: "deepseek-reasoner", icon: Cpu, desc: "思考模型", color: "text-amber-600" },
|
||||
{ id: "deepseek-chat-search", name: "deepseek-chat-search", icon: SearchIcon, desc: "非思考模型 (带搜索)", color: "text-cyan-500" },
|
||||
{ id: "deepseek-reasoner-search", name: "deepseek-reasoner-search", icon: SearchIcon, desc: "思考模型 (带搜索)", color: "text-cyan-600" },
|
||||
];
|
||||
|
||||
export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
@@ -116,13 +118,16 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
|
||||
try {
|
||||
const json = JSON.parse(dataStr)
|
||||
console.log('[ApiTester] Parsed JSON:', json)
|
||||
const choice = json.choices?.[0]
|
||||
if (choice?.delta) {
|
||||
const delta = choice.delta
|
||||
console.log('[ApiTester] Delta:', delta)
|
||||
if (delta.reasoning_content) {
|
||||
setStreamingThinking(prev => prev + delta.reasoning_content)
|
||||
}
|
||||
if (delta.content) {
|
||||
console.log('[ApiTester] Content:', delta.content)
|
||||
setStreamingContent(prev => prev + delta.content)
|
||||
}
|
||||
}
|
||||
@@ -135,7 +140,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
if (e.name === 'AbortError') {
|
||||
onMessage('info', '已停止生成')
|
||||
} else {
|
||||
onMessage('error', 'Network error: ' + e.message)
|
||||
onMessage('error', '网络错误: ' + e.message)
|
||||
setResponse({ error: e.message, success: false })
|
||||
}
|
||||
} finally {
|
||||
@@ -167,12 +172,12 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
account: selectedAccount,
|
||||
})
|
||||
if (data.success) {
|
||||
onMessage('success', `${selectedAccount}: Test Success (${data.response_time}ms)`)
|
||||
onMessage('success', `${selectedAccount}: 测试成功 (${data.response_time}ms)`)
|
||||
} else {
|
||||
onMessage('error', `${selectedAccount}: ${data.message}`)
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error: ' + e.message)
|
||||
onMessage('error', '网络错误: ' + e.message)
|
||||
setResponse({ error: e.message })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -229,7 +234,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
>
|
||||
<div className={clsx(
|
||||
"p-1.5 rounded-md shrink-0 transition-colors",
|
||||
model === m.id ? "text-primary" : "text-muted-foreground group-hover:text-foreground"
|
||||
model === m.id ? m.color : "text-muted-foreground group-hover:text-foreground"
|
||||
)}>
|
||||
<Icon className="w-4 h-4" />
|
||||
</div>
|
||||
@@ -240,7 +245,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<div className="text-[11px] text-muted-foreground mt-0.5">{m.desc}</div>
|
||||
</div>
|
||||
{model === m.id && (
|
||||
<div className="absolute top-3 right-3 text-primary">
|
||||
<div className={clsx("absolute top-3 right-3", m.color)}>
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-current" />
|
||||
</div>
|
||||
)}
|
||||
@@ -274,7 +279,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<input
|
||||
type="password"
|
||||
className="w-full h-10 px-3 bg-muted/30 border border-border rounded-lg text-sm font-mono placeholder:text-muted-foreground/40 focus:outline-none focus:ring-1 focus:ring-ring focus:border-ring transition-all"
|
||||
placeholder={config.keys?.[0] ? `默认: ...${config.keys[0].slice(-6)}` : '输入自定义 Key'}
|
||||
placeholder={config.keys?.[0] ? `默认: ...${config.keys[0].slice(-6)}` : '输入自定义密钥'}
|
||||
value={apiKey}
|
||||
onChange={e => setApiKey(e.target.value)}
|
||||
/>
|
||||
@@ -319,7 +324,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
"text-[10px] px-1.5 py-0.5 rounded-sm border uppercase font-medium tracking-wider",
|
||||
response.success ? "border-emerald-500/20 text-emerald-500 bg-emerald-500/10" : "border-destructive/20 text-destructive bg-destructive/10"
|
||||
)}>
|
||||
{response.status_code || 'ERR'}
|
||||
{response.status_code || '错误'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -328,7 +333,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<div className="text-xs bg-secondary/50 border border-border rounded-lg p-3 space-y-1.5">
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<Zap className="w-3.5 h-3.5" />
|
||||
<span className="font-medium">Thinking Process</span>
|
||||
<span className="font-medium">思维链过程</span>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap leading-relaxed text-muted-foreground font-mono text-[11px] max-h-60 overflow-y-auto custom-scrollbar pl-5 border-l-2 border-border/50">
|
||||
{streamingThinking || response?.response?.thinking}
|
||||
@@ -340,7 +345,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
{!selectedAccount ? (
|
||||
streamingContent || (response?.error && <span className="text-destructive font-medium">{response.error}</span>)
|
||||
) : (
|
||||
response?.response?.message || <span className="text-muted-foreground italic">Generating response...</span>
|
||||
response?.response?.message || <span className="text-muted-foreground italic">正在生成响应...</span>
|
||||
)}
|
||||
{isStreaming && <span className="inline-block w-1.5 h-4 bg-primary ml-1 align-middle animate-pulse" />}
|
||||
</div>
|
||||
@@ -354,7 +359,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<div className="max-w-4xl mx-auto relative group">
|
||||
<textarea
|
||||
className="w-full bg-[#09090b] border border-border rounded-xl pl-4 pr-12 py-3 text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all resize-none custom-scrollbar placeholder:text-muted-foreground/50 text-foreground shadow-inner"
|
||||
placeholder="Type a message..."
|
||||
placeholder="输入消息..."
|
||||
rows={1}
|
||||
style={{ minHeight: '52px' }}
|
||||
value={message}
|
||||
@@ -386,7 +391,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto mt-3 flex justify-center">
|
||||
<span className="text-[10px] text-muted-foreground/40 font-medium">DeepSeek Admin Interface</span>
|
||||
<span className="text-[10px] text-muted-foreground/40 font-medium">DeepSeek 管理员界面</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
onMessage('error', data.detail || '导入失败')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error')
|
||||
onMessage('error', '网络错误')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
className="w-full flex items-center justify-center gap-2 py-2.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all font-medium text-sm shadow-sm"
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
||||
{copied ? 'Copied' : 'Copy Base64 Config'}
|
||||
{copied ? '已复制' : '复制 Base64 配置'}
|
||||
</button>
|
||||
<p className="text-[10px] text-muted-foreground mt-2 text-center">
|
||||
变量名: <code className="bg-background px-1 py-0.5 rounded border border-border">DS2API_CONFIG_JSON</code>
|
||||
@@ -186,10 +186,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
</h3>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={handleExport} className="px-3 py-1.5 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-xs font-medium border border-border">
|
||||
Load Current
|
||||
加载当前配置
|
||||
</button>
|
||||
<button onClick={handleImport} disabled={loading} className="px-3 py-1.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors text-xs font-medium disabled:opacity-50">
|
||||
{loading ? 'Importing...' : 'Apply Config'}
|
||||
{loading ? '正在导入...' : '应用配置'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { Key, ArrowRight, ShieldCheck, Lock } from 'lucide-react'
|
||||
import { Key, ArrowRight, ShieldCheck, Lock, Check } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default function Login({ onLogin, onMessage }) {
|
||||
@@ -46,69 +46,55 @@ export default function Login({ onLogin, onMessage }) {
|
||||
|
||||
<div className="w-full max-w-[400px] relative z-10 animate-in fade-in zoom-in-95 duration-200">
|
||||
<div className="w-full bg-card border border-border rounded-xl p-8 shadow-sm">
|
||||
<div className="text-center space-y-2 mb-8">
|
||||
<div className="text-center space-y-2 mb-8 animate-in fade-in slide-in-from-top-4 duration-500">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-2">
|
||||
<Lock className="w-6 h-6" />
|
||||
</div>
|
||||
<h1 className="text-xl font-semibold tracking-tight text-foreground">
|
||||
欢迎回来
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
请输入管理员密钥继续
|
||||
</p>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground">欢迎回来</h1>
|
||||
<p className="text-sm text-muted-foreground/80">请输入管理员密钥以继续</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium text-muted-foreground ml-0.5">
|
||||
管理员密钥
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground/50 transition-colors">
|
||||
<Key className="w-4 h-4" />
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="admin_key"
|
||||
className="w-full h-12 px-4 bg-[#09090b] border border-border rounded-lg text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-foreground font-mono"
|
||||
placeholder="••••••••••••••••"
|
||||
value={adminKey}
|
||||
onChange={(e) => setAdminKey(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
|
||||
/>
|
||||
<form onSubmit={handleLogin} className="space-y-5 animate-in fade-in slide-in-from-bottom-4 duration-700 delay-150">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-widest ml-1">管理员密钥</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
|
||||
<Key className="w-4 h-4" />
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full bg-[#09090b] border border-border rounded-xl pl-10 pr-4 py-3 text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all placeholder:text-muted-foreground/30 text-foreground"
|
||||
placeholder="输入您的管理员密钥..."
|
||||
value={adminKey}
|
||||
onChange={e => setAdminKey(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<button
|
||||
type="button"
|
||||
role="checkbox"
|
||||
aria-checked={remember}
|
||||
onClick={() => setRemember(!remember)}
|
||||
className={clsx(
|
||||
"w-4 h-4 rounded border flex items-center justify-center transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-ring/40",
|
||||
remember ? "bg-primary border-primary text-primary-foreground" : "border-muted-foreground/40 bg-transparent hover:border-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{remember && <div className="w-2 h-2 rounded-[1px] bg-current" />}
|
||||
</button>
|
||||
<span
|
||||
onClick={() => setRemember(!remember)}
|
||||
className="text-xs text-muted-foreground cursor-pointer select-none hover:text-foreground transition-colors"
|
||||
>
|
||||
记住登录状态
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<label className="flex items-center gap-2.5 cursor-pointer group">
|
||||
<div className="relative flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="peer sr-only"
|
||||
checked={remember}
|
||||
onChange={e => setRemember(e.target.checked)}
|
||||
/>
|
||||
<div className="w-4.5 h-4.5 bg-secondary border border-border rounded-md peer-checked:bg-primary peer-checked:border-primary transition-all shadow-sm"></div>
|
||||
<Check className="absolute w-3 h-3 text-primary-foreground opacity-0 peer-checked:opacity-100 left-0.5 transition-opacity" />
|
||||
</div>
|
||||
<span className="text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors">记住登录状态</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full flex items-center justify-center py-2.5 px-4 rounded-lg bg-primary hover:bg-primary/90 text-primary-foreground font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="w-full h-12 flex items-center justify-center gap-2 bg-primary text-primary-foreground rounded-xl hover:bg-primary/90 transition-all font-semibold text-sm shadow-lg shadow-primary/20 hover:shadow-primary/30 disabled:opacity-50 disabled:shadow-none"
|
||||
>
|
||||
{loading ? (
|
||||
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-5 h-5 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span>登录</span>
|
||||
@@ -121,13 +107,13 @@ export default function Login({ onLogin, onMessage }) {
|
||||
<div className="mt-6 pt-6 border-t border-border flex justify-center">
|
||||
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground/60 font-medium tracking-wide uppercase">
|
||||
<ShieldCheck className="w-3 h-3" />
|
||||
<span>Secured Connection</span>
|
||||
<span>安全连接</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-[10px] text-muted-foreground/30 font-mono">DS2API Admin Portal</p>
|
||||
<p className="text-[10px] text-muted-foreground/30 font-mono text-center">DS2API 管理员门户</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
onMessage('error', data.detail || '同步失败')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error')
|
||||
onMessage('error', '网络错误')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
<input
|
||||
type="password"
|
||||
className="w-full h-10 px-3 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-all pr-10"
|
||||
placeholder={preconfig?.has_token ? "Using pre-configured token" : "Enter Vercel Access Token"}
|
||||
placeholder={preconfig?.has_token ? "正在使用预配置的令牌" : "输入 Vercel 访问令牌"}
|
||||
value={vercelToken}
|
||||
onChange={e => setVercelToken(e.target.value)}
|
||||
/>
|
||||
@@ -140,11 +140,11 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
{loading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||
Syncing...
|
||||
正在同步...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-2">
|
||||
Sync & Redeploy <ArrowRight className="w-4 h-4" />
|
||||
同步并重新部署 <ArrowRight className="w-4 h-4" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user