Files
admin/src/app/(root)/gateway/page.tsx
2026-06-29 17:44:28 +08:00

230 lines
6.9 KiB
TypeScript

"use client"
import { format } from "date-fns"
import { Suspense, useCallback, useState } from "react"
import { toast } from "sonner"
import {
clear,
createProxyChains,
createProxyPort,
getGatewayPage,
updateGateway,
} from "@/actions/gateway"
import { Auth } from "@/components/auth"
import { DataTable, useDataTable } from "@/components/data-table"
import { Page } from "@/components/page"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { ScopeChannelWriteClearExpired, ScopeProxyWrite } from "@/lib/scopes"
import type { Gateway } from "@/models/gateway"
import CreatePage from "./create"
export default function GatewayPage() {
const [loading, setLoading] = useState(false)
const getGatewayPageWrapper = useCallback((page: number, size: number) => {
return getGatewayPage({ page, size })
}, [])
const table = useDataTable(getGatewayPageWrapper)
const handleToggle = async (id: number, status: number) => {
setLoading(true)
try {
const result = await updateGateway({
id: id,
status: status === 0 ? 1 : 0,
})
if (result.success) {
toast.success(status === 0 ? "启用成功" : "停用成功")
table.refresh()
} else {
toast.error(result.message || "操作失败")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`操作失败: ${message}`)
} finally {
setLoading(false)
}
}
const clearExpired = async (val: Gateway) => {
setLoading(true)
try {
const result = await clear({
proxy_id: val.id,
})
if (result.success) {
const count =
result.data && "count" in result.data ? result.data.count : 0
toast.success(`清理过期连接成功,共 ${count}`)
} else {
toast.error(result.message || "清理过期连接失败,请稍后重试一下~")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`清理过期连接失败: ${message}`)
} finally {
setLoading(false)
}
}
const createPort = async (val: Gateway) => {
setLoading(true)
try {
const result = await createProxyPort({
id: val.id,
})
if (result.success) {
toast.success(`重建端口池成功!`)
} else {
toast.error(result.message || "请稍后重试一下~")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`清理过期连接失败: ${message}`)
} finally {
setLoading(false)
}
}
const createChains = async (val: Gateway) => {
setLoading(true)
try {
const result = await createProxyChains({
id: val.id,
})
if (result.success) {
toast.success(`重建代理链成功!!!`)
} else {
toast.error(result.message || "请稍后重试一下~")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`清理过期连接失败: ${message}`)
} finally {
setLoading(false)
}
}
return (
<Page>
<div className="flex justify-between items-stretch">
<div className="flex gap-3">
<Auth scope={ScopeProxyWrite}>
<CreatePage onSuccess={table.refresh} />
</Auth>
</div>
</div>
<Suspense>
<DataTable<Gateway>
{...table}
status={loading ? "load" : "done"}
columns={[
{
header: "域名",
accessorKey: "host",
},
{ header: "IP地址", accessorKey: "ip" },
{ header: "端口", accessorKey: "port" },
{
header: "MAC地址",
accessorKey: "mac",
},
{
header: "密钥",
accessorKey: "secret",
},
{
header: "类型",
accessorKey: "type",
cell: ({ row }) => {
const typeMap: Record<number, string> = {
1: "自有",
2: "白银",
3: "GOST",
}
return typeMap[row.original.type] ?? ""
},
},
{
header: "状态",
accessorKey: "status",
cell: ({ row }) => (row.original.status === 0 ? "离线" : "在线"),
},
{
header: "创建时间",
accessorKey: "created_at",
cell: ({ row }) =>
format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
},
{
id: "action",
meta: { pin: "right" },
header: "操作",
cell: ({ row }) => {
return (
<div className="flex gap-2 ">
<Button
onClick={() =>
handleToggle(row.original.id, row.original.status)
}
disabled={loading}
variant={
row.original.status === 0 ? "default" : "destructive"
}
size="sm"
>
{row.original.status === 0 ? "启用" : "停用"}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline">
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-8">
<Auth scope={ScopeChannelWriteClearExpired}>
<DropdownMenuItem
onClick={() => clearExpired(row.original)}
>
</DropdownMenuItem>
</Auth>
<Auth scope={ScopeProxyWrite}>
<DropdownMenuItem
onClick={() => createPort(row.original)}
>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => createChains(row.original)}
>
</DropdownMenuItem>
</Auth>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
},
},
]}
/>
</Suspense>
</Page>
)
}