feat(connections): tag client source ip with name, closes #181

This commit is contained in:
kunish 2023-09-16 23:31:40 +08:00
parent b2aa3585c5
commit e538f9d057
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
10 changed files with 201 additions and 53 deletions

View File

@ -20,6 +20,7 @@
"@solid-primitives/clipboard": "^1.5.7",
"@solid-primitives/event-listener": "^2.3.0",
"@solid-primitives/i18n": "^1.4.1",
"@solid-primitives/keyed": "^1.2.0",
"@solid-primitives/media": "^2.2.5",
"@solid-primitives/resize-observer": "^2.0.22",
"@solid-primitives/storage": "^2.1.1",

19
pnpm-lock.yaml generated
View File

@ -26,6 +26,9 @@ dependencies:
'@solid-primitives/i18n':
specifier: ^1.4.1
version: 1.4.1(solid-js@1.7.11)
'@solid-primitives/keyed':
specifier: ^1.2.0
version: 1.2.0(solid-js@1.7.11)
'@solid-primitives/media':
specifier: ^2.2.5
version: 2.2.5(solid-js@1.7.11)
@ -2044,6 +2047,14 @@ packages:
solid-js: 1.7.11
dev: false
/@solid-primitives/keyed@1.2.0(solid-js@1.7.11):
resolution: {integrity: sha512-0DuTeJdxWjCTu73XnDZs24JzfXckBnpvCfQ6Mf/kTPKkMZJh7tjkBnZEk48ckrE9xmwat9stIdfrBmZctsepIw==}
peerDependencies:
solid-js: ^1.6.12
dependencies:
solid-js: 1.7.11
dev: false
/@solid-primitives/media@2.2.5(solid-js@1.7.11):
resolution: {integrity: sha512-wTESNFteSwOZsNIBPLMIVLuOHIIzt2AIZdaCYYxfsJIr/xjDqSomlmdFlAmxfJD3ondO7fwtWfc0rcmAvjoPCA==}
peerDependencies:
@ -2259,10 +2270,6 @@ packages:
resolution: {integrity: sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==}
dev: false
/@types/node@20.6.0:
resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==}
dev: false
/@types/node@20.6.1:
resolution: {integrity: sha512-4LcJvuXQlv4lTHnxwyHQZ3uR9Zw2j7m1C9DfuwoTFQQP4Pmu04O6IfLYgMmHoOCt0nosItLLZAH+sOrRE0Bo8g==}
dev: false
@ -2274,7 +2281,7 @@ packages:
/@types/resolve@1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
'@types/node': 20.6.0
'@types/node': 20.6.1
dev: false
/@types/semver@7.5.1:
@ -4140,7 +4147,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 20.6.0
'@types/node': 20.6.1
merge-stream: 2.0.0
supports-color: 7.2.0
dev: false

View File

@ -1,4 +1,7 @@
import { createForm } from '@felte/solid'
import { validator } from '@felte/validator-zod'
import { useI18n } from '@solid-primitives/i18n'
import { IconX } from '@tabler/icons-solidjs'
import type {
DragEventHandler,
Draggable,
@ -13,7 +16,9 @@ import {
createSortable,
useDragDropContext,
} from '@thisbeyond/solid-dnd'
import { Component, For, Show, createSignal } from 'solid-js'
import { uniq } from 'lodash'
import { Component, For, Index, Show, createSignal } from 'solid-js'
import { z } from 'zod'
import { Button, ConfigTitle } from '~/components'
import {
CONNECTIONS_TABLE_ACCESSOR_KEY,
@ -22,16 +27,87 @@ import {
MODAL,
TAILWINDCSS_SIZE,
} from '~/constants'
import { connectionsTableSize, setConnectionsTableSize } from '~/signals'
import {
allConnections,
clientSourceIPTags,
connectionsTableSize,
setClientSourceIPTags,
setConnectionsTableSize,
} from '~/signals'
import {
ConnectionsTableColumnOrder,
ConnectionsTableColumnVisibility,
} from '~/types'
type ColumnVisibility = Partial<Record<CONNECTIONS_TABLE_ACCESSOR_KEY, boolean>>
type ColumnOrder = CONNECTIONS_TABLE_ACCESSOR_KEY[]
const TagClientSourceIPWithNameForm: Component = () => {
const schema = z.object({
tagName: z.string().nonempty(),
sourceIP: z.string().nonempty(),
})
const [t] = useI18n()
const { form } = createForm<z.infer<typeof schema>>({
extend: validator({ schema }),
onSubmit: ({ tagName, sourceIP }) =>
setClientSourceIPTags((tags) => {
if (
tags.some(
(tag) => tag.tagName === tagName || tag.sourceIP === sourceIP,
)
) {
return tags
}
return [...tags, { tagName, sourceIP }]
}),
})
return (
<form use:form={form}>
<div class="join flex">
<select name="sourceIP" class="select join-item select-bordered">
<option />
<Index
each={uniq(
allConnections().map(({ metadata: { sourceIP } }) => sourceIP),
)
.sort()
.filter(
(sourceIP) =>
!clientSourceIPTags().some(
({ sourceIP: tagSourceIP }) => tagSourceIP === sourceIP,
),
)}
>
{(sourceIP) => (
<option class="badge" value={sourceIP()}>
{sourceIP()}
</option>
)}
</Index>
</select>
<input
name="tagName"
class="input join-item input-bordered min-w-0 flex-1"
placeholder="name"
/>
<Button type="submit" class="join-item">
{t('tag')}
</Button>
</div>
</form>
)
}
export const ConnectionsSettingsModal = (props: {
order: ColumnOrder
visible: ColumnVisibility
onOrderChange: (value: ColumnOrder) => void
onVisibleChange: (value: ColumnVisibility) => void
order: ConnectionsTableColumnOrder
visible: ConnectionsTableColumnVisibility
onOrderChange: (value: ConnectionsTableColumnOrder) => void
onVisibleChange: (value: ConnectionsTableColumnVisibility) => void
}) => {
const [t] = useI18n()
const [activeKey, setActiveKey] =
@ -123,6 +199,39 @@ export const ConnectionsSettingsModal = (props: {
</select>
</div>
<div>
<ConfigTitle withDivider>
{t('tagClientSourceIPWithName')}
</ConfigTitle>
<div class="flex flex-col gap-4">
<TagClientSourceIPWithNameForm />
<div class="flex flex-wrap gap-2">
<For each={clientSourceIPTags()}>
{({ tagName, sourceIP }) => (
<div class="badge badge-primary badge-lg items-center gap-2">
<span>
{tagName} - {sourceIP}
</span>
<Button
class="btn-circle btn-ghost btn-xs"
onClick={() =>
setClientSourceIPTags((tags) =>
tags.filter((tag) => tag.tagName !== tagName),
)
}
>
<IconX size={12} />
</Button>
</div>
)}
</For>
</div>
</div>
</div>
<div>
<ConfigTitle withDivider>{t('sort')}</ConfigTitle>

View File

@ -15,7 +15,7 @@ export const ConnectionsTableDetailsModal: Component<{
<pre>
<code>
{JSON.stringify(
allConnections.find(
allConnections().find(
({ id }) => id === props.selectedConnectionID,
),
null,

View File

@ -89,4 +89,6 @@ export default {
hideUnAvailableProxies: 'Hide UnAvailable Proxies',
reloadConfigFile: 'Reload Config File',
flushFakeIPData: 'Flush Fake-IP Data',
tagClientSourceIPWithName: 'Tag Client Source IP With Name',
tag: 'Tag',
}

View File

@ -89,4 +89,6 @@ export default {
hideUnAvailableProxies: '隐藏不可用节点',
reloadConfigFile: '重新加载配置文件',
flushFakeIPData: '清空 Fake-IP 数据',
tagClientSourceIPWithName: '为客户端源 IP 地址添加名称标记',
tag: '标记',
}

View File

@ -36,23 +36,20 @@ import {
ConnectionsSettingsModal,
ConnectionsTableDetailsModal,
} from '~/components'
import {
CONNECTIONS_TABLE_ACCESSOR_KEY,
CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER,
CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY,
MODAL,
} from '~/constants'
import { CONNECTIONS_TABLE_ACCESSOR_KEY, MODAL } from '~/constants'
import { formatTimeFromNow } from '~/helpers'
import {
clientSourceIPTags,
connectionsTableColumnOrder,
connectionsTableColumnVisibility,
connectionsTableSize,
setConnectionsTableColumnOrder,
setConnectionsTableColumnVisibility,
tableSizeClassName,
useConnections,
} from '~/signals'
import type { Connection } from '~/types'
type ColumnVisibility = Partial<Record<CONNECTIONS_TABLE_ACCESSOR_KEY, boolean>>
type ColumnOrder = CONNECTIONS_TABLE_ACCESSOR_KEY[]
enum ActiveTab {
activeConnections,
closedConnections,
@ -79,20 +76,6 @@ export default () => {
useConnections()
const [globalFilter, setGlobalFilter] = createSignal('')
const [columnVisibility, setColumnVisibility] = makePersisted(
createSignal<ColumnVisibility>(CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY),
{
name: 'columnVisibility',
storage: localStorage,
},
)
const [columnOrder, setColumnOrder] = makePersisted(
createSignal<ColumnOrder>(CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER),
{
name: 'columnOrder',
storage: localStorage,
},
)
const [selectedConnectionID, setSelectedConnectionID] = createSignal<string>()
@ -227,7 +210,13 @@ export default () => {
{
header: () => t('sourceIP'),
accessorKey: CONNECTIONS_TABLE_ACCESSOR_KEY.SourceIP,
accessorFn: (original) => original.metadata.sourceIP,
accessorFn: (original) => {
const tag = clientSourceIPTags().find(
(tag) => tag.sourceIP === original.metadata.sourceIP,
)
return tag ? tag.tagName : original.metadata.sourceIP
},
},
{
header: () => t('sourcePort'),
@ -258,7 +247,7 @@ export default () => {
},
state: {
get columnOrder() {
return columnOrder()
return connectionsTableColumnOrder()
},
get grouping() {
return grouping()
@ -267,7 +256,7 @@ export default () => {
return sorting()
},
get columnVisibility() {
return columnVisibility()
return connectionsTableColumnVisibility()
},
get globalFilter() {
return globalFilter()
@ -494,11 +483,11 @@ export default () => {
</div>
<ConnectionsSettingsModal
order={columnOrder()}
visible={columnVisibility()}
onOrderChange={(data: ColumnOrder) => setColumnOrder(data)}
onVisibleChange={(data: ColumnVisibility) =>
setColumnVisibility({ ...data })
order={connectionsTableColumnOrder()}
visible={connectionsTableColumnVisibility()}
onOrderChange={(data) => setConnectionsTableColumnOrder(data)}
onVisibleChange={(data) =>
setConnectionsTableColumnVisibility({ ...data })
}
/>

View File

@ -2,6 +2,8 @@ import { usePrefersDark } from '@solid-primitives/media'
import { makePersisted } from '@solid-primitives/storage'
import { createEffect, createSignal } from 'solid-js'
import {
CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER,
CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY,
LATENCY_QUALITY_MAP_HTTP,
LATENCY_QUALITY_MAP_HTTPS,
LOG_LEVEL,
@ -10,6 +12,10 @@ import {
TAILWINDCSS_SIZE,
} from '~/constants'
import { setCurTheme } from '~/signals'
import {
ConnectionsTableColumnOrder,
ConnectionsTableColumnVisibility,
} from '~/types'
export const [proxiesPreviewType, setProxiesPreviewType] = makePersisted(
createSignal(PROXIES_PREVIEW_TYPE.Auto),
@ -54,6 +60,35 @@ export const [connectionsTableSize, setConnectionsTableSize] = makePersisted(
createSignal<TAILWINDCSS_SIZE>(TAILWINDCSS_SIZE.XS),
{ name: 'connectionsTableSize', storage: localStorage },
)
export const [
connectionsTableColumnVisibility,
setConnectionsTableColumnVisibility,
] = makePersisted(
createSignal<ConnectionsTableColumnVisibility>(
CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY,
),
{
name: 'connectionsTableColumnVisibility',
storage: localStorage,
},
)
export const [connectionsTableColumnOrder, setConnectionsTableColumnOrder] =
makePersisted(
createSignal<ConnectionsTableColumnOrder>(
CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER,
),
{
name: 'connectionsTableColumnOrder',
storage: localStorage,
},
)
export const [clientSourceIPTags, setClientSourceIPTags] = makePersisted(
createSignal<{ tagName: string; sourceIP: string }[]>([]),
{
name: 'clientSourceIPTags',
storage: localStorage,
},
)
export const [logsTableSize, setLogsTableSize] = makePersisted(
createSignal<TAILWINDCSS_SIZE>(TAILWINDCSS_SIZE.XS),
{ name: 'logsTableSize', storage: localStorage },

View File

@ -12,11 +12,9 @@ export type WsMsg = {
// we make connections global, so we can keep track of connections when user in proxy page
// when user selects proxy and close some connections they can back and check connections
// they closed
export let allConnections: Connection[] = []
export const setAllConnections = (allConns: Connection[]) => {
allConnections = allConns
}
export const [allConnections, setAllConnections] = createSignal<Connection[]>(
[],
)
export let latestConnectionMsg: Accessor<WsMsg> = () => ({
uploadTotal: 0,
@ -103,7 +101,7 @@ export const restructRawMsgToConnection = (
}
export const mergeAllConnections = (activeConns: Connection[]) => {
return unionWith(allConnections, activeConns, (a, b) => a.id === b.id)
return unionWith(allConnections(), activeConns, (a, b) => a.id === b.id)
}
const diffClosedConnections = (

View File

@ -1,4 +1,4 @@
import { LOG_LEVEL } from '~/constants'
import { CONNECTIONS_TABLE_ACCESSOR_KEY, LOG_LEVEL } from '~/constants'
declare module 'solid-js' {
namespace JSX {
@ -176,3 +176,8 @@ export type BackendVersion = {
meta: boolean
version: string
}
export type ConnectionsTableColumnVisibility = Partial<
Record<CONNECTIONS_TABLE_ACCESSOR_KEY, boolean>
>
export type ConnectionsTableColumnOrder = CONNECTIONS_TABLE_ACCESSOR_KEY[]