mirror of
https://github.com/fatedier/frp.git
synced 2024-12-22 23:14:49 +07:00
web: support to clear offline proxies data on dashboard (#3963)
This commit is contained in:
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
42
assets/frpc/static/index-bLBhaJo8.js
Normal file
42
assets/frpc/static/index-bLBhaJo8.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/frpc/static/index-iuf46MlF.css
Normal file
1
assets/frpc/static/index-iuf46MlF.css
Normal file
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
84
assets/frps/static/index-1gecbKzv.js
Normal file
84
assets/frps/static/index-1gecbKzv.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/frps/static/index-Lf6B06jY.css
Normal file
1
assets/frps/static/index-Lf6B06jY.css
Normal file
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
web/frpc/auto-imports.d.ts
vendored
1
web/frpc/auto-imports.d.ts
vendored
@ -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 {
|
||||||
|
4
web/frpc/components.d.ts
vendored
4
web/frpc/components.d.ts
vendored
@ -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" }]
|
||||||
}
|
}
|
||||||
|
10
web/frpc/tsconfig.node.json
Normal file
10
web/frpc/tsconfig.node.json
Normal file
@ -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
6
web/frps/auto-imports.d.ts
vendored
6
web/frps/auto-imports.d.ts
vendored
@ -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 {
|
||||||
|
|
||||||
|
12
web/frps/components.d.ts
vendored
12
web/frps/components.d.ts
vendored
@ -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,12 +78,12 @@ 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) {
|
||||||
this.subdomain = `${proxyStats.conf.subdomain}.${subdomainHost}`
|
this.subdomain = `${proxyStats.conf.subdomain}.${subdomainHost}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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" }]
|
||||||
}
|
}
|
||||||
|
10
web/frps/tsconfig.node.json
Normal file
10
web/frps/tsconfig.node.json
Normal file
@ -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
Block a user