-
{{ $t('connections') }}: {{ activeConnections.length }}
-
{{ $t('download') }}: {{ prettyBytes(downloadTotal) }} ({{ prettyBytes(downloadSpeedTotal) }}/s)
-
{{ $t('upload') }}: {{ prettyBytes(uploadTotal) }} ({{ prettyBytes(uploadSpeedTotal) }}/s)
-
{{ $t('memoryUsage') }}: {{ prettyBytes(memory) }}
-
{{ $t('version') }}: {{ version }}
-
-
- {{ opt.label }}
-
-
-
-
-
+
+
+
{{ $t('connections') }}: {{ activeConnections.length }}
+
+ {{ $t('download') }}: {{ prettyBytes(downloadTotal) }} ({{
+ prettyBytes(downloadSpeedTotal)
+ }}/s)
-
-
-
-
-
-
-
+
+ {{ $t('upload') }}: {{ prettyBytes(uploadTotal) }} ({{ prettyBytes(uploadSpeedTotal) }}/s)
+
{{ $t('memoryUsage') }}: {{ prettyBytes(memory) }}
+
{{ $t('version') }}: {{ version }}
+
+
+ {{ opt }}
+
+
+
+
+
+ {{ opt.label }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+
diff --git a/src/components/sidebar/ConnectionCtrl.vue b/src/components/sidebar/ConnectionCtrl.vue
index 97314378..63275ef4 100644
--- a/src/components/sidebar/ConnectionCtrl.vue
+++ b/src/components/sidebar/ConnectionCtrl.vue
@@ -1,43 +1,96 @@
-
-
+
\ No newline at end of file
+
diff --git a/src/components/sidebar/LogsCtrl.vue b/src/components/sidebar/LogsCtrl.vue
index 9dd61278..8186b312 100644
--- a/src/components/sidebar/LogsCtrl.vue
+++ b/src/components/sidebar/LogsCtrl.vue
@@ -1,15 +1,41 @@
-
-
-
-
-
+
+
+ {{ $t('logLevel') }}:
+
+
+ {{ opt }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+import { initLogs, isPaused, LOG_LEVEL, logFilter, logLevel } from '@/store/logs'
+import { PauseIcon, PlayIcon } from '@heroicons/vue/24/outline'
+
diff --git a/src/components/sidebar/ProxiesCtrl.vue b/src/components/sidebar/ProxiesCtrl.vue
index 2d672444..87f0490c 100644
--- a/src/components/sidebar/ProxiesCtrl.vue
+++ b/src/components/sidebar/ProxiesCtrl.vue
@@ -1,21 +1,37 @@
-
-
{{ $t('flushFakeIP') }}
-
+
+
+ {{ $t('flushFakeIP') }}
+
+
Direct
Rule
Global
-
+
\ No newline at end of file
+
diff --git a/src/helper/index.ts b/src/helper/index.ts
index a9cd4da3..3753f6ee 100644
--- a/src/helper/index.ts
+++ b/src/helper/index.ts
@@ -1,5 +1,5 @@
-import { useWindowSize } from "@vueuse/core"
-import { computed } from "vue"
+import { useWindowSize } from '@vueuse/core'
+import { computed } from 'vue'
const windowSize = useWindowSize()
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 81359b15..bf60efa8 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -25,4 +25,7 @@ export default {
sortBy: 'Sort By',
rule: 'Rule',
sourceIP: 'Source IP',
+ activeConnections: 'Active Connections',
+ closedConnections: 'Closed Connections',
+ logLevel: 'Log Level',
}
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index 91851852..c776b46a 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -1,12 +1,12 @@
+import { LANG, language } from '@/store/config'
import { createI18n } from 'vue-i18n'
import en from './en'
import zh from './zh'
-import { LANG, language } from '@/store/config'
export const i18n = createI18n({
locale: language.value,
messages: {
[LANG.EN_US]: en,
- [LANG.ZH_CN]: zh
- }
-})
\ No newline at end of file
+ [LANG.ZH_CN]: zh,
+ },
+})
diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts
index 585210f9..7834b17e 100644
--- a/src/i18n/zh.ts
+++ b/src/i18n/zh.ts
@@ -25,4 +25,7 @@ export default {
sortBy: '排序方式',
rule: '规则',
sourceIP: '源IP',
+ activeConnections: '活跃连接',
+ closedConnections: '已关闭连接',
+ logLevel: '日志等级',
}
diff --git a/src/main.ts b/src/main.ts
index 127864e4..ef6830f5 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,11 +1,11 @@
-import './assets/main.css'
+import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
+import relativeTime from 'dayjs/plugin/relativeTime'
import { createApp } from 'vue'
import App from './App.vue'
-import router from './router'
-import dayjs from 'dayjs'
-import relativeTime from 'dayjs/plugin/relativeTime'
+import './assets/main.css'
import { i18n } from './i18n'
+import router from './router'
const app = createApp(App)
dayjs.extend(relativeTime)
diff --git a/src/router/index.ts b/src/router/index.ts
index 1d4a8e28..d4cbeb42 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -35,9 +35,9 @@ const router = createRouter({
component: RulesPage,
},
{
- path: "/:catchAll(.*)",
+ path: '/:catchAll(.*)',
redirect: ROUTE_NAME.proxies,
- }
+ },
],
})
diff --git a/src/store/config.ts b/src/store/config.ts
index 3ce2889b..23bc0f44 100644
--- a/src/store/config.ts
+++ b/src/store/config.ts
@@ -1,15 +1,18 @@
-import { getConfigsAPI, patchConfigsAPI } from "@/api";
-import { useStorage } from "@vueuse/core";
-import { ref } from "vue";
+import { getConfigsAPI, patchConfigsAPI } from '@/api'
+import { useStorage } from '@vueuse/core'
+import { ref } from 'vue'
export enum LANG {
EN_US = 'en-US',
- ZH_CN = 'zh-CN'
+ ZH_CN = 'zh-CN',
}
+export const theme = useStorage('config/theme', 'default')
export const language = useStorage('config/language', LANG.EN_US)
-export const isDark = useStorage('config/dark', false)
-export const speedtestUrl = useStorage('config/speedtest-url', 'http://www.gstatic.com/generate_204')
+export const speedtestUrl = useStorage(
+ 'config/speedtest-url',
+ 'http://www.gstatic.com/generate_204',
+)
export const speedtestTimeout = useStorage('config/speedtest-timeout', 5000)
export const compactConnectionCard = useStorage('config/compact-connection-card', false)
export const hostLabelMap = useStorage>('config/host-label-map', {})
diff --git a/src/store/connections.ts b/src/store/connections.ts
index 9b9c2d8c..9246fab8 100644
--- a/src/store/connections.ts
+++ b/src/store/connections.ts
@@ -1,17 +1,21 @@
-import { fetchConnectionsAPI } from "@/api";
-import type { Connection, ConnectionRawMessage } from "@/types";
-import { useStorage } from "@vueuse/core";
-import { computed, ref, watch } from "vue";
+import { fetchConnectionsAPI } from '@/api'
+import type { Connection, ConnectionRawMessage } from '@/types'
+import { useStorage } from '@vueuse/core'
+import { differenceWith } from 'lodash'
+import { computed, ref, watch } from 'vue'
export const activeConnections = ref([])
+export const closedConnections = ref([])
+
export const downloadTotal = ref(0)
export const uploadTotal = ref(0)
export const memory = ref(0)
export const quickFilterRegex = useStorage('config/quick-filter-regex', 'dns|direct')
export const quickFilterEnabled = useStorage('config/quick-filter-enabled', false)
+export const showActiveConnections = ref(true)
-export enum sortType {
+export enum SORT_TYPE {
HOST = 'host',
RULE = 'rule',
CHAINS = 'chains',
@@ -22,61 +26,74 @@ export enum sortType {
SOURCE_IP = 'sourceIP',
}
-const sortFunctionMap: Record number> = {
- [sortType.HOST]: (a: Connection, b: Connection) => {
- return (a.metadata.host || a.metadata.destinationIP).localeCompare((b.metadata.host || b.metadata.destinationIP))
+const sortFunctionMap: Record number> = {
+ [SORT_TYPE.HOST]: (a: Connection, b: Connection) => {
+ return (a.metadata.host || a.metadata.destinationIP).localeCompare(
+ b.metadata.host || b.metadata.destinationIP,
+ )
},
- [sortType.RULE]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.RULE]: (a: Connection, b: Connection) => {
return a.rule.localeCompare(b.rule)
},
- [sortType.CHAINS]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.CHAINS]: (a: Connection, b: Connection) => {
return a.chains.join('').localeCompare(b.chains.join(''))
},
- [sortType.DOWNLOAD]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.DOWNLOAD]: (a: Connection, b: Connection) => {
return b.download - a.download
},
- [sortType.DOWNLOAD_SPEED]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.DOWNLOAD_SPEED]: (a: Connection, b: Connection) => {
return b.downloadSpeed - a.downloadSpeed
},
- [sortType.UPLOAD]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.UPLOAD]: (a: Connection, b: Connection) => {
return b.upload - a.upload
},
- [sortType.UPLOAD_SPEED]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.UPLOAD_SPEED]: (a: Connection, b: Connection) => {
return b.uploadSpeed - a.uploadSpeed
},
- [sortType.SOURCE_IP]: (a: Connection, b: Connection) => {
+ [SORT_TYPE.SOURCE_IP]: (a: Connection, b: Connection) => {
return a.metadata.sourceIP.localeCompare(b.metadata.sourceIP)
},
}
-export const connectionSortType = useStorage('config/connection-sort-type', sortType.HOST)
+export const connectionSortType = useStorage(
+ 'config/connection-sort-type',
+ SORT_TYPE.HOST,
+)
export const connectionFilter = ref('')
export const isPaused = ref(false)
export const renderConnections = computed(() => {
- return activeConnections.value.filter((conn) => {
- if (quickFilterEnabled.value && quickFilterRegex.value) {
- const regex = new RegExp(quickFilterRegex.value)
-
- return !regex.test(conn.chains.join(''))
- }
+ return (showActiveConnections.value ? activeConnections.value : closedConnections.value)
+ .filter((conn) => {
+ if (quickFilterEnabled.value && quickFilterRegex.value) {
+ const regex = new RegExp(quickFilterRegex.value)
+
+ return !(
+ regex.test(conn.chains.join('')) ||
+ regex.test(conn.metadata.host) ||
+ regex.test(conn.metadata.destinationIP)
+ )
+ }
- return true
- }).filter(conn => {
- if (connectionFilter.value) {
- return [
- conn.metadata.host,
- conn.metadata.destinationIP,
- conn.metadata.destinationPort,
- conn.chains,
- conn.rule,
- ].some(i => i?.includes(connectionFilter.value))
- }
+ return true
+ })
+ .filter((conn) => {
+ if (connectionFilter.value) {
+ return [
+ conn.metadata.host,
+ conn.metadata.destinationIP,
+ conn.metadata.destinationPort,
+ conn.chains,
+ conn.rule,
+ ].some((i) => i?.includes(connectionFilter.value))
+ }
- return true
- }).sort((a, b) => {
- return a.id.localeCompare(b.id)
- }).sort(sortFunctionMap[connectionSortType.value])
+ return true
+ })
+ .sort((a, b) => {
+ return a.id.localeCompare(b.id)
+ })
+ .sort(sortFunctionMap[connectionSortType.value])
})
let cancel: () => void
@@ -84,7 +101,7 @@ let cancel: () => void
export const initConnections = () => {
cancel?.()
activeConnections.value = []
-
+
const ws = fetchConnectionsAPI()
const unwatch = watch(ws.data, (data) => {
if (!data) return
@@ -103,19 +120,28 @@ export const initConnections = () => {
if (isPaused.value) {
return
}
- activeConnections.value = parsedData.connections?.map((connection) => {
- const preConnection = activeConnections.value.find((c) => c.id === connection.id) ?? { download: 0, upload: 0 }
- return {
- ...connection,
- downloadSpeed: connection.download - preConnection.download,
- uploadSpeed: connection.upload - preConnection.upload
- }
- }) ?? []
+ closedConnections.value = [
+ ...closedConnections.value,
+ ...differenceWith(activeConnections.value, parsedData.connections, (a, b) => a.id === b.id),
+ ]
+ activeConnections.value =
+ parsedData.connections?.map((connection) => {
+ const preConnection = activeConnections.value.find((c) => c.id === connection.id) ?? {
+ download: 0,
+ upload: 0,
+ }
+
+ return {
+ ...connection,
+ downloadSpeed: connection.download - preConnection.download,
+ uploadSpeed: connection.upload - preConnection.upload,
+ }
+ }) ?? []
})
cancel = () => {
unwatch()
ws.close()
}
-}
\ No newline at end of file
+}
diff --git a/src/store/logs.ts b/src/store/logs.ts
index 1cdb670a..0f74bb48 100644
--- a/src/store/logs.ts
+++ b/src/store/logs.ts
@@ -1,10 +1,20 @@
-import { fetchLogsAPI } from "@/api";
-import type { Log, LogWithSeq } from "@/types";
-import { ref, watch } from "vue";
+import { fetchLogsAPI } from '@/api'
+import type { Log, LogWithSeq } from '@/types'
+import { useStorage } from '@vueuse/core'
+import { ref, watch } from 'vue'
+
+export enum LOG_LEVEL {
+ Info = 'info',
+ Error = 'error',
+ Warning = 'warning',
+ Debug = 'debug',
+ Silent = 'silent',
+}
export const logs = ref([])
export const logFilter = ref('')
export const isPaused = ref(false)
+export const logLevel = useStorage('config/log-level', LOG_LEVEL.Info)
let cancel: () => void
@@ -13,7 +23,9 @@ export const initLogs = () => {
logs.value = []
let idx = 1
- const ws = fetchLogsAPI()
+ const ws = fetchLogsAPI({
+ level: logLevel.value,
+ })
const unwatch = watch(ws.data, (data) => {
if (!data) return
@@ -26,7 +38,7 @@ export const initLogs = () => {
logs.value.unshift({
...parsedData,
- seq: idx++
+ seq: idx++,
})
logs.value = logs.value.slice(0, 1000)
@@ -36,4 +48,4 @@ export const initLogs = () => {
unwatch()
ws.close()
}
-}
\ No newline at end of file
+}
diff --git a/src/store/proxies.ts b/src/store/proxies.ts
index 34b5384c..ac65b342 100644
--- a/src/store/proxies.ts
+++ b/src/store/proxies.ts
@@ -1,9 +1,15 @@
-import { disconnectByIdAPI, fetchProxiesAPI, fetchProxyGroupLatencyAPI, fetchProxyLatencyAPI, selectProxyAPI } from "@/api";
-import type { Proxy, ProxyGroup } from "@/types";
-import { ref } from "vue";
+import {
+ disconnectByIdAPI,
+ fetchProxiesAPI,
+ fetchProxyGroupLatencyAPI,
+ fetchProxyLatencyAPI,
+ selectProxyAPI,
+} from '@/api'
+import type { Proxy, ProxyGroup } from '@/types'
import { last } from 'lodash'
-import { activeConnections } from "./connections";
-import { speedtestTimeout, speedtestUrl } from "./config";
+import { ref } from 'vue'
+import { speedtestTimeout, speedtestUrl } from './config'
+import { activeConnections } from './connections'
export const proxyGroups = ref([])
export const proxyMap = ref>({})
@@ -16,14 +22,18 @@ export const getLatencyByName = (proxyName: string) => {
export const fetchProxies = async () => {
const { data } = await fetchProxiesAPI()
const sortIndex = data.proxies['GLOBAL'].all ?? []
- const proxies = Object.values(data.proxies).filter((proxy) => proxy.all?.length && proxy.name !== 'GLOBAL') as ProxyGroup[]
+ const proxies = Object.values(data.proxies).filter(
+ (proxy) => proxy.all?.length && proxy.name !== 'GLOBAL',
+ ) as ProxyGroup[]
proxyMap.value = data.proxies
- proxyGroups.value = proxies.sort((prev, next) =>
- sortIndex.indexOf(prev.name) - sortIndex.indexOf(next.name))
+ proxyGroups.value = proxies.sort(
+ (prev, next) => sortIndex.indexOf(prev.name) - sortIndex.indexOf(next.name),
+ )
latencyMap.value = Object.fromEntries(
- Object.entries(data.proxies).map(([name, proxy]) => [name, getLatencyFromHistory(proxy)]))
+ Object.entries(data.proxies).map(([name, proxy]) => [name, getLatencyFromHistory(proxy)]),
+ )
}
export const selectProxy = async (proxyGroup: string, name: string) => {
@@ -35,13 +45,21 @@ export const selectProxy = async (proxyGroup: string, name: string) => {
}
export const proxyLatencyTest = async (proxyName: string) => {
- const { data: latencyResult } = await fetchProxyLatencyAPI(proxyName, speedtestUrl.value, speedtestTimeout.value)
+ const { data: latencyResult } = await fetchProxyLatencyAPI(
+ proxyName,
+ speedtestUrl.value,
+ speedtestTimeout.value,
+ )
latencyMap.value[getNowProxyNodeName(proxyName)] = latencyResult.delay
}
export const proxyGroupLatencyTest = async (proxyGroupName: string) => {
- const { data: latencyResult } = await fetchProxyGroupLatencyAPI(proxyGroupName, speedtestUrl.value, speedtestTimeout.value)
+ const { data: latencyResult } = await fetchProxyGroupLatencyAPI(
+ proxyGroupName,
+ speedtestUrl.value,
+ speedtestTimeout.value,
+ )
Object.entries(latencyResult).forEach(([name, latency]) => {
latencyMap.value[getNowProxyNodeName(name)] = latency
diff --git a/src/store/rules.ts b/src/store/rules.ts
index 95204c5b..f94c139b 100644
--- a/src/store/rules.ts
+++ b/src/store/rules.ts
@@ -1,6 +1,6 @@
-import { getRulesAPI } from "@/api";
-import type { Rule } from "@/types";
-import { ref } from "vue";
+import { getRulesAPI } from '@/api'
+import type { Rule } from '@/types'
+import { ref } from 'vue'
export const rules = ref()
export const fetchRules = async () => {
diff --git a/src/store/setup.ts b/src/store/setup.ts
index 10e5f34b..4fba7510 100644
--- a/src/store/setup.ts
+++ b/src/store/setup.ts
@@ -1,19 +1,21 @@
import { useStorage } from '@vueuse/core'
-import { computed } from 'vue'
-import { v4 as uuid } from 'uuid'
import { isEqual, omit } from 'lodash'
+import { v4 as uuid } from 'uuid'
+import { computed } from 'vue'
type Backend = {
- host: string,
- port: number,
- password: string,
- protocol: string,
+ host: string
+ port: number
+ password: string
+ protocol: string
uuid: string
}
export const backendList = useStorage('setup/api-list', [])
export const activeUuid = useStorage('setup/active-uuid', '')
-export const activeBackend = computed(() => backendList.value.find((backend) => backend.uuid === activeUuid.value))
+export const activeBackend = computed(() =>
+ backendList.value.find((backend) => backend.uuid === activeUuid.value),
+)
export const addBackend = (backend: Omit) => {
const currentEnd = backendList.value.find((end) => {
@@ -29,7 +31,7 @@ export const addBackend = (backend: Omit) => {
backendList.value.push({
...backend,
- uuid: id
+ uuid: id,
})
activeUuid.value = id
-}
\ No newline at end of file
+}
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 2702c3d1..9f9ebdcd 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -1,8 +1,6 @@
-declare module '*.vue' {
-}
+declare module '*.vue' {}
-declare module '*.tsx' {
-}
+declare module '*.tsx' {}
export type Proxy = {
name: string
@@ -53,7 +51,7 @@ export type Connection = ConnectionRawMessage & {
}
export type Log = {
- type: LOG_LEVEL
+ type: 'info' | 'warning' | 'error' | 'debug'
payload: string
}
diff --git a/src/views/ConnectionsPage.vue b/src/views/ConnectionsPage.vue
index a819af5b..1f3b1d61 100644
--- a/src/views/ConnectionsPage.vue
+++ b/src/views/ConnectionsPage.vue
@@ -1,18 +1,29 @@
-
+
-
+
{{ $t('noContent') }}
-
-
+
+
-
+
-
@@ -20,12 +31,12 @@
\ No newline at end of file
+
diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue
index ae1f1563..a8097cb8 100644
--- a/src/views/HomePage.vue
+++ b/src/views/HomePage.vue
@@ -1,57 +1,93 @@
-
-
+
+
-
-
-
\ No newline at end of file
+watch(
+ activeUuid,
+ () => {
+ fetchConfigs()
+ fetchProxies()
+ fetchRules()
+ initConnections()
+ initLogs()
+ },
+ {
+ immediate: true,
+ },
+)
+
diff --git a/src/views/LogsPage.vue b/src/views/LogsPage.vue
index bb136284..a4098d7d 100644
--- a/src/views/LogsPage.vue
+++ b/src/views/LogsPage.vue
@@ -1,25 +1,32 @@
-
-
-
+
+
+
{{ $t('noContent') }}
-
{{ log.seq }}
- {{ log.type }}
+ {{ log.type }}
{{ log.payload }}
diff --git a/src/views/RulesPage.vue b/src/views/RulesPage.vue
index a8a7e1f1..0ba422fe 100644
--- a/src/views/RulesPage.vue
+++ b/src/views/RulesPage.vue
@@ -1,9 +1,10 @@
-
-
+
+
{{ rule.proxy }}
{{ rule.payload }}
@@ -11,6 +12,5 @@
diff --git a/src/views/SetupPage.vue b/src/views/SetupPage.vue
index ba88430a..45de12a8 100644
--- a/src/views/SetupPage.vue
+++ b/src/views/SetupPage.vue
@@ -1,12 +1,15 @@
-