修复提取IP表格字段和页面展示 & 前端设置白名单添加数量限制 & 修复长效套餐购买数量和提取数量默认值和递增减
This commit is contained in:
@@ -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>,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user