import { useEffect, useState } from 'react'
import { Button, Badge } from '@/components/atoms'
import { Modal } from '@/components/common/Modal'
import { DataTable, type DataColumn } from '@/components/common/DataTable'
import { showToast } from '@/stores/toastStore'
import {
getPppoeStatus, connectPppoe, disconnectPppoe,
getPppoeAccounts, addPppoeAccount, deletePppoeAccount,
type PppoeStatus, type PppoeAccount,
} from '@/lib/api'
export function PppoePage() {
const [status, setStatus] = useState<PppoeStatus | null>(null)
const [accounts, setAccounts] = useState<PppoeAccount[]>([])
const [loading, setLoading] = useState(true)
const [connecting, setConnecting] = useState(false)
const [showAdd, setShowAdd] = useState(false)
const [form, setForm] = useState({ interfaceName: '', username: '', password: '' })
const fetchData = async () => {
try {
const [s, a] = await Promise.all([getPppoeStatus(), getPppoeAccounts()])
setStatus(s); setAccounts(a)
} catch { /* 静默 */ }
setLoading(false)
}
useEffect(() => { fetchData() }, [])
const handleConnect = async () => {
setConnecting(true)
try { await connectPppoe(); showToast('success', 'PPPoE 拨号已发起'); await fetchData() }
catch { showToast('error', '拨号失败') }
setConnecting(false)
}
const handleDisconnect = async () => {
setConnecting(true)
try { await disconnectPppoe(); showToast('success', 'PPPoE 已断开'); await fetchData() }
catch { showToast('error', '断开失败') }
setConnecting(false)
}
const handleAddAccount = async () => {
if (!form.interfaceName || !form.username || !form.password) { showToast('warning', '请填写完整信息'); return }
try { await addPppoeAccount(form); showToast('success', '账号已添加'); setShowAdd(false); setForm({ interfaceName: '', username: '', password: '' }); await fetchData() }
catch { showToast('error', '添加失败') }
}
const accColumns: DataColumn<PppoeAccount>[] = [
{ key: 'interfaceName', label: '接口', sortable: true },
{ key: 'username', label: '用户名', sortable: true },
{
key: 'enable', label: '状态', sortable: true,
render: (a) => <Badge variant={a.enable ? 'success' : 'default'}>{a.enable ? '启用' : '禁用'}</Badge>,
},
{
key: 'actions', label: '操作',
render: (a) => (
<Button variant="ghost" size="sm" onClick={async () => {
try { await deletePppoeAccount(a.id); showToast('success', '账号已删除'); await fetchData() }
catch { showToast('error', '删除失败') }
}}>删除</Button>
),
},
]
return (
<div className="p-6">
<h1 className="text-lg font-bold text-[var(--color-text-primary)] mb-4">PPPoE 拨号管理</h1>
{/* 拨号状态卡片 */}
<div className="glass-panel p-5 mb-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-sm font-semibold text-[var(--color-text-primary)]">拨号状态</h2>
<Badge variant={status?.connected ? 'success' : 'default'}>
{status?.connected ? '已连接' : '未连接'}
</Badge>
</div>
{status?.connected ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<StatItem label="接口" value={status.interfaceName || '-'} />
<StatItem label="IP 地址" value={status.ip || '-'} />
<StatItem label="在线时长" value={fmtUptime(status.uptime)} />
<StatItem label="状态" value="● 在线" />
</div>
) : (
<p className="text-sm text-[var(--color-text-tertiary)] mb-4">当前未拨号</p>
)}
<div className="flex gap-3">
<Button variant="primary" size="sm" disabled={connecting || status?.connected} onClick={handleConnect}>连接</Button>
<Button variant="secondary" size="sm" disabled={connecting || !status?.connected} onClick={handleDisconnect}>断开</Button>
</div>
</div>
{/* 账号列表 */}
<div>
<div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-semibold text-[var(--color-text-primary)]">宽带账号</h2>
<Button size="sm" variant="soft" onClick={() => setShowAdd(true)}>添加账号</Button>
</div>
<DataTable
data={accounts} columns={accColumns} rowKey={(a) => a.id}
searchFields={['interfaceName', 'username']} searchPlaceholder="搜索接口 / 用户名…"
loading={loading} emptyText="暂无宽带账号"
/>
</div>
{/* 添加账号弹窗 */}
<Modal open={showAdd} onClose={() => setShowAdd(false)} maxWidth="max-w-sm">
<div className="p-6">
<h3 className="text-sm font-semibold mb-4 text-[var(--color-text-primary)]">添加宽带账号</h3>
<div className="space-y-3">
<InputField label="接口名" value={form.interfaceName} onChange={(v) => setForm((f) => ({ ...f, interfaceName: v }))} placeholder="例如 ppp0" />
<InputField label="用户名" value={form.username} onChange={(v) => setForm((f) => ({ ...f, username: v }))} />
<InputField label="密码" value={form.password} onChange={(v) => setForm((f) => ({ ...f, password: v }))} type="password" />
</div>
<div className="flex justify-end gap-2 mt-4">
<Button variant="secondary" size="sm" onClick={() => setShowAdd(false)}>取消</Button>
<Button variant="primary" size="sm" onClick={handleAddAccount}>确定</Button>
</div>
</div>
</Modal>
</div>
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div>
<p className="text-xs text-[var(--color-text-tertiary)]">{label}</p>
<p className="text-sm font-medium text-[var(--color-text-primary)]">{value}</p>
</div>
)
}
function InputField({ label, value, onChange, placeholder, type = 'text' }: {
label: string; value: string; onChange: (v: string) => void; placeholder?: string; type?: string
}) {
return (
<label className="block">
<span className="text-xs text-[var(--color-text-secondary)]">{label}</span>
<input type={type} value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder}
className="mt-1 w-full px-3 py-2 text-sm rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-0)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-brand-500)]/40" />
</label>
)
}
function fmtUptime(seconds: number): string {
if (seconds < 60) return `${seconds}秒`
if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟`
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
return `${h}小时${m}分`
}
|