feat: add system info API v2 (#5394)

This commit is contained in:
fatedier
2026-07-07 02:00:44 +08:00
committed by GitHub
parent 7fe152e3aa
commit 5876beceac
11 changed files with 344 additions and 125 deletions

View File

@@ -2,5 +2,5 @@ import { http } from './http'
import type { ServerInfo } from '../types/server'
export const getServerInfo = () => {
return http.get<ServerInfo>('../api/serverinfo')
return http.getV2<ServerInfo>('../api/v2/system/info')
}

View File

@@ -1,5 +1,10 @@
export interface ServerInfo {
version: string
config: ServerInfoConfig
status: ServerInfoStatus
}
export interface ServerInfoConfig {
bindPort: number
vhostHTTPPort: number
vhostHTTPSPort: number
@@ -12,8 +17,9 @@ export interface ServerInfo {
heartbeatTimeout: number
allowPortsStr: string
tlsForce: boolean
}
// Stats
export interface ServerInfoStatus {
totalTrafficIn: number
totalTrafficOut: number
curConns: number

View File

@@ -161,6 +161,7 @@ import {
import { getServerInfo } from '../api/server'
import ProxyCard from '../components/ProxyCard.vue'
import type { ProxyStatsInfo } from '../types/proxy'
import type { ServerInfo } from '../types/server'
const route = useRoute()
const router = useRouter()
@@ -183,16 +184,9 @@ const total = ref(0)
let requestSeq = 0
let searchDebounceTimer: number | null = null
type ServerInfoLite = {
vhostHTTPPort: number
vhostHTTPSPort: number
tcpmuxHTTPConnectPort: number
subdomainHost: string
}
let serverInfoPromise: Promise<ServerInfo> | null = null
let serverInfoPromise: Promise<ServerInfoLite> | null = null
const fetchServerInfo = (): Promise<ServerInfoLite> => {
const fetchServerInfo = (): Promise<ServerInfo> => {
if (!serverInfoPromise) {
serverInfoPromise = getServerInfo().catch((err) => {
serverInfoPromise = null
@@ -232,25 +226,33 @@ const convertProxy = async (
}
if (type === 'http') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPPort) {
return new HTTPProxy(proxy, info.vhostHTTPPort, info.subdomainHost)
if (info && info.config.vhostHTTPPort) {
return new HTTPProxy(
proxy,
info.config.vhostHTTPPort,
info.config.subdomainHost,
)
}
return null
}
if (type === 'https') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPSPort) {
return new HTTPSProxy(proxy, info.vhostHTTPSPort, info.subdomainHost)
if (info && info.config.vhostHTTPSPort) {
return new HTTPSProxy(
proxy,
info.config.vhostHTTPSPort,
info.config.subdomainHost,
)
}
return null
}
if (type === 'tcpmux') {
const info = await fetchServerInfo()
if (info && info.tcpmuxHTTPConnectPort) {
if (info && info.config.tcpmuxHTTPConnectPort) {
return new TCPMuxProxy(
proxy,
info.tcpmuxHTTPConnectPort,
info.subdomainHost,
info.config.tcpmuxHTTPConnectPort,
info.config.subdomainHost,
)
}
return null

View File

@@ -104,6 +104,7 @@ import {
} from '../api/proxy'
import { getServerInfo } from '../api/server'
import type { ProxyStatsInfo } from '../types/proxy'
import type { ServerInfo } from '../types/server'
const route = useRoute()
const router = useRouter()
@@ -133,15 +134,9 @@ let searchDebounceTimer: number | null = null
// Server info cache - cache the Promise itself so concurrent first calls
// from Promise.all (convertProxies) don't kick off multiple HTTP requests.
type ServerInfoLite = {
vhostHTTPPort: number
vhostHTTPSPort: number
tcpmuxHTTPConnectPort: number
subdomainHost: string
}
let serverInfoPromise: Promise<ServerInfoLite> | null = null
let serverInfoPromise: Promise<ServerInfo> | null = null
const fetchServerInfo = (): Promise<ServerInfoLite> => {
const fetchServerInfo = (): Promise<ServerInfo> => {
if (!serverInfoPromise) {
serverInfoPromise = getServerInfo().catch((err) => {
// Allow retry after failure
@@ -164,25 +159,33 @@ const convertProxy = async (
}
if (type === 'http') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPPort) {
return new HTTPProxy(proxy, info.vhostHTTPPort, info.subdomainHost)
if (info && info.config.vhostHTTPPort) {
return new HTTPProxy(
proxy,
info.config.vhostHTTPPort,
info.config.subdomainHost,
)
}
return null
}
if (type === 'https') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPSPort) {
return new HTTPSProxy(proxy, info.vhostHTTPSPort, info.subdomainHost)
if (info && info.config.vhostHTTPSPort) {
return new HTTPSProxy(
proxy,
info.config.vhostHTTPSPort,
info.config.subdomainHost,
)
}
return null
}
if (type === 'tcpmux') {
const info = await fetchServerInfo()
if (info && info.tcpmuxHTTPConnectPort) {
if (info && info.config.tcpmuxHTTPConnectPort) {
return new TCPMuxProxy(
proxy,
info.tcpmuxHTTPConnectPort,
info.subdomainHost,
info.config.tcpmuxHTTPConnectPort,
info.config.subdomainHost,
)
}
return null

View File

@@ -254,6 +254,7 @@ import {
SUDPProxy,
} from '../utils/proxy'
import Traffic from '../components/Traffic.vue'
import type { ServerInfo } from '../types/server'
const route = useRoute()
const router = useRouter()
@@ -275,12 +276,7 @@ const goBack = () => {
}
}
let serverInfo: {
vhostHTTPPort: number
vhostHTTPSPort: number
tcpmuxHTTPConnectPort: number
subdomainHost: string
} | null = null
let serverInfo: ServerInfo | null = null
const clientLink = computed(() => {
if (!proxy.value) return ''
@@ -367,25 +363,30 @@ const fetchProxy = async () => {
try {
const data = await getProxyByNameV2(name)
const info = await fetchServerInfo()
const config = info.config
const type = data.type || data.conf?.type || ''
if (type === 'tcp') {
proxy.value = new TCPProxy(data)
} else if (type === 'udp') {
proxy.value = new UDPProxy(data)
} else if (type === 'http' && info?.vhostHTTPPort) {
proxy.value = new HTTPProxy(data, info.vhostHTTPPort, info.subdomainHost)
} else if (type === 'https' && info?.vhostHTTPSPort) {
} else if (type === 'http' && config.vhostHTTPPort) {
proxy.value = new HTTPProxy(
data,
config.vhostHTTPPort,
config.subdomainHost,
)
} else if (type === 'https' && config.vhostHTTPSPort) {
proxy.value = new HTTPSProxy(
data,
info.vhostHTTPSPort,
info.subdomainHost,
config.vhostHTTPSPort,
config.subdomainHost,
)
} else if (type === 'tcpmux' && info?.tcpmuxHTTPConnectPort) {
} else if (type === 'tcpmux' && config.tcpmuxHTTPConnectPort) {
proxy.value = new TCPMuxProxy(
data,
info.tcpmuxHTTPConnectPort,
info.subdomainHost,
config.tcpmuxHTTPConnectPort,
config.subdomainHost,
)
} else if (type === 'stcp') {
proxy.value = new STCPProxy(data)

View File

@@ -4,7 +4,7 @@
<el-col :xs="24" :sm="12" :lg="6">
<StatCard
label="Clients"
:value="data.clientCounts"
:value="data.status.clientCounts"
type="clients"
subtitle="Connected clients"
to="/clients"
@@ -13,7 +13,7 @@
<el-col :xs="24" :sm="12" :lg="6">
<StatCard
label="Proxies"
:value="data.proxyCounts"
:value="proxyCounts"
type="proxies"
subtitle="Active proxies"
to="/proxies/tcp"
@@ -22,7 +22,7 @@
<el-col :xs="24" :sm="12" :lg="6">
<StatCard
label="Connections"
:value="data.curConns"
:value="data.status.curConns"
type="connections"
subtitle="Current connections"
/>
@@ -54,7 +54,7 @@
<div class="traffic-info">
<div class="label">Inbound</div>
<div class="value">
{{ formatFileSize(data.totalTrafficIn) }}
{{ formatFileSize(data.status.totalTrafficIn) }}
</div>
</div>
</div>
@@ -66,7 +66,7 @@
<div class="traffic-info">
<div class="label">Outbound</div>
<div class="value">
{{ formatFileSize(data.totalTrafficOut) }}
{{ formatFileSize(data.status.totalTrafficOut) }}
</div>
</div>
</div>
@@ -83,7 +83,7 @@
</template>
<div class="proxy-types-grid">
<div
v-for="(count, type) in data.proxyTypeCounts"
v-for="(count, type) in data.status.proxyTypeCount"
:key="type"
class="proxy-type-item"
v-show="count > 0"
@@ -109,51 +109,51 @@
<div class="config-grid">
<div class="config-item">
<span class="config-label">Bind Port</span>
<span class="config-value">{{ data.bindPort }}</span>
<span class="config-value">{{ data.config.bindPort }}</span>
</div>
<div class="config-item" v-if="data.kcpBindPort != 0">
<div class="config-item" v-if="data.config.kcpBindPort != 0">
<span class="config-label">KCP Port</span>
<span class="config-value">{{ data.kcpBindPort }}</span>
<span class="config-value">{{ data.config.kcpBindPort }}</span>
</div>
<div class="config-item" v-if="data.quicBindPort != 0">
<div class="config-item" v-if="data.config.quicBindPort != 0">
<span class="config-label">QUIC Port</span>
<span class="config-value">{{ data.quicBindPort }}</span>
<span class="config-value">{{ data.config.quicBindPort }}</span>
</div>
<div class="config-item" v-if="data.vhostHTTPPort != 0">
<div class="config-item" v-if="data.config.vhostHTTPPort != 0">
<span class="config-label">HTTP Port</span>
<span class="config-value">{{ data.vhostHTTPPort }}</span>
<span class="config-value">{{ data.config.vhostHTTPPort }}</span>
</div>
<div class="config-item" v-if="data.vhostHTTPSPort != 0">
<div class="config-item" v-if="data.config.vhostHTTPSPort != 0">
<span class="config-label">HTTPS Port</span>
<span class="config-value">{{ data.vhostHTTPSPort }}</span>
<span class="config-value">{{ data.config.vhostHTTPSPort }}</span>
</div>
<div class="config-item" v-if="data.tcpmuxHTTPConnectPort != 0">
<div class="config-item" v-if="data.config.tcpmuxHTTPConnectPort != 0">
<span class="config-label">TCPMux Port</span>
<span class="config-value">{{ data.tcpmuxHTTPConnectPort }}</span>
<span class="config-value">{{ data.config.tcpmuxHTTPConnectPort }}</span>
</div>
<div class="config-item" v-if="data.subdomainHost != ''">
<div class="config-item" v-if="data.config.subdomainHost != ''">
<span class="config-label">Subdomain Host</span>
<span class="config-value">{{ data.subdomainHost }}</span>
<span class="config-value">{{ data.config.subdomainHost }}</span>
</div>
<div class="config-item">
<span class="config-label">Max Pool Count</span>
<span class="config-value">{{ data.maxPoolCount }}</span>
<span class="config-value">{{ data.config.maxPoolCount }}</span>
</div>
<div class="config-item">
<span class="config-label">Max Ports/Client</span>
<span class="config-value">{{ data.maxPortsPerClient }}</span>
<span class="config-value">{{ maxPortsPerClientLabel }}</span>
</div>
<div class="config-item" v-if="data.allowPortsStr != ''">
<div class="config-item" v-if="data.config.allowPortsStr != ''">
<span class="config-label">Allow Ports</span>
<span class="config-value">{{ data.allowPortsStr }}</span>
<span class="config-value">{{ data.config.allowPortsStr }}</span>
</div>
<div class="config-item" v-if="data.tlsForce">
<div class="config-item" v-if="data.config.tlsForce">
<span class="config-label">TLS Force</span>
<el-tag size="small" type="warning">Enabled</el-tag>
</div>
<div class="config-item">
<span class="config-label">Heartbeat Timeout</span>
<span class="config-value">{{ data.heartbeatTimeout }}s</span>
<span class="config-value">{{ data.config.heartbeatTimeout }}s</span>
</div>
</div>
</el-card>
@@ -167,69 +167,59 @@ import { formatFileSize } from '../utils/format'
import { Download, Upload } from '@element-plus/icons-vue'
import StatCard from '../components/StatCard.vue'
import { getServerInfo } from '../api/server'
import type { ServerInfo } from '../types/server'
const data = ref({
const data = ref<ServerInfo>({
version: '',
bindPort: 0,
kcpBindPort: 0,
quicBindPort: 0,
vhostHTTPPort: 0,
vhostHTTPSPort: 0,
tcpmuxHTTPConnectPort: 0,
subdomainHost: '',
maxPoolCount: 0,
maxPortsPerClient: '',
allowPortsStr: '',
tlsForce: false,
heartbeatTimeout: 0,
clientCounts: 0,
curConns: 0,
proxyCounts: 0,
totalTrafficIn: 0,
totalTrafficOut: 0,
proxyTypeCounts: {} as Record<string, number>,
config: {
bindPort: 0,
kcpBindPort: 0,
quicBindPort: 0,
vhostHTTPPort: 0,
vhostHTTPSPort: 0,
tcpmuxHTTPConnectPort: 0,
subdomainHost: '',
maxPoolCount: 0,
maxPortsPerClient: 0,
allowPortsStr: '',
tlsForce: false,
heartbeatTimeout: 0,
},
status: {
clientCounts: 0,
curConns: 0,
totalTrafficIn: 0,
totalTrafficOut: 0,
proxyTypeCount: {},
},
})
const hasActiveProxies = computed(() => {
return Object.values(data.value.proxyTypeCounts).some((c) => c > 0)
return Object.values(data.value.status.proxyTypeCount).some((c) => c > 0)
})
const proxyCounts = computed(() => {
return Object.values(data.value.status.proxyTypeCount).reduce(
(sum, count) => sum + (count || 0),
0,
)
})
const maxPortsPerClientLabel = computed(() => {
const value = data.value.config.maxPortsPerClient
return value === 0 ? 'no limit' : String(value)
})
const formatTrafficTotal = () => {
const total = data.value.totalTrafficIn + data.value.totalTrafficOut
const total =
data.value.status.totalTrafficIn + data.value.status.totalTrafficOut
return formatFileSize(total)
}
const fetchData = async () => {
try {
const json = await getServerInfo()
data.value.version = json.version
data.value.bindPort = json.bindPort
data.value.kcpBindPort = json.kcpBindPort
data.value.quicBindPort = json.quicBindPort
data.value.vhostHTTPPort = json.vhostHTTPPort
data.value.vhostHTTPSPort = json.vhostHTTPSPort
data.value.tcpmuxHTTPConnectPort = json.tcpmuxHTTPConnectPort
data.value.subdomainHost = json.subdomainHost
data.value.maxPoolCount = json.maxPoolCount
data.value.maxPortsPerClient = String(json.maxPortsPerClient)
if (data.value.maxPortsPerClient == '0') {
data.value.maxPortsPerClient = 'no limit'
}
data.value.allowPortsStr = json.allowPortsStr
data.value.tlsForce = json.tlsForce
data.value.heartbeatTimeout = json.heartbeatTimeout
data.value.clientCounts = json.clientCounts
data.value.curConns = json.curConns
data.value.totalTrafficIn = json.totalTrafficIn
data.value.totalTrafficOut = json.totalTrafficOut
data.value.proxyTypeCounts = json.proxyTypeCount || {}
data.value.proxyCounts = 0
if (json.proxyTypeCount != null) {
Object.values(json.proxyTypeCount).forEach((count: any) => {
data.value.proxyCounts += count || 0
})
}
data.value = json
} catch {
ElMessage({
showClose: true,