推送远程分支前的排查和修复代码

This commit is contained in:
Eamon-meng
2025-12-20 15:08:12 +08:00
parent e23f89cde6
commit 5b1207783b
12 changed files with 143 additions and 160 deletions

View File

@@ -32,6 +32,7 @@ const eslintConfig = defineConfig([
'@stylistic/block-spacing': 'off', '@stylistic/block-spacing': 'off',
'@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off',
'@react-hooks/set-state-in-effect': 'off',
}, },
}, },
]) ])

8
src/actions/batch.ts Normal file
View File

@@ -0,0 +1,8 @@
'use server'
import {PageRecord} from '@/lib/api'
import {Batch} from '@/lib/models/batch'
import {callByUser} from './base'
export async function pageBatch(props: {page: number, size: number}) {
return callByUser<PageRecord<Batch>>('/api/batch/page', props)
}

View File

@@ -2,7 +2,7 @@
import {callByUser, callPublic} from '@/actions/base' import {callByUser, callPublic} from '@/actions/base'
import {Channel} from '@/lib/models' import {Channel} from '@/lib/models'
import {PageRecord} from '@/lib/api' import {PageRecord} from '@/lib/api'
import {BatchRecord} from '@/lib/models/batch' import {Batch} from '@/lib/models/batch'
export async function listChannels(props: { export async function listChannels(props: {
page: number page: number
@@ -16,10 +16,6 @@ export async function listChannels(props: {
return callByUser<PageRecord<Channel>>('/api/channel/list', props) return callByUser<PageRecord<Channel>>('/api/channel/list', props)
} }
export async function extractRecord(props: {page: number, size: number}) {
return callByUser<PageRecord<BatchRecord>>('/api/batch/page', props)
}
type CreateChannelsResp = { type CreateChannelsResp = {
host: string host: string
port: string port: string

View File

@@ -87,5 +87,9 @@ export async function payClose(props: {
} }
export async function getPrice(props: CreateResourceReq) { export async function getPrice(props: CreateResourceReq) {
return callByDevice<{price: string}>('/api/resource/price', props) return callByDevice<{
price: string
discounted_price?: string
discounted?: number
}>('/api/resource/price', props)
} }

View File

@@ -76,7 +76,7 @@ export default function Footer(props: FooterProps) {
/> />
</div> </div>
<div className="flex-none mt-6 pt-6 border-t border-gray-700 flex flex-col text-gray-300"> <div className="flex-none mt-6 pt-6 border-t border-gray-700 flex text-center flex-col text-gray-300">
<p className="text-xs"> <p className="text-xs">
IP服务使IP从事的任何行为均不代表蓝狐代理IP的意志和观点 IP服务使IP从事的任何行为均不代表蓝狐代理IP的意志和观点
<br/> <br/>

View File

@@ -0,0 +1,22 @@
import {Badge} from '@/components/ui/badge'
import {Channel} from '@/lib/models'
import {isBefore} from 'date-fns'
export default function Addr({channel}: {
channel: Channel
}) {
const ip = channel.host
const port = channel.port
const expired = isBefore(channel.expired_at, new Date())
return (
<div className={`${expired ? 'text-weak' : ''}`}>
<span>{ip}:{port}</span>
{expired && (
<Badge variant="secondary">
</Badge>
)}
</div>
)
}

View File

@@ -12,5 +12,5 @@ export type ChannelsLayoutProps = {
} }
export default async function ChannelsLayout(props: ChannelsLayoutProps) { export default async function ChannelsLayout(props: ChannelsLayoutProps) {
return <Suspense>{props.children}</Suspense> return props.children
} }

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import {useCallback, useEffect, useState} from 'react' import {Suspense, useCallback, useEffect, useState} from 'react'
import {useStatus} from '@/lib/states' import {useStatus} from '@/lib/states'
import {PageRecord} from '@/lib/api' import {PageRecord} from '@/lib/api'
import {Channel} from '@/lib/models' import {Channel} from '@/lib/models'
@@ -17,6 +17,7 @@ import {Button} from '@/components/ui/button'
import {EraserIcon, SearchIcon} from 'lucide-react' import {EraserIcon, SearchIcon} from 'lucide-react'
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select' import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'
import {Badge} from '@/components/ui/badge' import {Badge} from '@/components/ui/badge'
import Addr from '../_components/addr'
export type ChannelsPageProps = {} export type ChannelsPageProps = {}
export default function ChannelsPage(props: ChannelsPageProps) { export default function ChannelsPage(props: ChannelsPageProps) {
@@ -50,11 +51,6 @@ export default function ChannelsPage(props: ChannelsPageProps) {
expire_before: undefined, expire_before: undefined,
}, },
}) })
// 检查是否过期
const isExpired = (expiredAt: string | Date) => {
const date = typeof expiredAt === 'string' ? new Date(expiredAt) : expiredAt
return isBefore(date, new Date())
}
const refresh = useCallback(async (page: number, size: number) => { const refresh = useCallback(async (page: number, size: number) => {
try { try {
@@ -63,12 +59,8 @@ export default function ChannelsPage(props: ChannelsPageProps) {
// 筛选条件 // 筛选条件
const filter = filterForm.getValues() const filter = filterForm.getValues()
const auth_type = filter.auth_type ? parseInt(filter.auth_type) : undefined const auth_type = filter.auth_type ? parseInt(filter.auth_type) : undefined
const expired_status = filter.expired_status
// 请求数据 // 请求数据
console.log({
page, size, ...filter, auth_type,
})
const resp = await listChannels({ const resp = await listChannels({
page, size, ...filter, auth_type, page, size, ...filter, auth_type,
}) })
@@ -77,20 +69,8 @@ export default function ChannelsPage(props: ChannelsPageProps) {
throw new Error(resp.message) throw new Error(resp.message)
} }
let filteredList = resp.data.list
if (expired_status !== undefined && expired_status !== 'all') {
filteredList = resp.data.list.filter((channel) => {
const expired = isExpired(channel.expired_at)
return !expired
})
resp.data.total = filteredList.length
}
// 更新数据 // 更新数据
setData({ setData(resp.data)
...resp.data,
list: filteredList,
})
setStatus('done') setStatus('done')
} }
catch (e) { catch (e) {
@@ -182,81 +162,67 @@ export default function ChannelsPage(props: ChannelsPageProps) {
</Form> </Form>
</section> </section>
<DataTable <Suspense>
status={status} <DataTable
data={data.list} status={status}
pagination={{ data={data.list}
page: data.page, pagination={{
size: data.size, page: data.page,
total: data.total, size: data.size,
onPageChange: page => refresh(page, data.size), total: data.total,
onSizeChange: size => refresh(1, size), onPageChange: page => refresh(page, data.size),
}} onSizeChange: size => refresh(1, size),
columns={[ }}
{ columns={[
header: '代理地址', {
cell: ({row}) => { header: '代理地址',
const channel = row.original cell: ({row}) => <Addr channel={row.original}/>,
const ip = channel.host
const port = channel.port
const expired = isExpired(channel.expired_at)
return (
<div className={`${expired ? 'text-weak' : ''}`}>
<span>{ip}:{port}</span>
{expired && (
<Badge variant="secondary">
</Badge>
)}
</div>
)
}, },
}, {
{ header: '认证方式',
header: '认证方式', cell: ({row}) => {
cell: ({row}) => { const channel = row.original
const channel = row.original const hasWhitelist = channel.whitelists && channel.whitelists.trim() !== ''
const hasWhitelist = channel.whitelists && channel.whitelists.trim() !== '' const hasAuth = channel.username && channel.password
const hasAuth = channel.username && channel.password
return ( return (
<div className="flex flex-col gap-1 min-w-0"> <div className="flex flex-col gap-1 min-w-0">
{hasWhitelist ? ( {hasWhitelist ? (
<div className="flex flex-col"> <div className="flex flex-col">
<span ></span> <span ></span>
<div className="flex flex-wrap gap-1 max-w-[200px]"> <div className="flex flex-wrap gap-1 max-w-[200px]">
{channel.whitelists.split(',').map((ip, index) => ( {channel.whitelists.split(',').map((ip, index) => (
<Badge key={index} variant="secondary"> <Badge key={index} variant="secondary">
{ip.trim()} {ip.trim()}
</Badge > </Badge >
))} ))}
</div>
</div> </div>
</div> ) : hasAuth ? (
) : hasAuth ? ( <div className="flex flex-col">
<div className="flex flex-col"> <span></span>
<span></span> <Badge variant="secondary">
<Badge variant="secondary"> {channel.username}:{channel.password}
{channel.username}:{channel.password} </Badge >
</Badge > </div>
</div> ) : (
) : ( <span className="text-sm text-gray-400"></span>
<span className="text-sm text-gray-400"></span> )}
)} </div>
</div> )
) },
}, },
}, {
{ header: '提取时间',
header: '提取时间', cell: ({row}) => format(row.original.created_at, 'yyyy-MM-dd HH:mm'),
cell: ({row}) => format(row.original.created_at, 'yyyy-MM-dd HH:mm'), },
}, {
{ header: '过期时间',
header: '过期时间', cell: ({row}) => format(row.original.expired_at, 'yyyy-MM-dd HH:mm:ss'),
cell: ({row}) => format(row.original.expired_at, 'yyyy-MM-dd HH:mm:ss'), },
}, ]}
]} />
/> </Suspense>
</Page> </Page>
) )
} }

View File

@@ -5,8 +5,7 @@ import {PageRecord} from '@/lib/api'
import Page from '@/components/page' import Page from '@/components/page'
import DataTable from '@/components/data-table' import DataTable from '@/components/data-table'
import {toast} from 'sonner' import {toast} from 'sonner'
import {extractRecord} from '@/actions/channel' import {Batch} from '@/lib/models/batch'
import {BatchRecord} from '@/lib/models/batch'
import {format} from 'date-fns' import {format} from 'date-fns'
import {Form, FormField} from '@/components/ui/form' import {Form, FormField} from '@/components/ui/form'
import {z} from 'zod' import {z} from 'zod'
@@ -15,12 +14,13 @@ import {zodResolver} from '@hookform/resolvers/zod'
import DatePicker from '@/components/date-picker' import DatePicker from '@/components/date-picker'
import {Button} from '@/components/ui/button' import {Button} from '@/components/ui/button'
import {EraserIcon, SearchIcon} from 'lucide-react' import {EraserIcon, SearchIcon} from 'lucide-react'
import {pageBatch} from '@/actions/batch'
export type RecordPageProps = {} export type RecordPageProps = {}
export default function RecordPage(props: RecordPageProps) { export default function RecordPage(props: RecordPageProps) {
const [status, setStatus] = useStatus() const [status, setStatus] = useStatus()
const [data, setData] = useState<PageRecord<BatchRecord>>({ const [data, setData] = useState<PageRecord<Batch>>({
page: 1, page: 1,
size: 10, size: 10,
total: 0, total: 0,
@@ -50,9 +50,7 @@ export default function RecordPage(props: RecordPageProps) {
setStatus('load') setStatus('load')
// 获取筛选条件 // 获取筛选条件
const filter = filterForm.getValues() const filter = filterForm.getValues()
console.log(filter, 'filter') const result = await pageBatch({
const result = await extractRecord({
page, page,
size, size,
...filter, ...filter,

View File

@@ -18,12 +18,7 @@ import {useFormContext, useWatch} from 'react-hook-form'
import {Schema} from '@/components/composites/purchase/long/form' import {Schema} from '@/components/composites/purchase/long/form'
import {Card} from '@/components/ui/card' import {Card} from '@/components/ui/card'
import {getPrice, CreateResourceReq} from '@/actions/resource' import {getPrice, CreateResourceReq} from '@/actions/resource'
import {ExtraResp} from '@/lib/api'
interface PriceData {
price: string
discounted_price?: string
discounted?: number
}
export default function Right() { export default function Right() {
const {control} = useFormContext<Schema>() const {control} = useFormContext<Schema>()
@@ -33,7 +28,7 @@ export default function Right() {
const quota = useWatch({control, name: 'quota'}) const quota = useWatch({control, name: 'quota'})
const expire = useWatch({control, name: 'expire'}) const expire = useWatch({control, name: 'expire'})
const dailyLimit = useWatch({control, name: 'daily_limit'}) const dailyLimit = useWatch({control, name: 'daily_limit'})
const [priceData, setPriceData] = useState<PriceData>({ const [priceData, setPriceData] = useState<ExtraResp<typeof getPrice>>({
price: '0.00', price: '0.00',
discounted_price: '0.00', discounted_price: '0.00',
discounted: 0, discounted: 0,
@@ -41,29 +36,27 @@ export default function Right() {
useEffect(() => { useEffect(() => {
const price = async () => { const price = async () => {
const params: CreateResourceReq = {
type: 2,
long: {
live: Number(live),
mode: Number(mode),
quota: Number(mode) === 1 ? Number(dailyLimit) : Number(quota),
expire: Number(mode) === 1 ? Number(expire) : undefined,
},
}
try { try {
const priceValue = await getPrice(params) const resp = await getPrice({
type: 2,
if (priceValue.success && priceValue.data?.price) { long: {
const data: PriceData = priceValue.data live: Number(live),
setPriceData({ mode: Number(mode),
price: data.price, quota: mode === '1' ? Number(dailyLimit) : Number(quota),
discounted_price: data.discounted_price ?? data.price ?? '', expire: mode === '1' ? Number(expire) : undefined,
discounted: data.discounted, },
}) })
if (!resp.success) {
throw new Error('获取价格失败')
} }
setPriceData({
price: resp.data.price,
discounted_price: resp.data.discounted_price ?? resp.data.price ?? '',
discounted: resp.data.discounted,
})
} }
catch (error) { catch (error) {
console.error('获取价格失败:', error)
setPriceData({ setPriceData({
price: '0.00', price: '0.00',
discounted_price: '0.00', discounted_price: '0.00',

View File

@@ -17,12 +17,7 @@ import Pay from '@/components/composites/purchase/pay'
import {useFormContext, useWatch} from 'react-hook-form' import {useFormContext, useWatch} from 'react-hook-form'
import {Card} from '@/components/ui/card' import {Card} from '@/components/ui/card'
import {CreateResourceReq, getPrice} from '@/actions/resource' import {CreateResourceReq, getPrice} from '@/actions/resource'
import {ExtraResp} from '@/lib/api'
interface PriceData {
price: string
discounted_price?: string
discounted?: number
}
export default function Right() { export default function Right() {
const {control} = useFormContext<Schema>() const {control} = useFormContext<Schema>()
@@ -32,7 +27,7 @@ export default function Right() {
const expire = useWatch({control, name: 'expire'}) const expire = useWatch({control, name: 'expire'})
const quota = useWatch({control, name: 'quota'}) const quota = useWatch({control, name: 'quota'})
const dailyLimit = useWatch({control, name: 'daily_limit'}) const dailyLimit = useWatch({control, name: 'daily_limit'})
const [priceData, setPriceData] = useState<PriceData>({ const [priceData, setPriceData] = useState<ExtraResp<typeof getPrice>>({
price: '0.00', price: '0.00',
discounted_price: '0.00', discounted_price: '0.00',
discounted: 0, discounted: 0,
@@ -40,26 +35,26 @@ export default function Right() {
useEffect(() => { useEffect(() => {
const price = async () => { const price = async () => {
const params: CreateResourceReq = {
type: 1,
short: {
live: Number(live),
mode: Number(mode),
quota: Number(mode) === 1 ? Number(dailyLimit) : Number(quota),
expire: Number(mode) === 1 ? Number(expire) : undefined,
},
}
try { try {
const priceResponse = await getPrice(params) const priceResponse = await getPrice({
type: 1,
if (priceResponse.success && priceResponse.data) { short: {
const data: PriceData = priceResponse.data live: Number(live),
setPriceData({ mode: Number(mode),
price: data.price, quota: mode === '1' ? Number(dailyLimit) : Number(quota),
discounted_price: data.discounted_price ?? data.price ?? '', expire: mode === '1' ? Number(expire) : undefined,
discounted: data.discounted, },
}) })
if (!priceResponse.success) {
throw new Error('获取价格失败')
} }
const data = priceResponse.data
setPriceData({
price: data.price,
discounted_price: data.discounted_price ?? data.price ?? '',
discounted: data.discounted,
})
} }
catch (error) { catch (error) {
console.error('获取价格失败:', error) console.error('获取价格失败:', error)

View File

@@ -1,4 +1,4 @@
export type BatchRecord = { export type Batch = {
batch_no: string batch_no: string
ip: string ip: string
id: number id: number