mirror of https://github.com/fatedier/frp
web: support to clear offline proxies data on dashboard (#3963)
parent
8023d147b0
commit
b31c67d7c0
|
@ -1,3 +1,7 @@
|
||||||
### Deprecation Notices
|
### Deprecation Notices
|
||||||
|
|
||||||
* Using an underscore in a flag name is deprecated and has been replaced by a hyphen. The underscore format will remain compatible for some time, until it is completely removed in a future version. For example, `--remote_port` is replaced with `--remote-port`.
|
* Using an underscore in a flag name is deprecated and has been replaced by a hyphen. The underscore format will remain compatible for some time, until it is completely removed in a future version. For example, `--remote_port` is replaced with `--remote-port`.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* The `Refresh` and `ClearOfflineProxies` buttons have been added to the Dashboard of frps.
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,13 +4,12 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frp client admin UI</title>
|
<title>frp client admin UI</title>
|
||||||
<script type="module" crossorigin src="./index-1c7ed8b0.js"></script>
|
<script type="module" crossorigin src="./index-bLBhaJo8.js"></script>
|
||||||
<link rel="stylesheet" href="./index-1e2a7ce0.css">
|
<link rel="stylesheet" crossorigin href="./index-iuf46MlF.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,13 +4,12 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frps dashboard</title>
|
<title>frps dashboard</title>
|
||||||
<script type="module" crossorigin src="./index-c322b7dd.js"></script>
|
<script type="module" crossorigin src="./index-1gecbKzv.js"></script>
|
||||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
<link rel="stylesheet" crossorigin href="./index-Lf6B06jY.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -61,23 +61,23 @@ func (m *serverMetrics) run() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(12 * time.Hour)
|
time.Sleep(12 * time.Hour)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
count, total := m.clearUselessInfo()
|
count, total := m.clearUselessInfo(time.Duration(7*24) * time.Hour)
|
||||||
log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
|
log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) clearUselessInfo() (int, int) {
|
func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration time.Duration) (int, int) {
|
||||||
count := 0
|
count := 0
|
||||||
total := 0
|
total := 0
|
||||||
// To check if there are proxies that closed than 7 days and drop them.
|
// To check if there are any proxies that have been closed for more than continuousOfflineDuration and remove them.
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
total = len(m.info.ProxyStatistics)
|
total = len(m.info.ProxyStatistics)
|
||||||
for name, data := range m.info.ProxyStatistics {
|
for name, data := range m.info.ProxyStatistics {
|
||||||
if !data.LastCloseTime.IsZero() &&
|
if !data.LastCloseTime.IsZero() &&
|
||||||
data.LastStartTime.Before(data.LastCloseTime) &&
|
data.LastStartTime.Before(data.LastCloseTime) &&
|
||||||
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
time.Since(data.LastCloseTime) > continuousOfflineDuration {
|
||||||
delete(m.info.ProxyStatistics, name)
|
delete(m.info.ProxyStatistics, name)
|
||||||
count++
|
count++
|
||||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||||
|
@ -86,6 +86,10 @@ func (m *serverMetrics) clearUselessInfo() (int, int) {
|
||||||
return count, total
|
return count, total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) ClearOfflineProxies() (int, int) {
|
||||||
|
return m.clearUselessInfo(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) NewClient() {
|
func (m *serverMetrics) NewClient() {
|
||||||
m.info.ClientCounts.Inc(1)
|
m.info.ClientCounts.Inc(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,4 +79,5 @@ type Collector interface {
|
||||||
GetProxiesByType(proxyType string) []*ProxyStats
|
GetProxiesByType(proxyType string) []*ProxyStats
|
||||||
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
||||||
GetProxyTraffic(name string) *ProxyTrafficInfo
|
GetProxyTraffic(name string) *ProxyTrafficInfo
|
||||||
|
ClearOfflineProxies() (int, int)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
@ -53,6 +54,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper)
|
||||||
subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
|
subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
|
||||||
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
|
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
|
||||||
subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
|
subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE")
|
||||||
|
|
||||||
// view
|
// view
|
||||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||||
|
@ -226,6 +228,9 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
proxyInfoResp := GetProxyInfoResp{}
|
proxyInfoResp := GetProxyInfoResp{}
|
||||||
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||||
|
sort.Slice(proxyInfoResp.Proxies, func(i, j int) bool {
|
||||||
|
return proxyInfoResp.Proxies[i].Name < proxyInfoResp.Proxies[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
buf, _ := json.Marshal(&proxyInfoResp)
|
buf, _ := json.Marshal(&proxyInfoResp)
|
||||||
res.Msg = string(buf)
|
res.Msg = string(buf)
|
||||||
|
@ -376,3 +381,26 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||||
buf, _ := json.Marshal(&trafficResp)
|
buf, _ := json.Marshal(&trafficResp)
|
||||||
res.Msg = string(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DELETE /api/proxies?status=offline
|
||||||
|
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
status := r.URL.Query().Get("status")
|
||||||
|
if status != "offline" {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "status only support offline"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cleared, total := mem.StatsCollector.ClearOfflineProxies()
|
||||||
|
log.Info("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
// Generated by unplugin-auto-import
|
// Generated by unplugin-auto-import
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
import '@vue/runtime-core'
|
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default']
|
ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "-frpc-dashboard",
|
"name": "frpc-dashboard",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -11,25 +11,25 @@
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"element-plus": "^2.3.3",
|
"element-plus": "^2.5.3",
|
||||||
"vue": "^3.2.47",
|
"vue": "^3.4.15",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.7.2",
|
||||||
"@types/node": "^18.11.12",
|
"@types/node": "^18.11.12",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.21.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^3.2.4",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "~5.3.3",
|
||||||
"unplugin-auto-import": "^0.14.3",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
"unplugin-vue-components": "^0.24.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
"vite": "^4.0.0",
|
"vite": "^5.0.12",
|
||||||
"vue-tsc": "^1.0.12"
|
"vue-tsc": "^1.8.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
|
||||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"types": ["node"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,25 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"target": "ES2020",
|
||||||
"paths": {
|
"useDefineForClassFields": true,
|
||||||
"@/*": ["./src/*"]
|
"module": "ESNext",
|
||||||
}
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
},
|
"skipLibCheck": true,
|
||||||
|
|
||||||
"references": [
|
/* Bundler mode */
|
||||||
{
|
"moduleResolution": "bundler",
|
||||||
"path": "./tsconfig.config.json"
|
"allowImportingTsExtensions": true,
|
||||||
}
|
"resolveJsonModule": true,
|
||||||
]
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
2126
web/frpc/yarn.lock
2126
web/frpc/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,8 @@
|
||||||
// Generated by 'unplugin-auto-import'
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// generated by unplugin-vue-components
|
/* eslint-disable */
|
||||||
// We suggest you to commit this file into source control
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
import '@vue/runtime-core'
|
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
|
@ -13,6 +13,8 @@ declare module '@vue/runtime-core' {
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
|
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
||||||
|
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
|
|
|
@ -12,27 +12,27 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/humanize-plus": "^1.8.0",
|
"@types/humanize-plus": "^1.8.0",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.3",
|
||||||
"element-plus": "^2.3.3",
|
"element-plus": "^2.5.3",
|
||||||
"humanize-plus": "^1.8.2",
|
"humanize-plus": "^1.8.2",
|
||||||
"vue": "^3.2.47",
|
"vue": "^3.4.15",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.7.2",
|
||||||
"@types/node": "^18.11.12",
|
"@types/node": "^18.11.12",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.21.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^3.2.4",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "~5.3.3",
|
||||||
"unplugin-auto-import": "^0.13.0",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
"unplugin-vue-components": "^0.23.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
"vite": "^4.0.4",
|
"vite": "^5.0.12",
|
||||||
"vue-tsc": "^1.0.12"
|
"vue-tsc": "^1.8.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ProxyView :proxies="proxies" proxyType="http" />
|
<ProxyView :proxies="proxies" proxyType="http" @refresh="fetchData"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -27,6 +27,7 @@ const fetchData = () => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
proxies.value = []
|
||||||
for (let proxyStats of json.proxies) {
|
for (let proxyStats of json.proxies) {
|
||||||
proxies.value.push(
|
proxies.value.push(
|
||||||
new HTTPProxy(proxyStats, vhostHTTPPort, subdomainHost)
|
new HTTPProxy(proxyStats, vhostHTTPPort, subdomainHost)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ProxyView :proxies="proxies" proxyType="https" />
|
<ProxyView :proxies="proxies" proxyType="https" @refresh="fetchData"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -27,6 +27,7 @@ const fetchData = () => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
proxies.value = []
|
||||||
for (let proxyStats of json.proxies) {
|
for (let proxyStats of json.proxies) {
|
||||||
proxies.value.push(
|
proxies.value.push(
|
||||||
new HTTPSProxy(proxyStats, vhostHTTPSPort, subdomainHost)
|
new HTTPSProxy(proxyStats, vhostHTTPSPort, subdomainHost)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ProxyView :proxies="proxies" proxyType="stcp" />
|
<ProxyView :proxies="proxies" proxyType="stcp" @refresh="fetchData"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -15,6 +15,7 @@ const fetchData = () => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
proxies.value = []
|
||||||
for (let proxyStats of json.proxies) {
|
for (let proxyStats of json.proxies) {
|
||||||
proxies.value.push(new STCPProxy(proxyStats))
|
proxies.value.push(new STCPProxy(proxyStats))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ProxyView :proxies="proxies" proxyType="sudp" />
|
<ProxyView :proxies="proxies" proxyType="sudp" @refresh="fetchData"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -15,6 +15,7 @@ const fetchData = () => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
proxies.value = []
|
||||||
for (let proxyStats of json.proxies) {
|
for (let proxyStats of json.proxies) {
|
||||||
proxies.value.push(new SUDPProxy(proxyStats))
|
proxies.value.push(new SUDPProxy(proxyStats))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ProxyView :proxies="proxies" proxyType="tcp" />
|
<ProxyView :proxies="proxies" proxyType="tcp" @refresh="fetchData" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -15,6 +15,7 @@ const fetchData = () => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
proxies.value = []
|
||||||
for (let proxyStats of json.proxies) {
|
for (let proxyStats of json.proxies) {
|
||||||
proxies.value.push(new TCPProxy(proxyStats))
|
proxies.value.push(new TCPProxy(proxyStats))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ProxyView :proxies="proxies" proxyType="udp" />
|
<ProxyView :proxies="proxies" proxyType="udp" @refresh="fetchData"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -15,12 +15,12 @@ const fetchData = () => {
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
proxies.value = []
|
||||||
for (let proxyStats of json.proxies) {
|
for (let proxyStats of json.proxies) {
|
||||||
proxies.value.push(new UDPProxy(proxyStats))
|
proxies.value.push(new UDPProxy(proxyStats))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData()
|
fetchData()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<el-page-header
|
||||||
|
:icon="null"
|
||||||
|
style="width: 100%; margin-left: 30px; margin-bottom: 20px"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ proxyType }}</span>
|
||||||
|
</template>
|
||||||
|
<template #content> </template>
|
||||||
|
<template #extra>
|
||||||
|
<div class="flex items-center" style="margin-right: 30px">
|
||||||
|
<el-popconfirm
|
||||||
|
title="Are you sure to clear all data of offline proxies?"
|
||||||
|
@confirm="clearOfflineProxies"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button>ClearOfflineProxies</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
<el-button @click="$emit('refresh')">Refresh</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-page-header>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
:data="proxies"
|
:data="proxies"
|
||||||
:default-sort="{ prop: 'name', order: 'ascending' }"
|
:default-sort="{ prop: 'name', order: 'ascending' }"
|
||||||
|
@ -67,6 +90,7 @@
|
||||||
import * as Humanize from 'humanize-plus'
|
import * as Humanize from 'humanize-plus'
|
||||||
import type { TableColumnCtx } from 'element-plus'
|
import type { TableColumnCtx } from 'element-plus'
|
||||||
import type { BaseProxy } from '../utils/proxy.js'
|
import type { BaseProxy } from '../utils/proxy.js'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import ProxyViewExpand from './ProxyViewExpand.vue'
|
import ProxyViewExpand from './ProxyViewExpand.vue'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -74,6 +98,8 @@ defineProps<{
|
||||||
proxyType: string
|
proxyType: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
||||||
return Humanize.fileSize(row.trafficIn)
|
return Humanize.fileSize(row.trafficIn)
|
||||||
}
|
}
|
||||||
|
@ -81,4 +107,37 @@ const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
||||||
const formatTrafficOut = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
const formatTrafficOut = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
||||||
return Humanize.fileSize(row.trafficOut)
|
return Humanize.fileSize(row.trafficOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearOfflineProxies = () => {
|
||||||
|
fetch('/api/proxies?status=offline', {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
ElMessage({
|
||||||
|
message: 'Successfully cleared offline proxies',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
emit('refresh')
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
message: 'Failed to clear offline proxies: ' + res.status + ' ' + res.statusText,
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
ElMessage({
|
||||||
|
message: 'Failed to clear offline proxies: ' + err.message,
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.el-page-header__title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -17,10 +17,7 @@
|
||||||
<el-form-item label="KCP Bind Port" v-if="data.kcpBindPort != 0">
|
<el-form-item label="KCP Bind Port" v-if="data.kcpBindPort != 0">
|
||||||
<span>{{ data.kcpBindPort }}</span>
|
<span>{{ data.kcpBindPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item label="QUIC Bind Port" v-if="data.quicBindPort != 0">
|
||||||
label="QUIC Bind Port"
|
|
||||||
v-if="data.quicBindPort != 0"
|
|
||||||
>
|
|
||||||
<span>{{ data.quicBindPort }}</span>
|
<span>{{ data.quicBindPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Http Port" v-if="data.vhostHTTPPort != 0">
|
<el-form-item label="Http Port" v-if="data.vhostHTTPPort != 0">
|
||||||
|
|
|
@ -23,8 +23,10 @@ class BaseProxy {
|
||||||
this.type = ''
|
this.type = ''
|
||||||
this.encryption = false
|
this.encryption = false
|
||||||
this.compression = false
|
this.compression = false
|
||||||
this.encryption = (proxyStats.conf?.transport?.useEncryption) || this.encryption;
|
this.encryption =
|
||||||
this.compression = (proxyStats.conf?.transport?.useCompression) || this.compression;
|
proxyStats.conf?.transport?.useEncryption || this.encryption
|
||||||
|
this.compression =
|
||||||
|
proxyStats.conf?.transport?.useCompression || this.compression
|
||||||
this.conns = proxyStats.curConns
|
this.conns = proxyStats.curConns
|
||||||
this.trafficIn = proxyStats.todayTrafficIn
|
this.trafficIn = proxyStats.todayTrafficIn
|
||||||
this.trafficOut = proxyStats.todayTrafficOut
|
this.trafficOut = proxyStats.todayTrafficOut
|
||||||
|
@ -76,7 +78,7 @@ class HTTPProxy extends BaseProxy {
|
||||||
this.type = 'http'
|
this.type = 'http'
|
||||||
this.port = port
|
this.port = port
|
||||||
if (proxyStats.conf) {
|
if (proxyStats.conf) {
|
||||||
this.customDomains = proxyStats.conf.customDomains || this.customDomains;
|
this.customDomains = proxyStats.conf.customDomains || this.customDomains
|
||||||
this.hostHeaderRewrite = proxyStats.conf.hostHeaderRewrite
|
this.hostHeaderRewrite = proxyStats.conf.hostHeaderRewrite
|
||||||
this.locations = proxyStats.conf.locations
|
this.locations = proxyStats.conf.locations
|
||||||
if (proxyStats.conf.subdomain) {
|
if (proxyStats.conf.subdomain) {
|
||||||
|
@ -92,7 +94,7 @@ class HTTPSProxy extends BaseProxy {
|
||||||
this.type = 'https'
|
this.type = 'https'
|
||||||
this.port = port
|
this.port = port
|
||||||
if (proxyStats.conf != null) {
|
if (proxyStats.conf != null) {
|
||||||
this.customDomains = proxyStats.conf.customDomains || this.customDomains;
|
this.customDomains = proxyStats.conf.customDomains || this.customDomains
|
||||||
if (proxyStats.conf.subdomain) {
|
if (proxyStats.conf.subdomain) {
|
||||||
this.subdomain = `${proxyStats.conf.subdomain}.${subdomainHost}`
|
this.subdomain = `${proxyStats.conf.subdomain}.${subdomainHost}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
|
||||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"types": ["node"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,25 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"target": "ES2020",
|
||||||
"paths": {
|
"useDefineForClassFields": true,
|
||||||
"@/*": ["./src/*"]
|
"module": "ESNext",
|
||||||
}
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
},
|
"skipLibCheck": true,
|
||||||
|
|
||||||
"references": [
|
/* Bundler mode */
|
||||||
{
|
"moduleResolution": "bundler",
|
||||||
"path": "./tsconfig.config.json"
|
"allowImportingTsExtensions": true,
|
||||||
}
|
"resolveJsonModule": true,
|
||||||
]
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
2225
web/frps/yarn.lock
2225
web/frps/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue