新增 debug 页面

This commit is contained in:
2025-09-23 11:40:12 +08:00
parent 2c106e43df
commit 2d5e334a5c
11 changed files with 279 additions and 30 deletions

18
src/actions/config.ts Normal file
View File

@@ -0,0 +1,18 @@
'use server'
import prisma from '@/lib/prisma'
import { log } from 'console'
export async function findConfigs(params: {
macaddr: string
}) {
try {
return await prisma.gateway.findMany({
where: {
macaddr: params.macaddr,
},
})
}
catch (e) {
throw new Error('查询配置失败: ' + (e as Error).message)
}
}

96
src/actions/remote.ts Normal file
View File

@@ -0,0 +1,96 @@
'use server'
import redis from '@/lib/redis'
const base = process.env.JD_BASE
const username = process.env.JD_USERNAME
const password = process.env.JD_PASSWORD
type JdResp<T> = {
code: number
meta: string
data: T
}
async function post<O>(path: string, data: unknown) {
try {
if (!base) throw new Error('JD_BASE 环境变量未设置')
if (!username) throw new Error('JD_USERNAME 环境变量未设置')
if (!password) throw new Error('JD_PASSWORD 环境变量未设置')
// 获取令牌
let token = await redis.get('token')
if (!token) {
const resp = await fetch(`${base}/client/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
}),
})
const json = await resp.json()
if (json.code !== 0) {
throw new Error('响应失败: ' + json.meta)
}
token = json.data
if (!token) {
throw new Error('响应中缺少 token')
}
await redis.set('token', token, {
expiration: { type: 'EX', value: 6 * 24 * 3600 },
})
}
// 发起请求
const resp = await fetch(base + path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Token': token,
},
body: JSON.stringify(data),
})
if (resp.status === 401) {
await redis.del('token')
throw new Error('令牌无效,已删除缓存,请重试')
}
return await resp.json() as JdResp<O>
}
catch (e) {
throw new Error('请求失败: ' + (e as Error).message)
}
}
export async function gatewayConfigGet(params: {
macaddr: string
}) {
try {
const resp = await post<string>('/gateway/config/get', params)
if (resp.code !== 0) {
throw new Error('响应失败: ' + resp.meta)
}
if (!resp.data) {
throw new Error('响应中缺少 data')
}
return JSON.parse(atob(resp.data)) as {
id: number
rules: {
table: number
enable: boolean
edge: string[]
network: string[]
cityhash: string
}[]
}
}
catch (e) {
throw new Error('获取远程配置失败: ' + (e as Error).message)
}
}

View File

@@ -0,0 +1,108 @@
'use client'
import { findConfigs } from '@/actions/config'
import { gatewayConfigGet } from '@/actions/remote'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
type EdgeConfig = {
port?: string
edge?: string
city?: string
_index: number
}
export default function DebugConfigPage() {
const [macaddr, setMacaddr] = useState('')
const [remotes, setRemotes] = useState<EdgeConfig[]>([])
const [locals, setLocals] = useState<EdgeConfig[]>([])
const fetch = async (macaddr: string) => {
try {
console.log('fetch', macaddr)
if (!macaddr) return
const rawLocal = await findConfigs({ macaddr })
console.log('raw local', rawLocal)
const rawRemote = await gatewayConfigGet({ macaddr })
console.log('raw remote', rawRemote)
setLocals(rawLocal.map(rule => ({
port: rule.network,
edge: rule.edge,
city: rule.cityhash,
_index: parseInt(rule.network.split('.')[3] || '0'),
})).sort((a, b) => a._index - b._index))
setRemotes(rawRemote.rules.map((rule) => {
const port = rule.network.find(n => !!n)
return ({
port: port,
edge: rule.edge.find(n => !!n),
city: rule.cityhash,
_index: port ? parseInt(port.split('.')[3]) : 0,
})
}).sort((a, b) => a._index - b._index))
}
catch (e) {
console.error('数据获取失败', e)
}
}
return (
<div className="flex-auto overflow-hidden flex flex-col p-6 gap-4.5">
<div className="flex-none flex gap-3">
<Input type="text" name="macaddr" value={macaddr} onChange={e => setMacaddr(e.target.value)} className="flex-none basis-60" />
<Button onClick={() => fetch(macaddr)}></Button>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{!locals.length || !remotes.length ? (
<TableRow>
<TableCell colSpan={3} className="text-center"></TableCell>
</TableRow>
) : locals.length !== remotes.length ? (
<TableRow>
<TableCell colSpan={3} className="text-center"></TableCell>
</TableRow>
) : (
locals.map((item, index) => (
<TableRow key={index}>
<TableCell>
{item.port === remotes[index].port ? (
<span className="text-green-500">{item.port}</span>
) : (
<span className="text-red-500">{item.port} : {remotes[index].port}</span>
)}
</TableCell>
<TableCell>
{item.edge === remotes[index].edge ? (
<span className="text-green-500">{item.edge}</span>
) : (
<span className="text-red-500">{item.edge} : {remotes[index].edge}</span>
)}
</TableCell>
<TableCell>
{item.city === remotes[index].city ? (
<span className="text-green-500">{item.city}</span>
) : (
<span className="text-red-500">{item.city} : {remotes[index].city}</span>
)}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
)
}

11
src/app/debug/layout.tsx Normal file
View File

@@ -0,0 +1,11 @@
import { ReactNode } from 'react'
export default async function DebugLayout(props: {
children: ReactNode
}) {
return (
<div className="w-screen h-screen flex flex-col">
{props.children}
</div>
)
}

View File

@@ -1,3 +1,4 @@
import 'server-only'
import { PrismaClient } from '@/generated/prisma/client'
const globalForPrisma = global as unknown as {

9
src/lib/redis.ts Normal file
View File

@@ -0,0 +1,9 @@
import 'server-only'
import { createClient } from 'redis'
const client = createClient({
url: process.env.REDIS_URL,
})
const redis = await client.connect()
export default redis