新增 debug 页面
This commit is contained in:
18
src/actions/config.ts
Normal file
18
src/actions/config.ts
Normal 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
96
src/actions/remote.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
108
src/app/debug/config/page.tsx
Normal file
108
src/app/debug/config/page.tsx
Normal 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
11
src/app/debug/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
9
src/lib/redis.ts
Normal 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
|
||||
Reference in New Issue
Block a user