diff --git a/src/renderer/src/components/base/base-setting-item.tsx b/src/renderer/src/components/base/base-setting-item.tsx index f6dd26d..24b54de 100644 --- a/src/renderer/src/components/base/base-setting-item.tsx +++ b/src/renderer/src/components/base/base-setting-item.tsx @@ -16,8 +16,8 @@ const SettingItem: React.FC = (props) => { <>
-

{title}

-
{actions}
+

{title}

+
{actions}
{children}
diff --git a/src/renderer/src/components/connections/connection-detail-modal.tsx b/src/renderer/src/components/connections/connection-detail-modal.tsx index 216041f..e147105 100644 --- a/src/renderer/src/components/connections/connection-detail-modal.tsx +++ b/src/renderer/src/components/connections/connection-detail-modal.tsx @@ -1,12 +1,31 @@ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react' import React from 'react' +import SettingItem from '../base/base-setting-item' +import { calcTraffic } from '@renderer/utils/calc' +import dayjs from 'dayjs' interface Props { connection: IMihomoConnectionDetail 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) => { const { connection, onClose } = props - return ( = (props) => { onOpenChange={onClose} scrollBehavior="inside" > - + 连接详情 -
-            {JSON.stringify(connection, null, 2)}
-          
+ + {connection.metadata.type}({connection.metadata.network}) + + {dayjs(connection.start).fromNow()} + + {connection.rule} + {connection.rulePayload ? `(${connection.rulePayload})` : ''} + + {[...connection.chains].reverse().join('>>')} + {calcTraffic(connection.uploadSpeed || 0)}/s + {calcTraffic(connection.downloadSpeed || 0)}/s + {calcTraffic(connection.upload)} + {calcTraffic(connection.download)} + {connection.metadata.process && ( + + {connection.metadata.process} + {connection.metadata.uid ? `(${connection.metadata.uid})` : ''} + + )} + {connection.metadata.processPath && ( + {connection.metadata.processPath} + )} + {connection.metadata.sourceIP && ( + {connection.metadata.sourceIP} + )} + {connection.metadata.destinationIP && ( + {connection.metadata.destinationIP} + )} + {connection.metadata.destinationGeoIP && ( + {connection.metadata.destinationGeoIP} + )} + {connection.metadata.destinationIPASN && ( + {connection.metadata.destinationIPASN} + )} + {connection.metadata.sourcePort && ( + {connection.metadata.sourcePort} + )} + {connection.metadata.destinationPort && ( + {connection.metadata.destinationPort} + )} + {connection.metadata.inboundIP && ( + {connection.metadata.inboundIP} + )} + {connection.metadata.inboundPort && ( + {connection.metadata.inboundPort} + )} + {connection.metadata.inboundName && ( + {connection.metadata.inboundName} + )} + {connection.metadata.inboundUser && ( + {connection.metadata.inboundUser} + )} + {connection.metadata.host && ( + {connection.metadata.host} + )} + {connection.metadata.dnsMode && ( + {connection.metadata.dnsMode} + )} + {connection.metadata.specialProxy && ( + {connection.metadata.specialProxy} + )} + {connection.metadata.specialRules && ( + {connection.metadata.specialRules} + )} + {connection.metadata.remoteDestination && ( + {connection.metadata.remoteDestination} + )} + {connection.metadata.dscp} + {connection.metadata.sniffHost && ( + {connection.metadata.sniffHost} + )}
+ + + + ) +} + +export default ConnectionItem diff --git a/src/renderer/src/pages/connections.tsx b/src/renderer/src/pages/connections.tsx index deece26..090851d 100644 --- a/src/renderer/src/pages/connections.tsx +++ b/src/renderer/src/pages/connections.tsx @@ -5,12 +5,12 @@ import { startMihomoConnections, stopMihomoConnections } from '@renderer/utils/ipc' -import { Key, useEffect, useMemo, useState } from 'react' -import { Button, Divider, Input } from '@nextui-org/react' -import { IoCloseCircle } from 'react-icons/io5' +import { useEffect, useMemo, useState } from 'react' +import { Button, Divider, Input, Select, SelectItem } from '@nextui-org/react' import { calcTraffic } from '@renderer/utils/calc' -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@nextui-org/react' -import ConnectionDetailModal from '@renderer/components/connections/connection-detail-modal' +import ConnectionItem from '@renderer/components/connections/connection-item' +import { Virtuoso } from 'react-virtuoso' +import dayjs from 'dayjs' let preData: IMihomoConnectionDetail[] = [] @@ -18,10 +18,42 @@ const Connections: React.FC = () => { const [filter, setFilter] = useState('') const [connectionsInfo, setConnectionsInfo] = useState() const [connections, setConnections] = useState([]) - const [selectedConnection, setSelectedConnection] = useState() - const [isDetailModalOpen, setIsDetailModalOpen] = useState(false) - + const [direction, setDirection] = useState(true) + const [sortBy, setSortBy] = useState('time') 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 return connections?.filter((connection) => { const raw = JSON.stringify(connection) @@ -70,99 +102,70 @@ const Connections: React.FC = () => { className="ml-1" size="sm" color="primary" - onPress={() => mihomoCloseAllConnections()} + onPress={() => { + if (filter === '') { + mihomoCloseAllConnections() + } else { + filteredConnections.forEach((conn) => { + mihomoCloseConnection(conn.id) + }) + } + }} > - 关闭所有连接 + 关闭所有连接({filteredConnections.length}) } > - {isDetailModalOpen && selectedConnection && ( - setIsDetailModalOpen(false)} - connection={selectedConnection} - /> - )}
-
+
+ + +
- { - setSelectedConnection(connections.find((c) => c.id === (id as string))) - setIsDetailModalOpen(true) - }} - isHeaderSticky - isStriped - className="h-[calc(100vh-100px)] p-2" - > - - 类型 - 来源 - 目标 - 规则 - 链路 - 关闭 - - - {(item) => ( - - - {item.metadata.type}({item.metadata.network}) - - {item.metadata.process || item.metadata.sourceIP} - - {item.metadata.host || - item.metadata.sniffHost || - item.metadata.remoteDestination || - item.metadata.destinationIP} - - - {item.rule} {item.rulePayload} - - - {item.chains.reverse().join('::')} - - - - - +
+ ( + )} - -
- {/* {filteredConnections?.map((connection) => { - return ( - - ) - })} */} + /> +
) } diff --git a/src/renderer/src/pages/logs.tsx b/src/renderer/src/pages/logs.tsx index 7028035..888c85b 100644 --- a/src/renderer/src/pages/logs.tsx +++ b/src/renderer/src/pages/logs.tsx @@ -48,7 +48,6 @@ const Logs: React.FC = () => {
{
{
{ return ( -
+