修复提取IP表格字段和页面展示 & 前端设置白名单添加数量限制 & 修复长效套餐购买数量和提取数量默认值和递增减
This commit is contained in:
@@ -155,34 +155,52 @@ export default function ChannelsPage(props: ChannelsPageProps) {
|
|||||||
}}
|
}}
|
||||||
columns={[
|
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 (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1 min-w-0">
|
||||||
{row.original.auth_ip && (
|
{hasWhitelist ? (
|
||||||
<>
|
<div className="flex flex-col">
|
||||||
<span className="text-weak">IP 白名单</span>
|
<span >白名单</span>
|
||||||
<span>{row.original.whitelists.replaceAll(',', ', ')}</span>
|
<div className="flex flex-wrap gap-1 max-w-[200px]">
|
||||||
</>
|
{channel.whitelists.split(',').map((ip, index) => (
|
||||||
)}
|
<span
|
||||||
{row.original.auth_pass && (
|
key={index}
|
||||||
<>
|
className="inline-block px-2 py-0.5 bg-gray-100 rounded text-xs text-gray-700 break-all"
|
||||||
<span className="text-weak">账号密码</span>
|
>
|
||||||
<span>
|
{ip.trim()}
|
||||||
{row.original.username}
|
</span>
|
||||||
:
|
))}
|
||||||
{row.original.password}
|
</div>
|
||||||
</span>
|
</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>
|
</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>,
|
header: '操作', cell: ({row}) => <span>-</span>,
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type SchemaType = z.infer<typeof schema>
|
|||||||
|
|
||||||
export type WhitelistPageProps = {}
|
export type WhitelistPageProps = {}
|
||||||
|
|
||||||
|
const MAX_WHITELIST_COUNT = 5
|
||||||
|
|
||||||
export default function WhitelistPage(props: WhitelistPageProps) {
|
export default function WhitelistPage(props: WhitelistPageProps) {
|
||||||
const [wait, setWait] = useState(false)
|
const [wait, setWait] = useState(false)
|
||||||
|
|
||||||
@@ -53,6 +55,8 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
|||||||
setWait(true)
|
setWait(true)
|
||||||
try {
|
try {
|
||||||
const resp = await listWhitelist({page, size})
|
const resp = await listWhitelist({page, size})
|
||||||
|
console.log(resp, '白名单resp')
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
throw new Error(resp.message)
|
throw new Error(resp.message)
|
||||||
}
|
}
|
||||||
@@ -227,9 +231,10 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
|||||||
|
|
||||||
{/* 全局操作 */}
|
{/* 全局操作 */}
|
||||||
<section>
|
<section>
|
||||||
<Button onClick={() => openDialog('add')} disabled={wait}>
|
<Button onClick={() => openDialog('add')} disabled={wait || data.total >= MAX_WHITELIST_COUNT}>
|
||||||
<Plus/>
|
<Plus/>
|
||||||
添加白名单
|
添加白名单
|
||||||
|
{data.total >= MAX_WHITELIST_COUNT && '(已达上限)'}
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button
|
{/* <Button
|
||||||
theme="fail"
|
theme="fail"
|
||||||
@@ -254,7 +259,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
|||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: `IP 地址`, accessorKey: 'host',
|
header: `IP 地址`, accessorKey: 'ip',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: `备注`, accessorKey: 'remark',
|
header: `备注`, accessorKey: 'remark',
|
||||||
@@ -264,7 +269,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'actions', header: `操作`, cell: ({row}) => (
|
id: 'actions', header: `操作`, cell: ({row}) => (
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
className="h-9 w-9"
|
className="h-9 w-9"
|
||||||
theme="outline"
|
theme="outline"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, Selec
|
|||||||
import {Button} from '@/components/ui/button'
|
import {Button} from '@/components/ui/button'
|
||||||
import {useForm, useFormContext} from 'react-hook-form'
|
import {useForm, useFormContext} from 'react-hook-form'
|
||||||
import {Alert, AlertTitle} from '@/components/ui/alert'
|
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 {memo, ReactNode, useEffect, useRef, useState} from 'react'
|
||||||
import {useStatus} from '@/lib/states'
|
import {useStatus} from '@/lib/states'
|
||||||
import {allResource} from '@/actions/resource'
|
import {allResource} from '@/actions/resource'
|
||||||
@@ -72,9 +72,12 @@ export default function Extract(props: ExtractProps) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardSection>
|
<CardSection>
|
||||||
<Alert variant="warn">
|
<Alert variant="warn" className="flex items-center">
|
||||||
<CircleAlert/>
|
<CircleAlert/>
|
||||||
<AlertTitle>提取IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
|
<AlertTitle className="flex">提取IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
|
||||||
|
<Link href="/admin/whitelist">
|
||||||
|
<Button ><Plus/>添加白名单</Button>
|
||||||
|
</Link>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<FormFields/>
|
<FormFields/>
|
||||||
|
|||||||
@@ -76,33 +76,37 @@ export default function Center() {
|
|||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
name="quota"
|
name="quota"
|
||||||
label="IP 购买数量">
|
label="IP 购买数量">
|
||||||
{({id, field}) => (
|
{({id, field}) => {
|
||||||
<div className="flex gap-2 items-center">
|
const value = Number(field.value) || 500
|
||||||
<Button
|
const minValue = 500
|
||||||
theme="outline"
|
const step = 100
|
||||||
type="button"
|
return (
|
||||||
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
|
<div className="flex gap-2 items-center">
|
||||||
onClick={() => form.setValue('quota', Math.max(10_000, Number(field.value) - 5_000))}
|
<Button
|
||||||
disabled={Number(field.value) === 10_000}>
|
theme="outline"
|
||||||
<Minus/>
|
type="button"
|
||||||
</Button>
|
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg ${
|
||||||
<Input
|
value === minValue ? 'opacity-50 cursor-not-allowed' : ''
|
||||||
{...field}
|
}`}
|
||||||
id={id}
|
onClick={() => form.setValue('quota', Math.max(minValue, value - step))}
|
||||||
type="number"
|
disabled={value === minValue}>
|
||||||
className="w-40 h-10 border border-gray-200 rounded-sm text-center"
|
<Minus/>
|
||||||
min={10_000}
|
</Button>
|
||||||
step={5_000}
|
|
||||||
/>
|
<div className="w-40 h-10 border border-gray-200 rounded-sm flex items-center justify-center">
|
||||||
<Button
|
{value}
|
||||||
theme="outline"
|
</div>
|
||||||
type="button"
|
|
||||||
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
|
<Button
|
||||||
onClick={() => form.setValue('quota', Number(field.value) + 5_000)}>
|
theme="outline"
|
||||||
<Plus/>
|
type="button"
|
||||||
</Button>
|
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
|
||||||
</div>
|
onClick={() => form.setValue('quota', value + step)}>
|
||||||
)}
|
<Plus/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</FormField>
|
</FormField>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -133,33 +137,38 @@ export default function Center() {
|
|||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
name="daily_limit"
|
name="daily_limit"
|
||||||
label="每日提取上限">
|
label="每日提取上限">
|
||||||
{({id, field}) => (
|
{({id, field}) => {
|
||||||
<div className="flex gap-2 items-center">
|
const value = Number(field.value) || 100
|
||||||
<Button
|
const minValue = 100
|
||||||
theme="outline"
|
const step = 100
|
||||||
type="button"
|
|
||||||
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
|
return (
|
||||||
onClick={() => form.setValue('daily_limit', Math.max(2_000, Number(field.value) - 1_000))}
|
<div className="flex gap-2 items-center">
|
||||||
disabled={Number(field.value) === 2_000}>
|
<Button
|
||||||
<Minus/>
|
theme="outline"
|
||||||
</Button>
|
type="button"
|
||||||
<Input
|
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg ${
|
||||||
{...field}
|
value === minValue ? 'opacity-50 cursor-not-allowed' : ''
|
||||||
id={id}
|
}`}
|
||||||
type="number"
|
onClick={() => form.setValue('daily_limit', Math.max(minValue, value - step))}
|
||||||
className="w-40 h-10 border border-gray-200 rounded-sm text-center"
|
disabled={value === minValue}>
|
||||||
min={2_000}
|
<Minus/>
|
||||||
step={1_000}
|
</Button>
|
||||||
/>
|
|
||||||
<Button
|
<div className="w-40 h-10 border border-gray-200 rounded-sm flex items-center justify-center">
|
||||||
theme="outline"
|
{value}
|
||||||
type="button"
|
</div>
|
||||||
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)}>
|
<Button
|
||||||
<Plus/>
|
theme="outline"
|
||||||
</Button>
|
type="button"
|
||||||
</div>
|
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>
|
</FormField>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -88,6 +88,22 @@ export type Channel = {
|
|||||||
expiration: Date
|
expiration: Date
|
||||||
created_at: Date
|
created_at: Date
|
||||||
updated_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 = {
|
export type Announcement = {
|
||||||
|
|||||||
Reference in New Issue
Block a user