新增 debug 页面
This commit is contained in:
10
.env.example
Normal file
10
.env.example
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 数据库连接字符串
|
||||||
|
DATABASE_URL=
|
||||||
|
|
||||||
|
# Redis 连接字符串
|
||||||
|
REDIS_URL=
|
||||||
|
|
||||||
|
# 京东网关配置
|
||||||
|
JD_BASE=https://smart.jdbox.xyz:58001
|
||||||
|
JD_USERNAME=
|
||||||
|
JD_PASSWORD=
|
||||||
15
bun.lock
15
bun.lock
@@ -19,6 +19,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
|
"redis": "^5.8.2",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"zustand": "^5.0.8",
|
"zustand": "^5.0.8",
|
||||||
@@ -302,6 +303,16 @@
|
|||||||
|
|
||||||
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
|
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
|
||||||
|
|
||||||
|
"@redis/bloom": ["@redis/bloom@5.8.2", "https://registry.npmmirror.com/@redis/bloom/-/bloom-5.8.2.tgz", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-855DR0ChetZLarblio5eM0yLwxA9Dqq50t8StXKp5bAtLT0G+rZ+eRzzqxl37sPqQKjUudSYypz55o6nNhbz0A=="],
|
||||||
|
|
||||||
|
"@redis/client": ["@redis/client@5.8.2", "https://registry.npmmirror.com/@redis/client/-/client-5.8.2.tgz", { "dependencies": { "cluster-key-slot": "1.1.2" } }, "sha512-WtMScno3+eBpTac1Uav2zugXEoXqaU23YznwvFgkPwBQVwEHTDgOG7uEAObtZ/Nyn8SmAMbqkEubJaMOvnqdsQ=="],
|
||||||
|
|
||||||
|
"@redis/json": ["@redis/json@5.8.2", "https://registry.npmmirror.com/@redis/json/-/json-5.8.2.tgz", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-uxpVfas3I0LccBX9rIfDgJ0dBrUa3+0Gc8sEwmQQH0vHi7C1Rx1Qn8Nv1QWz5bohoeIXMICFZRcyDONvum2l/w=="],
|
||||||
|
|
||||||
|
"@redis/search": ["@redis/search@5.8.2", "https://registry.npmmirror.com/@redis/search/-/search-5.8.2.tgz", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-cNv7HlgayavCBXqPXgaS97DRPVWFznuzsAmmuemi2TMCx5scwLiP50TeZvUS06h/MG96YNPe6A0Zt57yayfxwA=="],
|
||||||
|
|
||||||
|
"@redis/time-series": ["@redis/time-series@5.8.2", "https://registry.npmmirror.com/@redis/time-series/-/time-series-5.8.2.tgz", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-g2NlHM07fK8H4k+613NBsk3y70R2JIM2dPMSkhIjl2Z17SYvaYKdusz85d7VYOrZBWtDrHV/WD2E3vGu+zni8A=="],
|
||||||
|
|
||||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||||
|
|
||||||
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.12.0", "", {}, "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw=="],
|
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.12.0", "", {}, "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw=="],
|
||||||
@@ -490,6 +501,8 @@
|
|||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"cluster-key-slot": ["cluster-key-slot@1.1.2", "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||||
|
|
||||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||||
|
|
||||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
@@ -908,6 +921,8 @@
|
|||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"redis": ["redis@5.8.2", "https://registry.npmmirror.com/redis/-/redis-5.8.2.tgz", { "dependencies": { "@redis/bloom": "5.8.2", "@redis/client": "5.8.2", "@redis/json": "5.8.2", "@redis/search": "5.8.2", "@redis/time-series": "5.8.2" } }, "sha512-31vunZj07++Y1vcFGcnNWEf5jPoTkGARgfWI4+Tk55vdwHxhAvug8VEtW7Cx+/h47NuJTEg/JL77zAwC6E0OeA=="],
|
||||||
|
|
||||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||||
|
|
||||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10
|
image: mariadb:10
|
||||||
environment:
|
environment:
|
||||||
@@ -7,4 +8,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "23306:3306"
|
- "23306:3306"
|
||||||
volumes:
|
volumes:
|
||||||
- .volumes/mysql:/var/lib/mysql
|
- .volumes/mysql:/var/lib/mysql
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
ports:
|
||||||
|
- "26379:6379"
|
||||||
|
volumes:
|
||||||
|
- .volumes/redis:/data
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
|
"redis": "^5.8.2",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
-- jdbox.accounts definition
|
|
||||||
CREATE TABLE `accounts` (
|
|
||||||
`id` varchar(191) NOT NULL,
|
|
||||||
`user_id` int(11) NOT NULL,
|
|
||||||
`type` varchar(191) NOT NULL,
|
|
||||||
`provider` varchar(191) NOT NULL,
|
|
||||||
`provider_account_id` varchar(191) NOT NULL,
|
|
||||||
`refresh_token` text DEFAULT NULL,
|
|
||||||
`access_token` text DEFAULT NULL,
|
|
||||||
`expires_at` int(11) DEFAULT NULL,
|
|
||||||
`token_type` varchar(191) DEFAULT NULL,
|
|
||||||
`scope` varchar(191) DEFAULT NULL,
|
|
||||||
`id_token` text DEFAULT NULL,
|
|
||||||
`session_state` varchar(191) DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `accounts_provider_provider_account_id_key` (`provider`, `provider_account_id`),
|
|
||||||
KEY `accounts_user_id_fkey` (`user_id`) USING BTREE
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
|
||||||
-- jdbox.sessions definition
|
-- jdbox.sessions definition
|
||||||
CREATE TABLE `sessions` (
|
CREATE TABLE `sessions` (
|
||||||
`id` varchar(191) NOT NULL,
|
`id` varchar(191) NOT NULL,
|
||||||
@@ -25,6 +7,7 @@ CREATE TABLE `sessions` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `sessions_userId_idx` (`userId`) USING BTREE
|
KEY `sessions_userId_idx` (`userId`) USING BTREE
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- jdbox.users definition
|
-- jdbox.users definition
|
||||||
CREATE TABLE `users` (
|
CREATE TABLE `users` (
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
@@ -36,17 +19,6 @@ CREATE TABLE `users` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `users_phone_key` (`phone`)
|
UNIQUE KEY `users_phone_key` (`phone`)
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
-- jdbox.verification_codes definition
|
|
||||||
CREATE TABLE `verification_codes` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`phone` varchar(191) NOT NULL,
|
|
||||||
`code` varchar(191) NOT NULL,
|
|
||||||
`type` varchar(191) NOT NULL,
|
|
||||||
`expiresAt` datetime(3) NOT NULL,
|
|
||||||
`createdAt` datetime(3) NOT NULL DEFAULT current_timestamp(3),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `verification_codes_phone_type_idx` (`phone`, `type`) USING BTREE
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- 插入初始用户
|
-- 插入初始用户
|
||||||
INSERT INTO users(phone, password, name)
|
INSERT INTO users(phone, password, name)
|
||||||
|
|||||||
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'
|
import { PrismaClient } from '@/generated/prisma/client'
|
||||||
|
|
||||||
const globalForPrisma = global as unknown as {
|
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