mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 03:32:17 +08:00
refactor connections
This commit is contained in:
parent
a5b7ed2378
commit
c5c90caa1b
|
@ -16,8 +16,8 @@ const SettingItem: React.FC<Props> = (props) => {
|
||||||
<>
|
<>
|
||||||
<div className="h-[32px] w-full flex justify-between">
|
<div className="h-[32px] w-full flex justify-between">
|
||||||
<div className="h-full flex items-center">
|
<div className="h-full flex items-center">
|
||||||
<h4 className="h-full text-md leading-[32px]">{title}</h4>
|
<h4 className="h-full text-md leading-[32px] whitespace-nowrap mr-2">{title}</h4>
|
||||||
<div>{actions}</div>
|
<div className="mr-2">{actions}</div>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,31 @@
|
||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
interface Props {
|
interface Props {
|
||||||
connection: IMihomoConnectionDetail
|
connection: IMihomoConnectionDetail
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
// sourceIP: string
|
||||||
|
// destinationIP: string
|
||||||
|
// destinationGeoIP: string
|
||||||
|
// destinationIPASN: string
|
||||||
|
// sourcePort: string
|
||||||
|
// destinationPort: string
|
||||||
|
// inboundIP: string
|
||||||
|
// inboundPort: string
|
||||||
|
// inboundName: string
|
||||||
|
// inboundUser: string
|
||||||
|
// host: string
|
||||||
|
// dnsMode: string
|
||||||
|
// specialProxy: string
|
||||||
|
// specialRules: string
|
||||||
|
// remoteDestination: string
|
||||||
|
// dscp: number
|
||||||
|
// sniffHost: string
|
||||||
const ConnectionDetailModal: React.FC<Props> = (props) => {
|
const ConnectionDetailModal: React.FC<Props> = (props) => {
|
||||||
const { connection, onClose } = props
|
const { connection, onClose } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
@ -16,12 +35,80 @@ const ConnectionDetailModal: React.FC<Props> = (props) => {
|
||||||
onOpenChange={onClose}
|
onOpenChange={onClose}
|
||||||
scrollBehavior="inside"
|
scrollBehavior="inside"
|
||||||
>
|
>
|
||||||
<ModalContent>
|
<ModalContent className="flag-emoji break-all">
|
||||||
<ModalHeader className="flex">连接详情</ModalHeader>
|
<ModalHeader className="flex">连接详情</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<pre>
|
<SettingItem title="连接类型">
|
||||||
<code className="select-text">{JSON.stringify(connection, null, 2)}</code>
|
{connection.metadata.type}({connection.metadata.network})
|
||||||
</pre>
|
</SettingItem>
|
||||||
|
<SettingItem title="连接建立时间">{dayjs(connection.start).fromNow()}</SettingItem>
|
||||||
|
<SettingItem title="规则">
|
||||||
|
{connection.rule}
|
||||||
|
{connection.rulePayload ? `(${connection.rulePayload})` : ''}
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="代理链">{[...connection.chains].reverse().join('>>')}</SettingItem>
|
||||||
|
<SettingItem title="上传速度">{calcTraffic(connection.uploadSpeed || 0)}/s</SettingItem>
|
||||||
|
<SettingItem title="下载速度">{calcTraffic(connection.downloadSpeed || 0)}/s</SettingItem>
|
||||||
|
<SettingItem title="上传量">{calcTraffic(connection.upload)}</SettingItem>
|
||||||
|
<SettingItem title="下载量">{calcTraffic(connection.download)}</SettingItem>
|
||||||
|
{connection.metadata.process && (
|
||||||
|
<SettingItem title="进程名">
|
||||||
|
{connection.metadata.process}
|
||||||
|
{connection.metadata.uid ? `(${connection.metadata.uid})` : ''}
|
||||||
|
</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.processPath && (
|
||||||
|
<SettingItem title="进程路径">{connection.metadata.processPath}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.sourceIP && (
|
||||||
|
<SettingItem title="源IP">{connection.metadata.sourceIP}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.destinationIP && (
|
||||||
|
<SettingItem title="目标IP">{connection.metadata.destinationIP}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.destinationGeoIP && (
|
||||||
|
<SettingItem title="目标GeoIP">{connection.metadata.destinationGeoIP}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.destinationIPASN && (
|
||||||
|
<SettingItem title="目标ASN">{connection.metadata.destinationIPASN}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.sourcePort && (
|
||||||
|
<SettingItem title="源端口">{connection.metadata.sourcePort}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.destinationPort && (
|
||||||
|
<SettingItem title="目标端口">{connection.metadata.destinationPort}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.inboundIP && (
|
||||||
|
<SettingItem title="入站IP">{connection.metadata.inboundIP}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.inboundPort && (
|
||||||
|
<SettingItem title="入站端口">{connection.metadata.inboundPort}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.inboundName && (
|
||||||
|
<SettingItem title="入站名称">{connection.metadata.inboundName}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.inboundUser && (
|
||||||
|
<SettingItem title="入站用户">{connection.metadata.inboundUser}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.host && (
|
||||||
|
<SettingItem title="主机">{connection.metadata.host}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.dnsMode && (
|
||||||
|
<SettingItem title="DNS模式">{connection.metadata.dnsMode}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.specialProxy && (
|
||||||
|
<SettingItem title="特殊代理">{connection.metadata.specialProxy}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.specialRules && (
|
||||||
|
<SettingItem title="特殊规则">{connection.metadata.specialRules}</SettingItem>
|
||||||
|
)}
|
||||||
|
{connection.metadata.remoteDestination && (
|
||||||
|
<SettingItem title="远程目标">{connection.metadata.remoteDestination}</SettingItem>
|
||||||
|
)}
|
||||||
|
<SettingItem title="DSCP">{connection.metadata.dscp}</SettingItem>
|
||||||
|
{connection.metadata.sniffHost && (
|
||||||
|
<SettingItem title="嗅探主机">{connection.metadata.sniffHost}</SettingItem>
|
||||||
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button variant="light" onPress={onClose}>
|
<Button variant="light" onPress={onClose}>
|
||||||
|
|
89
src/renderer/src/components/connections/connection-item.tsx
Normal file
89
src/renderer/src/components/connections/connection-item.tsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { Button, Card, CardFooter, CardHeader, Chip } from '@nextui-org/react'
|
||||||
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { CgClose } from 'react-icons/cg'
|
||||||
|
import ConnectionDetailModal from './connection-detail-modal'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
index: number
|
||||||
|
info: IMihomoConnectionDetail
|
||||||
|
close: (id: string) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConnectionItem: React.FC<Props> = (props) => {
|
||||||
|
const { index, info } = props
|
||||||
|
const [isDetailModalOpen, setIsDetailModalOpen] = useState(false)
|
||||||
|
return (
|
||||||
|
<div className={`px-2 pb-2 ${index === 0 ? 'pt-2' : ''}`}>
|
||||||
|
{isDetailModalOpen && (
|
||||||
|
<ConnectionDetailModal onClose={() => setIsDetailModalOpen(false)} connection={info} />
|
||||||
|
)}
|
||||||
|
<Card
|
||||||
|
isPressable
|
||||||
|
className="w-full"
|
||||||
|
onPress={() => {
|
||||||
|
setIsDetailModalOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="w-full flex justify-between">
|
||||||
|
<div className="w-[calc(100%-48px)]">
|
||||||
|
<CardHeader className="pb-0 gap-1">
|
||||||
|
<Chip color="primary" size="sm" radius="sm" variant="light">
|
||||||
|
{info.metadata.type}({info.metadata.network.toUpperCase()})
|
||||||
|
</Chip>
|
||||||
|
<div className="text-ellipsis whitespace-nowrap overflow-hidden">
|
||||||
|
{info.metadata.process || info.metadata.sourceIP}
|
||||||
|
{' -> '}
|
||||||
|
{info.metadata.host ||
|
||||||
|
info.metadata.sniffHost ||
|
||||||
|
info.metadata.remoteDestination ||
|
||||||
|
info.metadata.destinationIP}
|
||||||
|
</div>
|
||||||
|
<small className="whitespace-nowrap text-default-500">
|
||||||
|
{dayjs(info.start).fromNow()}
|
||||||
|
</small>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter
|
||||||
|
onWheel={(e) => {
|
||||||
|
e.currentTarget.scrollLeft += e.deltaY
|
||||||
|
}}
|
||||||
|
className="overscroll-contain pt-1 flex justify-start gap-1 overflow-x-auto no-scrollbar"
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
className="flag-emoji text-ellipsis whitespace-nowrap overflow-hidden"
|
||||||
|
size="sm"
|
||||||
|
radius="sm"
|
||||||
|
variant="bordered"
|
||||||
|
>
|
||||||
|
{info.chains[0]}
|
||||||
|
</Chip>
|
||||||
|
<Chip size="sm" radius="sm" variant="bordered">
|
||||||
|
↑ {calcTraffic(info.upload)} ↓ {calcTraffic(info.download)}
|
||||||
|
</Chip>
|
||||||
|
{info.uploadSpeed !== 0 || info.downloadSpeed !== 0 ? (
|
||||||
|
<Chip color="primary" size="sm" radius="sm" variant="bordered">
|
||||||
|
↑ {calcTraffic(info.uploadSpeed || 0)}/s ↓ {calcTraffic(info.downloadSpeed || 0)}
|
||||||
|
/s
|
||||||
|
</Chip>
|
||||||
|
) : null}
|
||||||
|
</CardFooter>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
color="warning"
|
||||||
|
variant="light"
|
||||||
|
isIconOnly
|
||||||
|
className="mr-2 my-auto"
|
||||||
|
onPress={() => {
|
||||||
|
props.close(info.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CgClose className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectionItem
|
|
@ -5,12 +5,12 @@ import {
|
||||||
startMihomoConnections,
|
startMihomoConnections,
|
||||||
stopMihomoConnections
|
stopMihomoConnections
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
import { Key, useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { Button, Divider, Input } from '@nextui-org/react'
|
import { Button, Divider, Input, Select, SelectItem } from '@nextui-org/react'
|
||||||
import { IoCloseCircle } from 'react-icons/io5'
|
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@nextui-org/react'
|
import ConnectionItem from '@renderer/components/connections/connection-item'
|
||||||
import ConnectionDetailModal from '@renderer/components/connections/connection-detail-modal'
|
import { Virtuoso } from 'react-virtuoso'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
let preData: IMihomoConnectionDetail[] = []
|
let preData: IMihomoConnectionDetail[] = []
|
||||||
|
|
||||||
|
@ -18,10 +18,42 @@ const Connections: React.FC = () => {
|
||||||
const [filter, setFilter] = useState('')
|
const [filter, setFilter] = useState('')
|
||||||
const [connectionsInfo, setConnectionsInfo] = useState<IMihomoConnectionsInfo>()
|
const [connectionsInfo, setConnectionsInfo] = useState<IMihomoConnectionsInfo>()
|
||||||
const [connections, setConnections] = useState<IMihomoConnectionDetail[]>([])
|
const [connections, setConnections] = useState<IMihomoConnectionDetail[]>([])
|
||||||
const [selectedConnection, setSelectedConnection] = useState<IMihomoConnectionDetail>()
|
const [direction, setDirection] = useState(true)
|
||||||
const [isDetailModalOpen, setIsDetailModalOpen] = useState(false)
|
const [sortBy, setSortBy] = useState('time')
|
||||||
|
|
||||||
const filteredConnections = useMemo(() => {
|
const filteredConnections = useMemo(() => {
|
||||||
|
if (sortBy) {
|
||||||
|
connections.sort((a, b) => {
|
||||||
|
if (direction) {
|
||||||
|
switch (sortBy) {
|
||||||
|
case 'time':
|
||||||
|
return dayjs(b.start).unix() - dayjs(a.start).unix()
|
||||||
|
case 'upload':
|
||||||
|
return a.upload - b.upload
|
||||||
|
case 'download':
|
||||||
|
return a.download - b.download
|
||||||
|
case 'uploadSpeed':
|
||||||
|
return (a.uploadSpeed || 0) - (b.uploadSpeed || 0)
|
||||||
|
case 'downloadSpeed':
|
||||||
|
return (a.downloadSpeed || 0) - (b.downloadSpeed || 0)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
switch (sortBy) {
|
||||||
|
case 'time':
|
||||||
|
return dayjs(a.start).unix() - dayjs(b.start).unix()
|
||||||
|
case 'upload':
|
||||||
|
return b.upload - a.upload
|
||||||
|
case 'download':
|
||||||
|
return b.download - a.download
|
||||||
|
case 'uploadSpeed':
|
||||||
|
return (b.uploadSpeed || 0) - (a.uploadSpeed || 0)
|
||||||
|
case 'downloadSpeed':
|
||||||
|
return (b.downloadSpeed || 0) - (a.downloadSpeed || 0)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
if (filter === '') return connections
|
if (filter === '') return connections
|
||||||
return connections?.filter((connection) => {
|
return connections?.filter((connection) => {
|
||||||
const raw = JSON.stringify(connection)
|
const raw = JSON.stringify(connection)
|
||||||
|
@ -70,99 +102,70 @@ const Connections: React.FC = () => {
|
||||||
className="ml-1"
|
className="ml-1"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
onPress={() => mihomoCloseAllConnections()}
|
onPress={() => {
|
||||||
|
if (filter === '') {
|
||||||
|
mihomoCloseAllConnections()
|
||||||
|
} else {
|
||||||
|
filteredConnections.forEach((conn) => {
|
||||||
|
mihomoCloseConnection(conn.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
关闭所有连接
|
关闭所有连接({filteredConnections.length})
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isDetailModalOpen && selectedConnection && (
|
|
||||||
<ConnectionDetailModal
|
|
||||||
onClose={() => setIsDetailModalOpen(false)}
|
|
||||||
connection={selectedConnection}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="overflow-x-auto sticky top-[49px] z-40">
|
<div className="overflow-x-auto sticky top-[49px] z-40">
|
||||||
<div className="flex p-2">
|
<div className="flex p-2 gap-2">
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
variant="flat"
|
||||||
size="sm"
|
size="sm"
|
||||||
value={filter}
|
value={filter}
|
||||||
placeholder="筛选过滤"
|
placeholder="筛选过滤"
|
||||||
isClearable
|
isClearable
|
||||||
onValueChange={setFilter}
|
onValueChange={setFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
size="sm"
|
||||||
|
className="w-[180px]"
|
||||||
|
selectedKeys={new Set([sortBy])}
|
||||||
|
onSelectionChange={async (v) => {
|
||||||
|
setSortBy(v.currentKey as string)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectItem key="upload">上传量</SelectItem>
|
||||||
|
<SelectItem key="download">下载量</SelectItem>
|
||||||
|
<SelectItem key="uploadSpeed">上传速度</SelectItem>
|
||||||
|
<SelectItem key="downloadSpeed">下载速度</SelectItem>
|
||||||
|
<SelectItem key="time">时间</SelectItem>
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onPress={() => {
|
||||||
|
setDirection((pre) => !pre)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{direction ? '升序' : '降序'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<div className="h-[calc(100vh-100px)] mt-[1px]">
|
||||||
onRowAction={(id: Key) => {
|
<Virtuoso
|
||||||
setSelectedConnection(connections.find((c) => c.id === (id as string)))
|
data={filteredConnections}
|
||||||
setIsDetailModalOpen(true)
|
itemContent={(i, connection) => (
|
||||||
}}
|
<ConnectionItem
|
||||||
isHeaderSticky
|
close={mihomoCloseConnection}
|
||||||
isStriped
|
index={i}
|
||||||
className="h-[calc(100vh-100px)] p-2"
|
key={connection.id}
|
||||||
>
|
info={connection}
|
||||||
<TableHeader>
|
/>
|
||||||
<TableColumn key="type">类型</TableColumn>
|
|
||||||
<TableColumn key="origin">来源</TableColumn>
|
|
||||||
<TableColumn key="target">目标</TableColumn>
|
|
||||||
<TableColumn key="rule">规则</TableColumn>
|
|
||||||
<TableColumn key="chains">链路</TableColumn>
|
|
||||||
<TableColumn key="close">关闭</TableColumn>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody items={filteredConnections ?? []}>
|
|
||||||
{(item) => (
|
|
||||||
<TableRow key={item.id}>
|
|
||||||
<TableCell>
|
|
||||||
{item.metadata.type}({item.metadata.network})
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{item.metadata.process || item.metadata.sourceIP}</TableCell>
|
|
||||||
<TableCell className="max-w-[100px] text-ellipsis whitespace-nowrap overflow-hidden">
|
|
||||||
{item.metadata.host ||
|
|
||||||
item.metadata.sniffHost ||
|
|
||||||
item.metadata.remoteDestination ||
|
|
||||||
item.metadata.destinationIP}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="max-w-[100px] text-ellipsis whitespace-nowrap overflow-hidden">
|
|
||||||
{item.rule} {item.rulePayload}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="flag-emoji max-w-[200px] text-ellipsis whitespace-nowrap overflow-hidden">
|
|
||||||
{item.chains.reverse().join('::')}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Button
|
|
||||||
isIconOnly
|
|
||||||
size="sm"
|
|
||||||
color="danger"
|
|
||||||
variant="light"
|
|
||||||
onPress={() => mihomoCloseConnection(item.id)}
|
|
||||||
>
|
|
||||||
<IoCloseCircle className="text-lg" />
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
/>
|
||||||
</Table>
|
</div>
|
||||||
{/* {filteredConnections?.map((connection) => {
|
|
||||||
return (
|
|
||||||
<ConnectionItem
|
|
||||||
mutate={mutate}
|
|
||||||
key={connection.id}
|
|
||||||
id={connection.id}
|
|
||||||
chains={connection.chains}
|
|
||||||
download={connection.download}
|
|
||||||
upload={connection.upload}
|
|
||||||
metadata={connection.metadata}
|
|
||||||
rule={connection.rule}
|
|
||||||
rulePayload={connection.rulePayload}
|
|
||||||
start={connection.start}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})} */}
|
|
||||||
</BasePage>
|
</BasePage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ const Logs: React.FC = () => {
|
||||||
<div className="sticky top-[49px] z-40">
|
<div className="sticky top-[49px] z-40">
|
||||||
<div className="w-full flex p-2">
|
<div className="w-full flex p-2">
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
value={filter}
|
value={filter}
|
||||||
placeholder="筛选过滤"
|
placeholder="筛选过滤"
|
||||||
|
|
|
@ -136,7 +136,6 @@ const Override: React.FC = () => {
|
||||||
<div className="sticky top-[49px] z-40 backdrop-blur bg-background/40">
|
<div className="sticky top-[49px] z-40 backdrop-blur bg-background/40">
|
||||||
<div className="flex p-2">
|
<div className="flex p-2">
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
value={url}
|
value={url}
|
||||||
onValueChange={setUrl}
|
onValueChange={setUrl}
|
||||||
|
|
|
@ -126,7 +126,7 @@ const Profiles: React.FC = () => {
|
||||||
<div className="sticky top-[49px] z-40 backdrop-blur bg-background/40">
|
<div className="sticky top-[49px] z-40 backdrop-blur bg-background/40">
|
||||||
<div className="flex p-2">
|
<div className="flex p-2">
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
// variant="bordered"
|
||||||
size="sm"
|
size="sm"
|
||||||
value={url}
|
value={url}
|
||||||
onValueChange={setUrl}
|
onValueChange={setUrl}
|
||||||
|
|
|
@ -22,10 +22,9 @@ const Rules: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage title="分流规则">
|
<BasePage title="分流规则">
|
||||||
<div className="sticky top-[50px] z-40">
|
<div className="sticky top-[49px] z-40">
|
||||||
<div className="flex p-2">
|
<div className="flex p-2">
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
value={filter}
|
value={filter}
|
||||||
placeholder="筛选过滤"
|
placeholder="筛选过滤"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user