mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Feature: add connections filter
This commit is contained in:
parent
9f602e23a4
commit
d5fa59f477
35
src/containers/Connections/Devices/index.tsx
Normal file
35
src/containers/Connections/Devices/index.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { BaseComponentProps } from '@models'
|
||||
import './style.scss'
|
||||
|
||||
interface DevicesProps extends BaseComponentProps {
|
||||
devices: Array<{ label: string, number: number }>
|
||||
selected: string
|
||||
onChange?: (label: string) => void
|
||||
}
|
||||
|
||||
export function Devices (props: DevicesProps) {
|
||||
const { className, style } = props
|
||||
const classname = classnames('connections-devices', className)
|
||||
function handleSelected (label: string) {
|
||||
props.onChange?.(label)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classname} style={style}>
|
||||
<div className={classnames('connections-devices-item', { selected: props.selected === '' })} onClick={() => handleSelected('')}>全部</div>
|
||||
{
|
||||
props.devices.map(
|
||||
device => (
|
||||
<div
|
||||
className={classnames('connections-devices-item', { selected: props.selected === device.label })}
|
||||
onClick={() => handleSelected(device.label)}>
|
||||
{ device.label } ({ device.number })
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
21
src/containers/Connections/Devices/style.scss
Normal file
21
src/containers/Connections/Devices/style.scss
Normal file
@ -0,0 +1,21 @@
|
||||
@import '~@styles/variables';
|
||||
|
||||
.connections-devices {
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.connections-devices-item {
|
||||
padding: 4px 10px;
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
color: $color-gray-darken;
|
||||
background-color: $color-gray-light;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: color .3s ease;
|
||||
|
||||
&.selected {
|
||||
color: $color-primary-dark;
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import React, { useMemo, useLayoutEffect, useCallback, useRef } from 'react'
|
||||
import { Cell, Column, ColumnInstance, TableOptions, useBlockLayout, useResizeColumns, UseResizeColumnsColumnProps, UseResizeColumnsOptions, useSortBy, UseSortByColumnOptions, UseSortByColumnProps, UseSortByOptions, useTable } from 'react-table'
|
||||
import React, { useMemo, useLayoutEffect, useCallback, useRef, useState } from 'react'
|
||||
import { Cell, Column, ColumnInstance, TableInstance, TableOptions, useBlockLayout, useFilters, UseFiltersInstanceProps, UseFiltersOptions, useResizeColumns, UseResizeColumnsColumnProps, UseResizeColumnsOptions, useSortBy, UseSortByColumnOptions, UseSortByColumnProps, UseSortByOptions, useTable } from 'react-table'
|
||||
import classnames from 'classnames'
|
||||
import { useScroll } from 'react-use'
|
||||
import { groupBy } from 'lodash'
|
||||
import { Header, Card, Checkbox, Modal, Icon } from '@components'
|
||||
import { useI18n } from '@stores'
|
||||
import * as API from '@lib/request'
|
||||
@ -9,6 +10,7 @@ import { StreamReader } from '@lib/streamer'
|
||||
import { useObject, useVisible } from '@lib/hook'
|
||||
import { fromNow } from '@lib/date'
|
||||
import { RuleType } from '@models'
|
||||
import { Devices } from './Devices'
|
||||
import { useConnections } from './store'
|
||||
import './style.scss'
|
||||
|
||||
@ -39,7 +41,12 @@ type TableColumnOption<D extends object = {}> =
|
||||
|
||||
interface ITableOptions<D extends object = {}> extends
|
||||
TableOptions<D>,
|
||||
UseSortByOptions<D> {}
|
||||
UseSortByOptions<D>,
|
||||
UseFiltersOptions<D> {}
|
||||
|
||||
interface ITableInstance<D extends object = {}> extends
|
||||
TableInstance<D>,
|
||||
UseFiltersInstanceProps<D> {}
|
||||
|
||||
function formatTraffic(num: number) {
|
||||
const s = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
@ -117,6 +124,10 @@ export default function Connections() {
|
||||
completed: !!c.completed
|
||||
})
|
||||
), [connections])
|
||||
const devices = useMemo(() => {
|
||||
const gb = groupBy(connections, 'metadata.sourceIP')
|
||||
return Object.keys(gb).map(key => ({ label: key, number: gb[key].length }))
|
||||
}, [connections])
|
||||
|
||||
// table
|
||||
const tableRef = useRef<HTMLDivElement>(null)
|
||||
@ -181,18 +192,21 @@ export default function Connections() {
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow
|
||||
prepareRow,
|
||||
setFilter
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
autoResetSortBy: false,
|
||||
autoResetFilters: false,
|
||||
initialState: { sortBy: [{ id: Columns.Time, desc: false }] }
|
||||
} as ITableOptions<formatConnection>,
|
||||
useResizeColumns,
|
||||
useBlockLayout,
|
||||
useFilters,
|
||||
useSortBy
|
||||
)
|
||||
) as ITableInstance<formatConnection>
|
||||
const headerGroup = useMemo(() => headerGroups[0], [headerGroups])
|
||||
const renderCell = useCallback(function (cell: Cell<formatConnection>) {
|
||||
switch (cell.column.id) {
|
||||
@ -208,6 +222,13 @@ export default function Connections() {
|
||||
}
|
||||
}, [lang])
|
||||
|
||||
// filter
|
||||
const [device, setDevice] = useState('')
|
||||
function handleDeviceSelected (label: string) {
|
||||
setDevice(label)
|
||||
setFilter?.(Columns.SourceIP, label)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<Header title={t('title')}>
|
||||
@ -217,6 +238,7 @@ export default function Connections() {
|
||||
<Checkbox className="connections-filter" checked={save} onChange={toggleSave}>{t('keepClosed')}</Checkbox>
|
||||
<Icon className="connections-filter dangerous" onClick={show} type="close-all" size={20} />
|
||||
</Header>
|
||||
{ devices.length > 1 && <Devices devices={devices} selected={device} onChange={handleDeviceSelected} /> }
|
||||
<Card className="connections-card">
|
||||
<div {...getTableProps()} className="connections" ref={tableRef}>
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="connections-header">
|
||||
|
@ -27,7 +27,7 @@
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: $color-gray-darken;
|
||||
background: #f3f6f9;
|
||||
background: $color-gray-light;
|
||||
height: $height;
|
||||
line-height: $height;
|
||||
font-weight: 500;
|
||||
@ -105,7 +105,7 @@
|
||||
}
|
||||
|
||||
&.completed {
|
||||
background-color: darken(#f3f6f9, 3%);
|
||||
background-color: darken($color-gray-light, 3%);
|
||||
color: rgba($color-primary-darken, 50%);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
@import '~@styles/variables';
|
||||
|
||||
.logs-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -13,7 +15,7 @@
|
||||
list-style: none;
|
||||
padding: 10px;
|
||||
border-radius: 2px;
|
||||
background-color: #f3f6f9;
|
||||
background-color: $color-gray-light;
|
||||
font-size: 12px;
|
||||
color: #73808f;
|
||||
overflow-y: auto;
|
||||
|
@ -14,6 +14,7 @@ $color-primary-lightly: #e4eaef;
|
||||
|
||||
// common colors
|
||||
$color-gray: #d8dee2;
|
||||
$color-gray-light: #f3f6f9;
|
||||
$color-gray-dark: #b7c5d6;
|
||||
$color-gray-darken: #909399;
|
||||
$color-white: #fff;
|
||||
|
Loading…
x
Reference in New Issue
Block a user