mirror of
https://github.com/MetaCubeX/metacubexd.git
synced 2024-11-10 05:15:35 +08:00
feat: columns hide
This commit is contained in:
parent
adfa4324ef
commit
cb9970c29e
46
src/components/ConnectionsModal.tsx
Normal file
46
src/components/ConnectionsModal.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { For } from 'solid-js'
|
||||||
|
import { AccessorKey } from '~/config/connection'
|
||||||
|
|
||||||
|
type ColumnVisibility = Partial<Record<AccessorKey, boolean>>
|
||||||
|
|
||||||
|
export default (props: {
|
||||||
|
data: ColumnVisibility
|
||||||
|
onChange: (value: ColumnVisibility) => void
|
||||||
|
}) => {
|
||||||
|
const { onChange } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input type="checkbox" id="connection-modal" class="modal-toggle" />
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-box w-80">
|
||||||
|
<For
|
||||||
|
each={Object.values(AccessorKey).filter(
|
||||||
|
(i) => ![AccessorKey.Close, AccessorKey.ID].includes(i),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{(key) => (
|
||||||
|
<div class="m-1 flex justify-between p-1">
|
||||||
|
{key}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle"
|
||||||
|
checked={props.data[key]}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange({
|
||||||
|
...props.data,
|
||||||
|
[key]: e.target.checked,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
<label class="modal-backdrop" htmlFor="connection-modal">
|
||||||
|
Close
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -32,8 +32,8 @@ export default (props: {
|
|||||||
const formatProxyType = (type: string) => {
|
const formatProxyType = (type: string) => {
|
||||||
const t = type.toLowerCase()
|
const t = type.toLowerCase()
|
||||||
|
|
||||||
if (t === 'shadowsocks') {
|
if (t.includes('shadowsocks')) {
|
||||||
return 'ss'
|
return t.replace('shadowsocks', 'ss')
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
@ -42,7 +42,7 @@ export default (props: {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={twMerge(
|
class={twMerge(
|
||||||
'card card-bordered tooltip tooltip-bottom card-compact flex gap-1 p-4',
|
'card card-bordered tooltip tooltip-bottom card-compact flex gap-1 p-3',
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-primary bg-success-content text-success'
|
? 'border-primary bg-success-content text-success'
|
||||||
: 'border-secondary',
|
: 'border-secondary',
|
||||||
|
15
src/config/connection.ts
Normal file
15
src/config/connection.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export enum AccessorKey {
|
||||||
|
Close = 'Close',
|
||||||
|
ID = 'ID',
|
||||||
|
Type = 'Type',
|
||||||
|
Process = 'Process',
|
||||||
|
Host = 'Host',
|
||||||
|
Rule = 'Rule',
|
||||||
|
Chains = 'Chains',
|
||||||
|
DlSpeed = 'DL Speed',
|
||||||
|
ULSpeed = 'UL Speed',
|
||||||
|
Download = 'DL',
|
||||||
|
Upload = 'UL',
|
||||||
|
Source = 'Source',
|
||||||
|
Destination = 'Destination',
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import { createEventSignal } from '@solid-primitives/event-listener'
|
import { createEventSignal } from '@solid-primitives/event-listener'
|
||||||
|
import { makePersisted } from '@solid-primitives/storage'
|
||||||
import { createReconnectingWS } from '@solid-primitives/websocket'
|
import { createReconnectingWS } from '@solid-primitives/websocket'
|
||||||
import {
|
import {
|
||||||
IconCircleX,
|
IconCircleX,
|
||||||
|
IconSettings,
|
||||||
IconSortAscending,
|
IconSortAscending,
|
||||||
IconSortDescending,
|
IconSortDescending,
|
||||||
} from '@tabler/icons-solidjs'
|
} from '@tabler/icons-solidjs'
|
||||||
@ -17,6 +19,8 @@ import byteSize from 'byte-size'
|
|||||||
import { isIPv6 } from 'is-ip'
|
import { isIPv6 } from 'is-ip'
|
||||||
import { For, createEffect, createSignal } from 'solid-js'
|
import { For, createEffect, createSignal } from 'solid-js'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
import ConnectionsModal from '~/components/ConnectionsModal'
|
||||||
|
import { AccessorKey } from '~/config/connection'
|
||||||
import { secret, useRequest, wsEndpointURL } from '~/signals'
|
import { secret, useRequest, wsEndpointURL } from '~/signals'
|
||||||
import type { Connection } from '~/types'
|
import type { Connection } from '~/types'
|
||||||
|
|
||||||
@ -25,7 +29,22 @@ type ConnectionWithSpeed = Connection & {
|
|||||||
uploadSpeed: number
|
uploadSpeed: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ColumnVisibility = Partial<Record<AccessorKey, boolean>>
|
||||||
|
|
||||||
|
const initColumnVisibility = {
|
||||||
|
...Object.fromEntries(Object.values(AccessorKey).map((i) => [i, true])),
|
||||||
|
[AccessorKey.ID]: false,
|
||||||
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const [columnVisibility, setColumnVisibility] = makePersisted(
|
||||||
|
createSignal<ColumnVisibility>(initColumnVisibility),
|
||||||
|
{
|
||||||
|
name: 'columnVisibility',
|
||||||
|
storage: localStorage,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const request = useRequest()
|
const request = useRequest()
|
||||||
const [search, setSearch] = createSignal('')
|
const [search, setSearch] = createSignal('')
|
||||||
|
|
||||||
@ -80,7 +99,7 @@ export default () => {
|
|||||||
|
|
||||||
const columns: ColumnDef<ConnectionWithSpeed>[] = [
|
const columns: ColumnDef<ConnectionWithSpeed>[] = [
|
||||||
{
|
{
|
||||||
id: 'close',
|
accessorKey: AccessorKey.Close,
|
||||||
header: () => (
|
header: () => (
|
||||||
<div class="flex h-full items-center">
|
<div class="flex h-full items-center">
|
||||||
<button
|
<button
|
||||||
@ -103,63 +122,63 @@ export default () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'ID',
|
accessorKey: AccessorKey.ID,
|
||||||
accessorFn: (row) => row.id,
|
accessorFn: (row) => row.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Type',
|
accessorKey: AccessorKey.Type,
|
||||||
accessorFn: (row) => `${row.metadata.type}(${row.metadata.network})`,
|
accessorFn: (row) => `${row.metadata.type}(${row.metadata.network})`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Process',
|
accessorKey: AccessorKey.Process,
|
||||||
accessorFn: (row) => row.metadata.process || '-',
|
accessorFn: (row) => row.metadata.process || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Host',
|
accessorKey: AccessorKey.Host,
|
||||||
accessorFn: (row) =>
|
accessorFn: (row) =>
|
||||||
row.metadata.host ? row.metadata.host : row.metadata.destinationIP,
|
row.metadata.host ? row.metadata.host : row.metadata.destinationIP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Rule',
|
accessorKey: AccessorKey.Rule,
|
||||||
accessorFn: (row) =>
|
accessorFn: (row) =>
|
||||||
!row.rulePayload ? row.rule : `${row.rule} :: ${row.rulePayload}`,
|
!row.rulePayload ? row.rule : `${row.rule} :: ${row.rulePayload}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Chains',
|
accessorKey: AccessorKey.Chains,
|
||||||
accessorFn: (row) => row.chains.slice().reverse().join(' :: '),
|
accessorFn: (row) => row.chains.slice().reverse().join(' :: '),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'DL Speed',
|
accessorKey: AccessorKey.DlSpeed,
|
||||||
accessorFn: (row) => `${byteSize(row.downloadSpeed)}/s`,
|
accessorFn: (row) => `${byteSize(row.downloadSpeed)}/s`,
|
||||||
sortingFn: (prev, next) =>
|
sortingFn: (prev, next) =>
|
||||||
prev.original.downloadSpeed - next.original.downloadSpeed,
|
prev.original.downloadSpeed - next.original.downloadSpeed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'UL Speed',
|
accessorKey: AccessorKey.ULSpeed,
|
||||||
accessorFn: (row) => `${byteSize(row.uploadSpeed)}/s`,
|
accessorFn: (row) => `${byteSize(row.uploadSpeed)}/s`,
|
||||||
sortingFn: (prev, next) =>
|
sortingFn: (prev, next) =>
|
||||||
prev.original.uploadSpeed - next.original.uploadSpeed,
|
prev.original.uploadSpeed - next.original.uploadSpeed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'DL',
|
accessorKey: AccessorKey.Download,
|
||||||
accessorFn: (row) => byteSize(row.download),
|
accessorFn: (row) => byteSize(row.download),
|
||||||
sortingFn: (prev, next) =>
|
sortingFn: (prev, next) =>
|
||||||
prev.original.download - next.original.download,
|
prev.original.download - next.original.download,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'UL',
|
accessorKey: AccessorKey.Upload,
|
||||||
accessorFn: (row) => byteSize(row.upload),
|
accessorFn: (row) => byteSize(row.upload),
|
||||||
sortingFn: (prev, next) => prev.original.upload - next.original.upload,
|
sortingFn: (prev, next) => prev.original.upload - next.original.upload,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Source',
|
accessorKey: AccessorKey.Source,
|
||||||
accessorFn: (row) =>
|
accessorFn: (row) =>
|
||||||
isIPv6(row.metadata.sourceIP)
|
isIPv6(row.metadata.sourceIP)
|
||||||
? `[${row.metadata.sourceIP}]:${row.metadata.sourcePort}`
|
? `[${row.metadata.sourceIP}]:${row.metadata.sourcePort}`
|
||||||
: `${row.metadata.sourceIP}:${row.metadata.sourcePort}`,
|
: `${row.metadata.sourceIP}:${row.metadata.sourcePort}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Destination',
|
accessorKey: AccessorKey.Destination,
|
||||||
accessorFn: (row) =>
|
accessorFn: (row) =>
|
||||||
isIPv6(row.metadata.destinationIP)
|
isIPv6(row.metadata.destinationIP)
|
||||||
? `[${row.metadata.destinationIP}]:${row.metadata.destinationPort}`
|
? `[${row.metadata.destinationIP}]:${row.metadata.destinationPort}`
|
||||||
@ -176,8 +195,8 @@ export default () => {
|
|||||||
get sorting() {
|
get sorting() {
|
||||||
return sorting()
|
return sorting()
|
||||||
},
|
},
|
||||||
columnVisibility: {
|
get columnVisibility() {
|
||||||
ID: false,
|
return columnVisibility()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
get data() {
|
get data() {
|
||||||
@ -200,11 +219,22 @@ export default () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<input
|
<div class="flex w-full">
|
||||||
class="input input-primary"
|
<input
|
||||||
placeholder="Search"
|
class="input input-primary mr-4 w-40 flex-1"
|
||||||
onInput={(e) => setSearch(e.target.value)}
|
placeholder="Search"
|
||||||
/>
|
onInput={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="connection-modal" class="btn">
|
||||||
|
<IconSettings />
|
||||||
|
</label>
|
||||||
|
<ConnectionsModal
|
||||||
|
data={columnVisibility()}
|
||||||
|
onChange={(data: ColumnVisibility) =>
|
||||||
|
setColumnVisibility({ ...data })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto whitespace-nowrap">
|
<div class="overflow-x-auto whitespace-nowrap">
|
||||||
<table class="table table-xs">
|
<table class="table table-xs">
|
||||||
|
@ -84,17 +84,19 @@ export default () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse
|
<div>
|
||||||
isOpen={collapsedMap()[`group-${proxy.name}`]}
|
<Collapse
|
||||||
title={title}
|
isOpen={collapsedMap()[`group-${proxy.name}`]}
|
||||||
content={content}
|
title={title}
|
||||||
onCollapse={(val) =>
|
content={content}
|
||||||
setCollapsedMap({
|
onCollapse={(val) =>
|
||||||
...collapsedMap(),
|
setCollapsedMap({
|
||||||
[`group-${proxy.name}`]: val,
|
...collapsedMap(),
|
||||||
})
|
[`group-${proxy.name}`]: val,
|
||||||
}
|
})
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
@ -143,17 +145,19 @@ export default () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse
|
<div>
|
||||||
isOpen={collapsedMap()[`provider-${proxyProvider.name}`]}
|
<Collapse
|
||||||
title={title}
|
isOpen={collapsedMap()[`provider-${proxyProvider.name}`]}
|
||||||
content={content}
|
title={title}
|
||||||
onCollapse={(val) =>
|
content={content}
|
||||||
setCollapsedMap({
|
onCollapse={(val) =>
|
||||||
...collapsedMap(),
|
setCollapsedMap({
|
||||||
[`provider-${proxyProvider.name}`]: val,
|
...collapsedMap(),
|
||||||
})
|
[`provider-${proxyProvider.name}`]: val,
|
||||||
}
|
})
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
|
Loading…
Reference in New Issue
Block a user