diff --git a/src/actions/auth.ts b/src/actions/auth.ts new file mode 100644 index 0000000..8bcbaa5 --- /dev/null +++ b/src/actions/auth.ts @@ -0,0 +1,115 @@ +'use server' +import prisma, { User } from '@/lib/prisma' // 使用统一的prisma实例 +import { compare } from 'bcryptjs' +import { cookies } from 'next/headers' +import { z } from 'zod' + +const loginSchema = z.object({ + account: z.string().min(3, '账号至少需要3个字符'), + password: z.string().min(6, '密码至少需要6个字符'), +}) + +export async function login(props: { + account: string + password: string +}) { + try { + const result = loginSchema.parse(props) + + const user: User | null = await prisma.user.findFirst({ + where: { + OR: [ + { account: result.account.trim() }, + { password: result.password.trim() }, + ], + }, + }) + if (!user) { + return { + success: false, + error: '用户不存在或密码未设置', + } + } + + // 验证密码 + const passwordMatch = await compare(result.password, user.password || '') + + if (!passwordMatch) { + return { + success: false, + error: '密码错误', + } + } + + // 创建会话 + const sessionToken = crypto.randomUUID() + await prisma.session.create({ + data: { + id: sessionToken, + userId: user.id, + expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + }) + + // 设置cookie + const response = { + success: true, + user: { + id: user.id, + account: user.account, + name: user.name, + }, + } + + const cookieStore = await cookies() + cookieStore.set('session', sessionToken, { + httpOnly: true, + // secure: process.env.NODE_ENV === 'production', + maxAge: 60 * 60 * 24 * 7, + }) + + return response + } + catch (error) { + console.error('登录错误:', error) + return { + success: false, + error: '服务器错误,请稍后重试', + } + } +} + +export async function logout() { + try { + const cookieStore = await cookies() + const sessionToken = cookieStore.get('session')?.value + + // 删除数据库中的session(如果存在) + if (sessionToken) { + await prisma.session.deleteMany({ + where: { id: sessionToken }, + }).catch(() => { + // 忽略删除错误,确保cookie被清除 + }) + } + + // 清除cookie + const response = { success: true } + cookieStore.set('session', '', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 0, // 立即过期 + path: '/', + }) + + return response + } + catch (error) { + console.error('退出错误:', error) + return { + success: false, + error: '退出失败', + } + } +} diff --git a/src/app/api/stats/route.ts b/src/actions/stats.ts similarity index 54% rename from src/app/api/stats/route.ts rename to src/actions/stats.ts index fdff272..6399f45 100644 --- a/src/app/api/stats/route.ts +++ b/src/actions/stats.ts @@ -1,196 +1,18 @@ -import { NextRequest, NextResponse } from 'next/server' -import { prisma } from '@/lib/prisma' +'use server' +import prisma from '@/lib/prisma' -// 处理 BigInt 序列化 -function safeSerialize(data: unknown) { - return JSON.parse(JSON.stringify(data, (key, value) => - typeof value === 'bigint' ? value.toString() : value, - )) -} - -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url) - const reportType = searchParams.get('type') - - switch (reportType) { - case 'gateway_info': - return await getGatewayInfo() - case 'gateway_config': - return await getGatewayConfig(request) - case 'city_config_count': - return await getCityConfigCount() - case 'city_node_count': - return await getCityNodeCount() - case 'allocation_status': - return await getAllocationStatus(request) - case 'edge_nodes': - return await getEdgeNodes(request) - default: - return NextResponse.json({ error: 'Invalid report type' }, { status: 400 }) - } - } - catch (error) { - console.error('API Error:', error) - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) - } -} - -// 获取网关基本信息 -async function getGatewayInfo() { - try { - const result = await prisma.$queryRaw` - SELECT macaddr, inner_ip, setid, enable - FROM token - ORDER BY macaddr - ` - return NextResponse.json(safeSerialize(result)) - } - catch (error) { - console.error('Gateway info query error:', error) - return NextResponse.json({ error: '查询网关信息失败' }, { status: 500 }) - } -} - -// 网关配置 -async function getGatewayConfig(request: NextRequest) { - try { - const { searchParams } = new URL(request.url) - const macAddress = searchParams.get('mac') || '' - const offset = parseInt(searchParams.get('offset') || '0') - const limit = parseInt(searchParams.get('limit') || '100') - - // 定义类型接口 - interface GatewayRecord { - edge: string | null - city: string | null - user: string | null - public: string | null - inner_ip: string | null - ischange: boolean | number | null - isonline: boolean | number | null - } - - // 获取总数 - let totalCountQuery = '' - let totalCountParams: (string | number)[] = [] - - if (macAddress) { - totalCountQuery = ` - SELECT COUNT(*) as total - FROM gateway - LEFT JOIN cityhash ON cityhash.hash = gateway.cityhash - LEFT JOIN edge ON edge.macaddr = gateway.edge - WHERE gateway.macaddr = ? - ` - totalCountParams = [macAddress] - } - else { - totalCountQuery = ` - SELECT COUNT(*) as total - FROM gateway - ` - } - - const totalCountResult = await prisma.$queryRawUnsafe<[{ total: bigint }]>( - totalCountQuery, - ...totalCountParams, - ) - const totalCount = Number(totalCountResult[0]?.total || 0) - - // 获取分页数据 - let query = ` - select edge, city, user, public, inner_ip, ischange, isonline - from - gateway - left join cityhash - on cityhash.hash = gateway.cityhash - left join edge - on edge.macaddr = gateway.edge - ` - let params: (string | number)[] = [] - - if (macAddress) { - query += ' WHERE gateway.macaddr = ?' - params = [macAddress] - } - else { - query += ' LIMIT ? OFFSET ?' - params.push(limit, offset) - } - - // 指定返回类型 - const result = await prisma.$queryRawUnsafe(query, ...params) - - return NextResponse.json({ - data: safeSerialize(result), - totalCount: totalCount, - currentPage: Math.floor(offset / limit) + 1, - totalPages: Math.ceil(totalCount / limit), - }) - } - catch (error) { - console.error('Gateway config query error:', error) - return NextResponse.json({ error: '查询网关配置失败' }, { status: 500 }) - } -} - -// 城市节点配置数量统计 -async function getCityConfigCount() { - try { - const result = await prisma.$queryRaw` - SELECT c.city, COUNT(e.id) as node_count - FROM cityhash c - LEFT JOIN edge e ON c.id = e.city_id - GROUP BY c.city - ` - return NextResponse.json(safeSerialize(result)) - } - catch (error) { - console.error('City config count query error:', error) - return NextResponse.json({ error: '查询城市配置失败' }, { status: 500 }) - } -} - -// 城市节点数量分布 -async function getCityNodeCount() { - try { - const result = await prisma.$queryRaw` - select c.city, c.hash, c.label, e.count, c.\`offset\` - from - cityhash c - left join ( - select city_id, count(*) as count - from - edge - where - edge.active is true - group by - city_id - ) e - on c.id = e.city_id - group by - c.hash - order by - count desc - ` - return NextResponse.json(safeSerialize(result)) - } - catch (error) { - console.error('City node count query error:', error) - return NextResponse.json({ error: '查询城市节点失败' }, { status: 500 }) - } +export type AllocationStatus = { + city: string + count: bigint + assigned: bigint } // 城市分配状态 -async function getAllocationStatus(request: NextRequest) { +export async function getAllocationStatus(hours: number) { try { - const { searchParams } = new URL(request.url) - const hours = searchParams.get('hours') || '24' - const hoursNum = parseInt(hours) || 24 - + const hoursNum = hours || 24 // 使用参数化查询防止SQL注入 - const result = await prisma.$queryRaw` + const result: AllocationStatus[] = await prisma.$queryRaw` SELECT city, c1.count AS count, @@ -222,24 +44,185 @@ async function getAllocationStatus(request: NextRequest) { WHERE cityhash.macaddr IS NOT NULL; ` - return NextResponse.json(safeSerialize(result)) + return { + success: true, + data: result, + } } catch (error) { console.error('Allocation status query error:', error) const errorMessage = error instanceof Error ? error.message : 'Unknown error' - return NextResponse.json( - { error: '查询分配状态失败: ' + errorMessage }, - { status: 500 }, + return { + success: false, + data: [], + error: '查询分配状态失败: ' + errorMessage, + } + } +} + +export type GatewayInfo = { + macaddr: string + inner_ip: string + setid: string + enable: number +} + +// 获取网关基本信息 +export async function getGatewayInfo() { + try { + const result: GatewayInfo[] = await prisma.$queryRaw` + SELECT macaddr, inner_ip, setid, enable + FROM token + ORDER BY macaddr + ` + return { + success: true, + data: result, + } + } + catch (error) { + console.error('Gateway info query error:', error) + return { + success: false, + data: [], + error: '查询网关信息失败', + } + } +} + +export type GatewayConfig = { + city: string + edge: string + user: string + public: string + inner_ip: string + ischange: number + isonline: number +} + +// 网关配置 +export async function getGatewayConfig(off: number, itemsPerPage: number, mac?: string) { + try { + const offset = off || 0 + const limit = itemsPerPage || 100 + + // 获取总数 + let totalCountQuery = '' + let totalCountParams: (string | number)[] = [] + + if (mac) { + totalCountQuery = ` + SELECT COUNT(*) as total + FROM gateway + LEFT JOIN cityhash ON cityhash.hash = gateway.cityhash + LEFT JOIN edge ON edge.macaddr = gateway.edge + WHERE gateway.macaddr = ? + ` + totalCountParams = [mac] + } + else { + totalCountQuery = ` + SELECT COUNT(*) as total + FROM gateway + ` + } + + const totalCountResult = await prisma.$queryRawUnsafe<[{ total: bigint }]>( + totalCountQuery, + ...totalCountParams, ) + const totalCount = Number(totalCountResult[0]?.total || 0) + + // 获取分页数据 + let query = ` + select edge, city, user, public, inner_ip, ischange, isonline + from + gateway + left join cityhash + on cityhash.hash = gateway.cityhash + left join edge + on edge.macaddr = gateway.edge + ` + let params: (string | number)[] = [] + + if (mac) { + query += ' WHERE gateway.macaddr = ?' + params = [mac] + } + else { + query += ' LIMIT ? OFFSET ?' + params.push(limit, offset) + } + + // 指定返回类型 + const result: GatewayConfig[] = await prisma.$queryRawUnsafe(query, ...params) + + return { + data: result, + totalCount: totalCount, + currentPage: Math.floor(offset / limit) + 1, + totalPages: Math.ceil(totalCount / limit), + } + } + catch (error) { + console.error('Gateway config query error:', error) + return { + success: false, + data: [], + error: '查询网关配置失败', + } + } +} +export type CityNode = { + city: string + count: number + hash: string + label: string + offset: string +} + +// 城市节点数量分布 +export async function getCityNodeCount() { + try { + const result: CityNode[] = await prisma.$queryRaw` + select c.city, c.hash, c.label, e.count, c.\`offset\` + from + cityhash c + left join ( + select city_id, count(*) as count + from + edge + where + edge.active is true + group by + city_id + ) e + on c.id = e.city_id + group by + c.hash + order by + count desc + ` + return { + success: true, + data: result, + } + } + catch (error) { + console.error('City node count query error:', error) + return { + success: false, + data: [], + error: '查询城市节点失败', + } } } // 获取节点信息 -async function getEdgeNodes(request: NextRequest) { +export async function getEdgeNodes(off: number, itemsPerPage: number) { try { - const { searchParams } = new URL(request.url) - const offset = parseInt(searchParams.get('offset') || '0') - const limit = parseInt(searchParams.get('limit') || '100') + const offset = off || 0 + const limit = itemsPerPage || 100 // 获取总数 - 使用类型断言 const totalCountResult = await prisma.$queryRaw<[{ total: bigint }]>` SELECT COUNT(*) as total @@ -257,15 +240,19 @@ async function getEdgeNodes(request: NextRequest) { ORDER BY edge.id LIMIT ${limit} OFFSET ${offset} ` - return NextResponse.json({ - data: safeSerialize(result), + return { + data: result, totalCount: totalCount, currentPage: Math.floor(offset / limit) + 1, totalPages: Math.ceil(totalCount / limit), - }) + } } catch (error) { console.error('Edge nodes query error:', error) - return NextResponse.json({ error: '查询边缘节点失败' }, { status: 500 }) + return { + success: false, + data: [], + error: '查询边缘节点失败', + } } } diff --git a/src/actions/user.ts b/src/actions/user.ts new file mode 100644 index 0000000..767e6aa --- /dev/null +++ b/src/actions/user.ts @@ -0,0 +1,145 @@ +'use server' +import prisma from '@/lib/prisma' +import { hash } from 'bcryptjs' + +type User = { + id: number + account: string + name: string | null + createdAt: Date + updatedAt: Date +} + +// 获取所有用户 +export async function findUsers(): Promise<{ + success: boolean + data: User[] + error: string +}> { + try { + const users = await prisma.user.findMany({ + select: { + id: true, + account: true, + name: true, + createdAt: true, + updatedAt: true, + }, + orderBy: { + createdAt: 'desc', + }, + }) + + return { + success: true, + data: users, + error: '', + } + } + catch (error) { + console.error('获取用户列表错误:', error) + return { + success: false, + data: [], + error: '服务器错误,请稍后重试', + } + } +} + +// 创建用户 +export async function createUser(params: { + account: string + password: string + name: string | '' +}) { + try { + // 检查用户是否已存在 + const existingUser = await prisma.user.findUnique({ + where: { account: params.account }, + }) + + if (existingUser) { + return { + success: false, + error: '用户账号已存在', + } + } + + // 加密密码 + const hashedPassword = await hash(params.password, 10) + + // 创建用户 + const user = await prisma.user.create({ + data: { + account: params.account, + password: hashedPassword, + name: params.name || params.account, + }, + }) + + // 不返回密码字段 + const { password: _, ...userWithoutPassword } = user + + return { + success: true, + user: userWithoutPassword, + } + } + catch (error) { + console.error('创建用户错误:', error) + return { + success: false, + error: '服务器错误,请稍后重试', + } + } +} + +// 删除用户 +export async function removeUser(id: number) { + try { + if (!id) { + return { + success: false, + error: '用户ID不能为空', + } + } + + // const userId = parseInt(id) + if (isNaN(id)) { + return { + success: false, + error: '无效的用户ID', + } + } + + // 检查用户是否存在 + const existingUser = await prisma.user.findUnique({ + where: { id: id }, + }) + + if (!existingUser) { + return { + status: 404, + success: false, + error: '用户不存在', + } + } + + // 删除用户 + await prisma.user.delete({ + where: { id: id }, + }) + return { + success: true, + message: '用户删除成功', + } + } + catch (error) { + console.error('删除用户错误:', error) + return { + success: false, + error: '服务器错误,请稍后重试', + status: 500, + } + } +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx deleted file mode 100644 index dc728a6..0000000 --- a/src/app/(auth)/login/page.tsx +++ /dev/null @@ -1,139 +0,0 @@ -'use client' -import { useState } from 'react' -import { useRouter } from 'next/navigation' -import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import * as z from 'zod' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { Lock, User } from 'lucide-react' -import { useAuthStore } from '@/store/auth' -import { toast, Toaster } from 'sonner' - -const formSchema = z.object({ - account: z.string().min(3, '账号至少需要3个字符'), - password: z.string().min(6, '密码至少需要6个字符'), -}) - -export default function LoginPage() { - const router = useRouter() - const [loading, setLoading] = useState(false) - const setAuth = useAuthStore(state => state.setAuth) - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - account: '', - password: '', - }, - }) - - async function onSubmit(values: z.infer) { - setLoading(true) - try { - const response = await fetch('/api/auth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }) - - const data = await response.json() - - if (!response.ok) { - throw new Error(data.error || '登录失败') - } - - if (data.success) { - toast.success('登录成功', { - description: '正在跳转到仪表盘...', - }) - setAuth(true) - await new Promise(resolve => setTimeout(resolve, 1000)) - router.push('/dashboard') - router.refresh() - } - } - catch (error) { - toast.error('登录失败', { - description: error instanceof Error ? error.message : '服务器连接失败,请稍后重试', - }) - } - finally { - setLoading(false) - } - } - return ( - <> -
- - - 登录 - - -
- - ( - - 账号 - -
- - -
-
- -
- )} - /> - ( - - 密码 - -
- - -
-
- -
- )} - /> - - - -
-
-
- - - ) -} diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts deleted file mode 100644 index 0d7afe4..0000000 --- a/src/app/api/auth/login/route.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { NextResponse } from 'next/server' -import { prisma } from '@/lib/prisma' // 使用统一的prisma实例 -import { compare } from 'bcryptjs' -import { z } from 'zod' - -const loginSchema = z.object({ - account: z.string().min(3, '账号至少需要3个字符'), - password: z.string().min(6, '密码至少需要6个字符'), -}) - -export async function POST(request: Request) { - try { - const body = await request.json() - const { account, password } = loginSchema.parse(body) - - const user = await prisma.user.findFirst({ - where: { - OR: [ - { account: account.trim() }, - { password: account.trim() }, - ], - }, - }) - - if (!user) { - return NextResponse.json( - { success: false, error: '用户不存在' }, - { status: 401 }, - ) - } - - // 验证密码 - const passwordMatch = await compare(password, user.password || '') - - if (!passwordMatch) { - return NextResponse.json({ - success: false, - error: '密码错误', - }, { status: 401 }) - } - - // 创建会话 - const sessionToken = crypto.randomUUID() - await prisma.session.create({ - data: { - id: sessionToken, - userId: user.id, - expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - }, - }) - - // 设置cookie - const response = NextResponse.json({ - success: true, - user: { - id: user.id, - account: user.account, - name: user.name, - }, - }) - - response.cookies.set('session', sessionToken, { - httpOnly: true, - // secure: process.env.NODE_ENV === 'production', - maxAge: 60 * 60 * 24 * 7, - }) - - return response - } - catch (error) { - console.error('登录错误:', error) - return NextResponse.json( - { success: false, error: '服务器错误,请稍后重试' }, - { status: 500 }, - ) - } -} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts deleted file mode 100644 index 13d18f4..0000000 --- a/src/app/api/auth/logout/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NextResponse } from 'next/server' -import { cookies } from 'next/headers' -import { prisma } from '@/lib/prisma' - -export async function POST() { - try { - const cookieStore = await cookies() - const sessionToken = cookieStore.get('session')?.value - - // 删除数据库中的session(如果存在) - if (sessionToken) { - await prisma.session.deleteMany({ - where: { id: sessionToken }, - }).catch(() => { - // 忽略删除错误,确保cookie被清除 - }) - } - - // 清除cookie - const response = NextResponse.json({ success: true }) - response.cookies.set('session', '', { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - maxAge: 0, // 立即过期 - path: '/', - }) - - return response - } - catch (error) { - console.error('退出错误:', error) - return NextResponse.json( - { success: false, error: '退出失败' }, - { status: 500 }, - ) - } -} diff --git a/src/app/api/users/route.tsx b/src/app/api/users/route.tsx deleted file mode 100644 index f97ae5d..0000000 --- a/src/app/api/users/route.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { NextResponse } from 'next/server' -import { prisma } from '@/lib/prisma' -import { hash } from 'bcryptjs' - -// 获取所有用户 -export async function GET() { - try { - const users = await prisma.user.findMany({ - select: { - id: true, - account: true, - name: true, - createdAt: true, - updatedAt: true, - }, - orderBy: { - createdAt: 'desc', - }, - }) - - return NextResponse.json({ - success: true, - users, - }) - } - catch (error) { - console.error('获取用户列表错误:', error) - return NextResponse.json( - { success: false, error: '服务器错误,请稍后重试' }, - { status: 500 }, - ) - } -} - -// 创建用户 -export async function POST(request: Request) { - try { - const { account, password, name } = await request.json() - - // 检查用户是否已存在 - const existingUser = await prisma.user.findUnique({ - where: { account }, - }) - - if (existingUser) { - return NextResponse.json( - { success: false, error: '用户账号已存在' }, - { status: 400 }, - ) - } - - // 加密密码 - const hashedPassword = await hash(password, 10) - - // 创建用户 - const user = await prisma.user.create({ - data: { - account, - password: hashedPassword, - name: name || account, - }, - }) - - // 不返回密码字段 - const { password: _, ...userWithoutPassword } = user - - return NextResponse.json({ - success: true, - user: userWithoutPassword, - }) - } - catch (error) { - console.error('创建用户错误:', error) - return NextResponse.json( - { success: false, error: '服务器错误,请稍后重试' }, - { status: 500 }, - ) - } -} - -// 删除用户 -export async function DELETE(request: Request) { - try { - // 从URL中获取查询参数 - const url = new URL(request.url) - const id = url.searchParams.get('id') - - if (!id) { - return NextResponse.json( - { success: false, error: '用户ID不能为空' }, - { status: 400 }, - ) - } - - const userId = parseInt(id) - if (isNaN(userId)) { - return NextResponse.json( - { success: false, error: '无效的用户ID' }, - { status: 400 }, - ) - } - - // 检查用户是否存在 - const existingUser = await prisma.user.findUnique({ - where: { id: userId }, - }) - - if (!existingUser) { - return NextResponse.json( - { success: false, error: '用户不存在' }, - { status: 404 }, - ) - } - - // 删除用户 - await prisma.user.delete({ - where: { id: userId }, - }) - - return NextResponse.json({ - success: true, - message: '用户删除成功', - }) - } - catch (error) { - console.error('删除用户错误:', error) - return NextResponse.json( - { success: false, error: '服务器错误,请稍后重试' }, - { status: 500 }, - ) - } -} diff --git a/src/app/dashboard/components/allocationStatus.tsx b/src/app/dashboard/components/allocationStatus.tsx index b1b8af4..f90eaf3 100644 --- a/src/app/dashboard/components/allocationStatus.tsx +++ b/src/app/dashboard/components/allocationStatus.tsx @@ -1,23 +1,10 @@ 'use client' import { useEffect, useState, useCallback } from 'react' -import { formatNumber, validateNumber } from '@/lib/formatters' import LoadingCard from '@/components/ui/loadingCard' import ErrorCard from '@/components/ui/errorCard' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' - -interface AllocationStatus { - city: string - count: number - assigned: number -} - -interface ApiAllocationStatus { - city?: string - count: number | string | bigint - assigned: number | string | bigint - unique_allocated_ips: number | string | bigint -} +import { getAllocationStatus, type AllocationStatus } from '@/actions/stats' export default function AllocationStatus({ detailed = false }: { detailed?: boolean }) { const [data, setData] = useState([]) @@ -48,21 +35,16 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool setLoading(true) const hours = getTimeHours() - - const response = await fetch(`/api/stats?type=allocation_status&hours=${hours}`) - - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) - - const result = await response.json() + const result = await getAllocationStatus(hours) // 数据验证 - const validatedData = (result as ApiAllocationStatus[]).map(item => ({ + const validatedData = (result.data).map(item => ({ city: item.city || '未知', - count: validateNumber(item.count), - assigned: validateNumber(item.assigned), + count: item.count || BigInt(0), + assigned: item.assigned || BigInt(0), })) - const sortedData = validatedData.sort((a, b) => b.count - a.count) + const sortedData = validatedData.sort((a, b) => Number(b.count - a.count)) setData(sortedData) } @@ -140,18 +122,18 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool {data.map((item, index) => { - const overage = calculateOverage(item.assigned, item.count) + const overage = calculateOverage(Number(item.assigned), Number(item.count)) return ( {item.city} - {formatNumber(item.count)} - {formatNumber(item.assigned)} + {item.count} + {item.assigned} 0 ? 'text-red-600 font-medium' : ''}> - {formatNumber(overage)} + {overage} diff --git a/src/app/dashboard/components/cityNodeStats.tsx b/src/app/dashboard/components/cityNodeStats.tsx index 7b08616..0a386fc 100644 --- a/src/app/dashboard/components/cityNodeStats.tsx +++ b/src/app/dashboard/components/cityNodeStats.tsx @@ -2,14 +2,7 @@ import { useEffect, useState } from 'react' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' - -interface CityNode { - city: string - count: number - hash: string - label: string - offset: string -} +import { getCityNodeCount, type CityNode } from '@/actions/stats' export default function CityNodeStats() { const [data, setData] = useState([]) @@ -21,9 +14,8 @@ export default function CityNodeStats() { const fetchData = async () => { try { - const response = await fetch('/api/stats?type=city_node_count') - const result = await response.json() - setData(result) + const result = await getCityNodeCount() + setData(result.data) } catch (error) { console.error('获取城市节点数据失败:', error) diff --git a/src/app/dashboard/components/edge.tsx b/src/app/dashboard/components/edge.tsx index b98130e..91ddadd 100644 --- a/src/app/dashboard/components/edge.tsx +++ b/src/app/dashboard/components/edge.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import { validateNumber } from '@/lib/formatters' import { Pagination } from '@/components/ui/pagination' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' +import { getEdgeNodes } from '@/actions/stats' interface Edge { id: number @@ -39,10 +40,9 @@ export default function Edge() { // 计算偏移量 const offset = (currentPage - 1) * itemsPerPage - const response = await fetch(`/api/stats?type=edge_nodes&offset=${offset}&limit=${itemsPerPage}`) - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) + // if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) - const result = await response.json() + const result = await getEdgeNodes(offset, itemsPerPage) type ResultEdge = { id: number macaddr: string diff --git a/src/app/dashboard/components/gatewayConfig.tsx b/src/app/dashboard/components/gatewayConfig.tsx index 8f282c8..e0d877b 100644 --- a/src/app/dashboard/components/gatewayConfig.tsx +++ b/src/app/dashboard/components/gatewayConfig.tsx @@ -3,23 +3,17 @@ import { useEffect, useState, Suspense } from 'react' import { useSearchParams } from 'next/navigation' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' import { Pagination } from '@/components/ui/pagination' +import { getGatewayConfig, type GatewayConfig } from '@/actions/stats' -interface GatewayConfig { - city: string - edge: string - user: string - public: string - inner_ip: string - ischange: number - isonline: number -} - -interface ApiResponse { - data: GatewayConfig[] - totalCount: number - currentPage: number - totalPages: number -} +// interface GatewayConfig { +// city: string +// edge: string +// user: string +// public: string +// inner_ip: string +// ischange: number +// isonline: number +// } function GatewayConfigContent() { const [data, setData] = useState([]) @@ -59,18 +53,7 @@ function GatewayConfigContent() { // 计算偏移量 const offset = (page - 1) * limit - // 构建API URL - let apiUrl = `/api/stats?type=gateway_config&offset=${offset}&limit=${limit}` - if (mac.trim()) { - apiUrl += `&mac=${encodeURIComponent(mac)}` - } - - const response = await fetch(apiUrl) - if (!response.ok) { - throw new Error(`HTTP错误! 状态: ${response.status}`) - } - - const result: ApiResponse = await response.json() + const result = await getGatewayConfig(offset, limit, mac) // 检查返回的数据是否有效 if (!result.data || result.data.length === 0) { diff --git a/src/app/dashboard/components/gatewayinfo.tsx b/src/app/dashboard/components/gatewayinfo.tsx index 1fee98b..c8564f8 100644 --- a/src/app/dashboard/components/gatewayinfo.tsx +++ b/src/app/dashboard/components/gatewayinfo.tsx @@ -8,13 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { z } from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' - -interface GatewayInfo { - macaddr: string - inner_ip: string - setid: string - enable: number -} +import { getGatewayInfo, type GatewayInfo } from '@/actions/stats' const filterSchema = z.object({ status: z.string(), @@ -75,13 +69,9 @@ export default function Gatewayinfo() { try { setLoading(true) setError('') - const response = await fetch('/api/stats?type=gateway_info') - if (!response.ok) { - throw new Error('获取网关信息失败') - } - const result = await response.json() - // const sortedData = result.sort(( a, b) => Number(a.inner_ip) - Number(b.inner_ip)) - const sortedData = result.sort((a: GatewayInfo, b: GatewayInfo) => + const result = await getGatewayInfo() + + const sortedData = result.data.sort((a: GatewayInfo, b: GatewayInfo) => sortByIpAddress(a.inner_ip, b.inner_ip), ) setData(sortedData) diff --git a/src/app/dashboard/components/settings.tsx b/src/app/dashboard/components/settings.tsx index 23e954b..8ae4f30 100644 --- a/src/app/dashboard/components/settings.tsx +++ b/src/app/dashboard/components/settings.tsx @@ -10,12 +10,15 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from ' import { User, Lock, Search, Trash2, Plus, X } from 'lucide-react' import { toast, Toaster } from 'sonner' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { findUsers, createUser, removeUser } from '@/actions/user' // 用户类型定义 interface UserData { - id: string + id: number account: string - createdAt: string + createdAt: Date + name: string | null + updatedAt: Date } const formSchema = z.object({ @@ -45,15 +48,10 @@ export default function Settings() { // 获取用户列表 const fetchUsers = async () => { try { - const response = await fetch('/api/users') - const data = await response.json() - - if (!response.ok) { - throw new Error(data.error || '获取用户列表失败') - } + const data = await findUsers() if (data.success) { - setUsers(data.users) + setUsers(data.data) } } catch (error) { @@ -72,23 +70,12 @@ export default function Settings() { async function onSubmit(values: z.infer) { setLoading(true) try { - const response = await fetch('/api/users', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - account: values.account, - password: values.password, - }), + const data = await createUser({ + account: values.account, + password: values.password, + name: '', }) - const data = await response.json() - - if (!response.ok) { - throw new Error(data.error || '创建用户失败') - } - if (data.success) { toast.success('用户创建成功', { description: '新账户已成功添加', @@ -115,17 +102,7 @@ export default function Settings() { } try { - // 使用查询参数传递ID - const response = await fetch(`/api/users?id=${userId}`, { - method: 'DELETE', - }) - - const data = await response.json() - - if (!response.ok) { - throw new Error(data.error || '删除用户失败') - } - + const data = await removeUser(userId) if (data.success) { toast.success('用户删除成功', { description: '用户账户已删除', diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 3ed9238..6e547ad 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -9,6 +9,8 @@ import AllocationStatus from './components/allocationStatus' import Settings from './components/settings' import Edge from './components/edge' import { LogOut } from 'lucide-react' +import { logout } from '@/actions/auth' +import { toast } from 'sonner' const tabs = [ { id: 'gatewayInfo', label: '网关信息' }, @@ -37,11 +39,9 @@ function DashboardContent() { const handleLogout = async () => { setIsLoading(true) try { - const response = await fetch('/api/auth/logout', { - method: 'POST', - }) + const response = await logout() - if (response.ok) { + if (response) { // 退出成功后跳转到登录页 router.push('/login') router.refresh() diff --git a/src/lib/formatters.ts b/src/lib/formatters.ts index f0a857b..67faaed 100644 --- a/src/lib/formatters.ts +++ b/src/lib/formatters.ts @@ -1,20 +1,3 @@ -// 数字格式化工具函数 -export const formatNumber = (num: number | string): string => { - const numberValue = typeof num === 'string' ? parseInt(num) || 0 : num - return numberValue.toLocaleString('zh-CN') -} - -export const formatLargeNumber = (num: number | string): string => { - const numberValue = typeof num === 'string' ? parseInt(num) || 0 : num - - if (numberValue > 1e9) return `${(numberValue / 1e9).toFixed(1)}亿` - if (numberValue > 1e6) return `${(numberValue / 1e6).toFixed(1)}百万` - if (numberValue > 1e4) return `${(numberValue / 1e4).toFixed(1)}万` - if (numberValue > 1e3) return `${(numberValue / 1e3).toFixed(1)}千` - - return numberValue.toLocaleString('zh-CN') -} - // 数据验证函数 export const validateNumber = (value: unknown): number => { if (typeof value === 'number') return value diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 3ea6fa9..ab0c930 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -4,10 +4,11 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient | undefined } -export const prisma = globalForPrisma.prisma ?? new PrismaClient() +const prisma = globalForPrisma.prisma ?? new PrismaClient() if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma } export default prisma +export * from '@/generated/prisma/client' diff --git a/src/middleware.ts b/src/middleware.ts index 42aa09e..48e322f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -9,7 +9,6 @@ export const config = { const isIgnored = [ '/login', - '/api/auth/login', ] export async function middleware(request: NextRequest) {