修复提取IP表格字段和页面展示 & 前端设置白名单添加数量限制 & 修复长效套餐购买数量和提取数量默认值和递增减

This commit is contained in:
Eamon-meng
2025-12-01 19:21:29 +08:00
parent 3a5f4220ad
commit 591177e7a1
5 changed files with 130 additions and 79 deletions

View File

@@ -155,34 +155,52 @@ export default function ChannelsPage(props: ChannelsPageProps) {
}}
columns={[
{
header: '代理地址', cell: ({row}) => `${row.original.proxy_host}:${row.original.proxy_port}`,
header: '代理地址', cell: ({row}) => {
const channel = row.original
const ip = channel.proxy?.ip
const port = channel.port
return `${ip}:${port}`
},
},
{
header: '认证方式', cell: ({row}) => {
header: '认证方式',
cell: ({row}) => {
const channel = row.original
const hasWhitelist = channel.whitelists && channel.whitelists.trim() !== ''
const hasAuth = channel.username && channel.password
return (
<div className="flex flex-col gap-1">
{row.original.auth_ip && (
<>
<span className="text-weak">IP </span>
<span>{row.original.whitelists.replaceAll(',', ', ')}</span>
</>
)}
{row.original.auth_pass && (
<>
<span className="text-weak"></span>
<span>
{row.original.username}
:
{row.original.password}
</span>
</>
<div className="flex flex-col gap-1 min-w-0">
{hasWhitelist ? (
<div className="flex flex-col">
<span ></span>
<div className="flex flex-wrap gap-1 max-w-[200px]">
{channel.whitelists.split(',').map((ip, index) => (
<span
key={index}
className="inline-block px-2 py-0.5 bg-gray-100 rounded text-xs text-gray-700 break-all"
>
{ip.trim()}
</span>
))}
</div>
</div>
) : hasAuth ? (
<div className="flex flex-col">
<span></span>
<div className="px-2 py-1 bg-gray-100 rounded text-sm text-gray-700 font-mono break-all max-w-[120px]">
{channel.username}:{channel.password}
</div>
</div>
) : (
<span className="text-sm text-gray-400"></span>
)}
</div>
)
},
},
{
header: '过期时间', cell: ({row}) => format(row.original.expiration, 'yyyy-MM-dd HH:mm:ss'),
header: '过期时间', cell: ({row}) => format(row.original.expired_at, 'yyyy-MM-dd HH:mm:ss'),
},
{
header: '操作', cell: ({row}) => <span>-</span>,

View File

@@ -33,6 +33,8 @@ type SchemaType = z.infer<typeof schema>
export type WhitelistPageProps = {}
const MAX_WHITELIST_COUNT = 5
export default function WhitelistPage(props: WhitelistPageProps) {
const [wait, setWait] = useState(false)
@@ -53,6 +55,8 @@ export default function WhitelistPage(props: WhitelistPageProps) {
setWait(true)
try {
const resp = await listWhitelist({page, size})
console.log(resp, '白名单resp')
if (!resp.success) {
throw new Error(resp.message)
}
@@ -227,9 +231,10 @@ export default function WhitelistPage(props: WhitelistPageProps) {
{/* 全局操作 */}
<section>
<Button onClick={() => openDialog('add')} disabled={wait}>
<Button onClick={() => openDialog('add')} disabled={wait || data.total >= MAX_WHITELIST_COUNT}>
<Plus/>
{data.total >= MAX_WHITELIST_COUNT && '(已达上限)'}
</Button>
{/* <Button
theme="fail"
@@ -254,7 +259,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
}}
columns={[
{
header: `IP 地址`, accessorKey: 'host',
header: `IP 地址`, accessorKey: 'ip',
},
{
header: `备注`, accessorKey: 'remark',
@@ -264,7 +269,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
},
{
id: 'actions', header: `操作`, cell: ({row}) => (
<div className="flex justify-end gap-2">
<div className="flex gap-2">
<Button
className="h-9 w-9"
theme="outline"

View File

@@ -8,7 +8,7 @@ import {Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, Selec
import {Button} from '@/components/ui/button'
import {useForm, useFormContext} from 'react-hook-form'
import {Alert, AlertTitle} from '@/components/ui/alert'
import {Box, CircleAlert, CopyIcon, ExternalLinkIcon, Loader, Timer} from 'lucide-react'
import {Box, CircleAlert, CopyIcon, ExternalLinkIcon, Loader, Plus, Timer} from 'lucide-react'
import {memo, ReactNode, useEffect, useRef, useState} from 'react'
import {useStatus} from '@/lib/states'
import {allResource} from '@/actions/resource'
@@ -72,9 +72,12 @@ export default function Extract(props: ExtractProps) {
)}
>
<CardSection>
<Alert variant="warn">
<Alert variant="warn" className="flex items-center">
<CircleAlert/>
<AlertTitle>IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
<AlertTitle className="flex">IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
<Link href="/admin/whitelist">
<Button ><Plus/></Button>
</Link>
</Alert>
<FormFields/>

View File

@@ -76,33 +76,37 @@ export default function Center() {
className="space-y-4"
name="quota"
label="IP 购买数量">
{({id, field}) => (
<div className="flex gap-2 items-center">
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('quota', Math.max(10_000, Number(field.value) - 5_000))}
disabled={Number(field.value) === 10_000}>
<Minus/>
</Button>
<Input
{...field}
id={id}
type="number"
className="w-40 h-10 border border-gray-200 rounded-sm text-center"
min={10_000}
step={5_000}
/>
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('quota', Number(field.value) + 5_000)}>
<Plus/>
</Button>
</div>
)}
{({id, field}) => {
const value = Number(field.value) || 500
const minValue = 500
const step = 100
return (
<div className="flex gap-2 items-center">
<Button
theme="outline"
type="button"
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg ${
value === minValue ? 'opacity-50 cursor-not-allowed' : ''
}`}
onClick={() => form.setValue('quota', Math.max(minValue, value - step))}
disabled={value === minValue}>
<Minus/>
</Button>
<div className="w-40 h-10 border border-gray-200 rounded-sm flex items-center justify-center">
{value}
</div>
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('quota', value + step)}>
<Plus/>
</Button>
</div>
)
}}
</FormField>
) : (
<>
@@ -133,33 +137,38 @@ export default function Center() {
className="space-y-4"
name="daily_limit"
label="每日提取上限">
{({id, field}) => (
<div className="flex gap-2 items-center">
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('daily_limit', Math.max(2_000, Number(field.value) - 1_000))}
disabled={Number(field.value) === 2_000}>
<Minus/>
</Button>
<Input
{...field}
id={id}
type="number"
className="w-40 h-10 border border-gray-200 rounded-sm text-center"
min={2_000}
step={1_000}
/>
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('daily_limit', Number(field.value) + 1_000)}>
<Plus/>
</Button>
</div>
)}
{({id, field}) => {
const value = Number(field.value) || 100
const minValue = 100
const step = 100
return (
<div className="flex gap-2 items-center">
<Button
theme="outline"
type="button"
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg ${
value === minValue ? 'opacity-50 cursor-not-allowed' : ''
}`}
onClick={() => form.setValue('daily_limit', Math.max(minValue, value - step))}
disabled={value === minValue}>
<Minus/>
</Button>
<div className="w-40 h-10 border border-gray-200 rounded-sm flex items-center justify-center">
{value}
</div>
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('daily_limit', value + step)}>
<Plus/>
</Button>
</div>
)
}}
</FormField>
</>
)}

View File

@@ -88,6 +88,22 @@ export type Channel = {
expiration: Date
created_at: Date
updated_at: Date
proxy: Proxy
port: number
expired_at: Date
}
export type Proxy = {
id: number
ip: string
msc: string
secret: string
type: number
status: number
created_at: Date
updated_at: Date
deleted_at: Date | null
channels: Channel[] | null
version: number
}
export type Announcement = {