Compare commits

..
13 Commits
Author SHA1 Message Date
fatedierandGitHub c8c1e5116c Merge pull request #5323 from fatedier/dev
Release v0.69.0
2026-05-22 00:55:23 +08:00
fatedierandGitHub 3e19ef9bfd feat(ui): add xtcp tab to frps dashboard (#5322) 2026-05-21 22:18:00 +08:00
fatedierandGitHub e20f974d61 feat(ui): add all proxies tab to frps dashboard (#5321) 2026-05-20 16:20:58 +08:00
fatedierandGitHub a88e0e9a49 refactor: clean up code (#5308) 2026-05-12 11:13:50 +08:00
霜冷长河andGitHub ad07d27914 fix: reset UDP read deadline on write activity to prevent idle timeout 2026-05-11 20:23:39 +08:00
fatedierandGitHub 4ec8de973f Merge pull request #5287 from fatedier/dev
bump version to v0.68.1
2026-04-14 01:28:33 +08:00
fatedierandGitHub 5bfcea3d0c merge dev to master (#5254)
* ci: bump github actions to latest major versions (#5251)

* docker: copy shared web directory for npm workspace builds
2026-03-20 15:54:26 +08:00
fatedierandGitHub 0a1b4ab21f Merge pull request #5249 from fatedier/dev
bump version
2026-03-20 13:56:28 +08:00
fatedierandGitHub 5f575b8442 Merge pull request #5147 from fatedier/dev
bump version
2026-01-31 14:01:40 +08:00
fatedierandGitHub a1348cdf00 bump version (#5112) 2026-01-04 14:54:13 +08:00
fatedierandGitHub 2f5e1f7945 Merge pull request #4999 from fatedier/dev
bump version
2025-09-25 20:23:42 +08:00
fatedierandGitHub 22ae8166d3 Merge pull request #4925 from fatedier/dev
bump version
2025-08-10 23:26:32 +08:00
fatedierandGitHub af6bc6369d Merge pull request #4849 from fatedier/dev
bump version
2025-06-25 11:51:19 +08:00
2 changed files with 116 additions and 35 deletions
+2
View File
@@ -140,6 +140,8 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
_, err = udpConn.Write(buf)
if err != nil {
udpConn.Close()
} else {
_ = udpConn.SetReadDeadline(time.Now().Add(30 * time.Second))
}
if !ok {
+114 -35
View File
@@ -77,8 +77,9 @@
<div v-if="filteredProxies.length > 0" class="proxies-list">
<ProxyCard
v-for="proxy in filteredProxies"
:key="proxy.name"
:key="`${proxy.type}:${proxy.name}`"
:proxy="proxy"
:show-type="activeType === 'all'"
/>
</div>
<div v-else-if="!loading" class="empty-state">
@@ -129,12 +130,14 @@ const route = useRoute()
const router = useRouter()
const proxyTypes = [
{ label: 'All', value: 'all' },
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
{ label: 'HTTP', value: 'http' },
{ label: 'HTTPS', value: 'https' },
{ label: 'TCPMUX', value: 'tcpmux' },
{ label: 'STCP', value: 'stcp' },
{ label: 'XTCP', value: 'xtcp' },
{ label: 'SUDP', value: 'sudp' },
]
@@ -200,15 +203,48 @@ const filteredProxies = computed(() => {
)
}
// Filter by search text
// Filter by search text across multiple fields
if (searchText.value) {
const search = searchText.value.toLowerCase()
result = result.filter((p) => p.name.toLowerCase().includes(search))
result = result.filter((p) => {
const fields: unknown[] = [
p.name,
p.type,
p.clientID,
p.user,
p.addr,
p.port,
p.customDomains,
p.subdomain,
]
return fields.some((v) => matchesSearch(v, search))
})
}
return result
})
// Normalize a field of unknown shape (string / number / array / null) to a
// lowercase string for case-insensitive substring matching. Arrays are joined
// so e.g. customDomains: ["A.com","B.com"] is searchable as one blob.
const matchesSearch = (value: unknown, needle: string): boolean => {
if (value === null || value === undefined) return false
let str: string
if (Array.isArray(value)) {
str = value
.filter((v) => v !== null && v !== undefined)
.map((v) => String(v))
.join(' ')
} else if (typeof value === 'number') {
if (value === 0) return false
str = String(value)
} else {
str = String(value)
}
if (!str) return false
return str.toLowerCase().includes(needle)
}
const onClientFilterChange = (key: string) => {
if (key) {
const client = clientOptions.value.find((c) => c.key === key)
@@ -249,45 +285,88 @@ const fetchServerInfo = async () => {
return serverInfo
}
const convertProxies = async (
type: string,
json: any,
): Promise<BaseProxy[]> => {
if (type === 'tcp') {
return json.proxies.map((p: any) => new TCPProxy(p))
}
if (type === 'udp') {
return json.proxies.map((p: any) => new UDPProxy(p))
}
if (type === 'http') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPPort) {
return json.proxies.map(
(p: any) => new HTTPProxy(p, info.vhostHTTPPort, info.subdomainHost),
)
}
return []
}
if (type === 'https') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPSPort) {
return json.proxies.map(
(p: any) => new HTTPSProxy(p, info.vhostHTTPSPort, info.subdomainHost),
)
}
return []
}
if (type === 'tcpmux') {
const info = await fetchServerInfo()
if (info && info.tcpmuxHTTPConnectPort) {
return json.proxies.map(
(p: any) =>
new TCPMuxProxy(p, info.tcpmuxHTTPConnectPort, info.subdomainHost),
)
}
return []
}
if (type === 'stcp') {
return json.proxies.map((p: any) => new STCPProxy(p))
}
if (type === 'sudp') {
return json.proxies.map((p: any) => new SUDPProxy(p))
}
// Fallback for types without a dedicated class (e.g. xtcp). Matches the
// pattern in ProxyDetail.vue so the type tag and meta render correctly.
return json.proxies.map((p: any) => {
const bp = new BaseProxy(p)
bp.type = type
return bp
})
}
const allProxyTypes = [
'tcp',
'udp',
'http',
'https',
'tcpmux',
'stcp',
'xtcp',
'sudp',
]
const fetchData = async () => {
loading.value = true
proxies.value = []
try {
const type = activeType.value
const json = await getProxiesByType(type)
if (type === 'tcp') {
proxies.value = json.proxies.map((p: any) => new TCPProxy(p))
} else if (type === 'udp') {
proxies.value = json.proxies.map((p: any) => new UDPProxy(p))
} else if (type === 'http') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPPort) {
proxies.value = json.proxies.map(
(p: any) => new HTTPProxy(p, info.vhostHTTPPort, info.subdomainHost),
)
}
} else if (type === 'https') {
const info = await fetchServerInfo()
if (info && info.vhostHTTPSPort) {
proxies.value = json.proxies.map(
(p: any) =>
new HTTPSProxy(p, info.vhostHTTPSPort, info.subdomainHost),
)
}
} else if (type === 'tcpmux') {
const info = await fetchServerInfo()
if (info && info.tcpmuxHTTPConnectPort) {
proxies.value = json.proxies.map(
(p: any) =>
new TCPMuxProxy(p, info.tcpmuxHTTPConnectPort, info.subdomainHost),
)
}
} else if (type === 'stcp') {
proxies.value = json.proxies.map((p: any) => new STCPProxy(p))
} else if (type === 'sudp') {
proxies.value = json.proxies.map((p: any) => new SUDPProxy(p))
if (type === 'all') {
const results = await Promise.all(
allProxyTypes.map(async (t) => {
const json = await getProxiesByType(t)
return convertProxies(t, json)
}),
)
proxies.value = results.flat()
} else {
const json = await getProxiesByType(type)
proxies.value = await convertProxies(type, json)
}
} catch (error: any) {
ElMessage({