diff --git a/package.json b/package.json index 1896600..5f1611c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb949da..8fbb240 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/components/ConnectionsSettingsModal.tsx b/src/components/ConnectionsSettingsModal.tsx index aecd383..59b210a 100644 --- a/src/components/ConnectionsSettingsModal.tsx +++ b/src/components/ConnectionsSettingsModal.tsx @@ -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> -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>({ + 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 ( +
+
+ + + + + +
+
+ ) +} 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: { +
+ + {t('tagClientSourceIPWithName')} + + +
+ + +
+ + {({ tagName, sourceIP }) => ( +
+ + {tagName} - {sourceIP} + + + +
+ )} +
+
+
+
+
{t('sort')} diff --git a/src/components/ConnectionsTableDetailsModal.tsx b/src/components/ConnectionsTableDetailsModal.tsx index cec7320..c1c9cfe 100644 --- a/src/components/ConnectionsTableDetailsModal.tsx +++ b/src/components/ConnectionsTableDetailsModal.tsx @@ -15,7 +15,7 @@ export const ConnectionsTableDetailsModal: Component<{
             
               {JSON.stringify(
-                allConnections.find(
+                allConnections().find(
                   ({ id }) => id === props.selectedConnectionID,
                 ),
                 null,
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 75fc421..3ccb2b8 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -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',
 }
diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts
index 7ae6d7f..5e04b2d 100644
--- a/src/i18n/zh.ts
+++ b/src/i18n/zh.ts
@@ -89,4 +89,6 @@ export default {
   hideUnAvailableProxies: '隐藏不可用节点',
   reloadConfigFile: '重新加载配置文件',
   flushFakeIPData: '清空 Fake-IP 数据',
+  tagClientSourceIPWithName: '为客户端源 IP 地址添加名称标记',
+  tag: '标记',
 }
diff --git a/src/pages/Connections.tsx b/src/pages/Connections.tsx
index af3c654..1fdc328 100644
--- a/src/pages/Connections.tsx
+++ b/src/pages/Connections.tsx
@@ -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>
-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(CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY),
-    {
-      name: 'columnVisibility',
-      storage: localStorage,
-    },
-  )
-  const [columnOrder, setColumnOrder] = makePersisted(
-    createSignal(CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER),
-    {
-      name: 'columnOrder',
-      storage: localStorage,
-    },
-  )
 
   const [selectedConnectionID, setSelectedConnectionID] = createSignal()
 
@@ -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 () => {
       
setColumnOrder(data)} - onVisibleChange={(data: ColumnVisibility) => - setColumnVisibility({ ...data }) + order={connectionsTableColumnOrder()} + visible={connectionsTableColumnVisibility()} + onOrderChange={(data) => setConnectionsTableColumnOrder(data)} + onVisibleChange={(data) => + setConnectionsTableColumnVisibility({ ...data }) } /> diff --git a/src/signals/config.ts b/src/signals/config.ts index 3d7828e..e0ba811 100644 --- a/src/signals/config.ts +++ b/src/signals/config.ts @@ -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.XS), { name: 'connectionsTableSize', storage: localStorage }, ) +export const [ + connectionsTableColumnVisibility, + setConnectionsTableColumnVisibility, +] = makePersisted( + createSignal( + CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY, + ), + { + name: 'connectionsTableColumnVisibility', + storage: localStorage, + }, +) +export const [connectionsTableColumnOrder, setConnectionsTableColumnOrder] = + makePersisted( + createSignal( + 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.XS), { name: 'logsTableSize', storage: localStorage }, diff --git a/src/signals/connections.ts b/src/signals/connections.ts index c702d7a..324bef2 100644 --- a/src/signals/connections.ts +++ b/src/signals/connections.ts @@ -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( + [], +) export let latestConnectionMsg: Accessor = () => ({ 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 = ( diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 3884fe3..d1554ce 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -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 +> +export type ConnectionsTableColumnOrder = CONNECTIONS_TABLE_ACCESSOR_KEY[]