feat(proxy): add proxy switch endpoint to enable/disable proxies, and all enabled parameter to display the status of each proxy when fetching the proxies list
This commit is contained in:
@@ -67,9 +67,9 @@ For detailed API documentation, please see [docs/api.md](docs/api.md)
|
||||
- [ ] Develop an agent software to handle windows service management
|
||||
- [ ] Refactor all log output level to be more clear
|
||||
- [x] Add frpc instance watchdog
|
||||
- [ ] Add per-proxy status display
|
||||
- [x] Add per-proxy status display
|
||||
- [ ] Add proxy edit submit action
|
||||
- [ ] Add per-proxy status control
|
||||
- [x] Add per-proxy status control
|
||||
- [x] Fix reboot failed when deployed as systemd service
|
||||
- [x] Enhance speed of listing instances
|
||||
- [x] Increase speed of fetching instance logs
|
||||
|
||||
51
docs/api.md
51
docs/api.md
@@ -1255,6 +1255,51 @@ X-Timestamp: 1704067200000
|
||||
|
||||
---
|
||||
|
||||
## Proxy Switch
|
||||
|
||||
**Endpoint:** `/frpcAct/proxyMgr/switch`
|
||||
**Method:** POST
|
||||
**Auth Required:** Yes (token)
|
||||
**Permission Level:** Admin
|
||||
|
||||
Switch the status of a proxy.
|
||||
|
||||
**Request Headers:**
|
||||
```
|
||||
X-Token: your_token
|
||||
X-Timestamp: 1704067200000
|
||||
```
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|----------|-------------|
|
||||
| X-Token | string | Yes | Authentication token |
|
||||
| X-Timestamp | int64 | Yes | Client timestamp in milliseconds |
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"instanceID": 1,
|
||||
"proxyName": "test",
|
||||
"action": 1
|
||||
}
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| instanceID | int | Instance ID |
|
||||
| proxyName | string | Name of the created proxy |
|
||||
| action | string | Enable or disable the current proxy. 0 is disable, 1 is enable |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Proxy status has been switched to off."
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## List Proxies
|
||||
|
||||
**Endpoint:** `/frpcAct/proxyMgr/list`
|
||||
@@ -1298,14 +1343,16 @@ X-Timestamp: 1704067200000
|
||||
"type": "tcp",
|
||||
"localIP": "127.0.0.1",
|
||||
"localPort": "22",
|
||||
"remotePort": "6000"
|
||||
"remotePort": "6000",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "web_proxy",
|
||||
"type": "http",
|
||||
"localIP": "127.0.0.1",
|
||||
"localPort": "8080",
|
||||
"remotePort": "80"
|
||||
"remotePort": "80",
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -452,7 +452,7 @@ func getStringFromMap(m map[string]interface{}, key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func getNumFromMap(m map[string]interface{}, key string) int {
|
||||
func getIntFromMap(m map[string]interface{}, key string) int {
|
||||
if v, ok := m[key].(int); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"super-frpc/config"
|
||||
"super-frpc/database"
|
||||
@@ -57,8 +58,8 @@ func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Name: getStringFromMap(proxyInfoMap, "name"),
|
||||
Type: getStringFromMap(proxyInfoMap, "type"),
|
||||
LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
|
||||
LocalPort: getNumFromMap(proxyInfoMap, "localPort"),
|
||||
RemotePort: getNumFromMap(proxyInfoMap, "remotePort"),
|
||||
LocalPort: getIntFromMap(proxyInfoMap, "localPort"),
|
||||
RemotePort: getIntFromMap(proxyInfoMap, "remotePort"),
|
||||
}
|
||||
|
||||
if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" ||
|
||||
@@ -155,8 +156,8 @@ func ModifyProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Name: getStringFromMap(proxyInfoMap, "name"),
|
||||
Type: getStringFromMap(proxyInfoMap, "type"),
|
||||
LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
|
||||
LocalPort: getNumFromMap(proxyInfoMap, "localPort"),
|
||||
RemotePort: getNumFromMap(proxyInfoMap, "remotePort"),
|
||||
LocalPort: getIntFromMap(proxyInfoMap, "localPort"),
|
||||
RemotePort: getIntFromMap(proxyInfoMap, "remotePort"),
|
||||
}
|
||||
|
||||
if proxyInfo.OldName == "" {
|
||||
@@ -336,18 +337,23 @@ func ListProxiesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
proxyList := make([]map[string]interface{}, len(cfg.Proxies))
|
||||
for i, proxy := range cfg.Proxies {
|
||||
contentStr := string(configContent)
|
||||
proxyList := make([]map[string]interface{}, 0, len(cfg.Proxies))
|
||||
for _, proxy := range cfg.Proxies {
|
||||
proxyData := map[string]interface{}{
|
||||
"name": proxy["name"],
|
||||
"type": proxy["type"],
|
||||
"localIP": proxy["localIP"],
|
||||
"localPort": proxy["localPort"],
|
||||
"remotePort": proxy["remotePort"],
|
||||
"enabled": true,
|
||||
}
|
||||
proxyList[i] = proxyData
|
||||
proxyList = append(proxyList, proxyData)
|
||||
}
|
||||
|
||||
disabledProxies := parseDisabledProxies(strings.Split(contentStr, "\n"))
|
||||
proxyList = append(proxyList, disabledProxies...)
|
||||
|
||||
utils.SendSuccessResponse(w, "Proxies listed successfully", map[string]interface{}{
|
||||
"instanceID": instance.ID,
|
||||
"proxyCount": len(proxyList),
|
||||
@@ -355,3 +361,190 @@ func ListProxiesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
postLog.Info(fmt.Sprintf("[ListProxiesHandler] Retrieved %d proxies for instance %d", len(proxyList), instance.ID))
|
||||
}
|
||||
|
||||
func SwitchProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
||||
postLog.Warning(fmt.Sprintf("[SwitchProxyHandler] Auth failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to read request body: %v", err))
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var reqMap map[string]interface{}
|
||||
if err := json.Unmarshal(body, &reqMap); err != nil {
|
||||
postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to unmarshal request body: %v", err))
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
||||
return
|
||||
}
|
||||
|
||||
instanceID := getIntFromMap(reqMap, "instanceID")
|
||||
if instanceID == 0 {
|
||||
postLog.Error("[SwitchProxyHandler] instanceID is required")
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
|
||||
return
|
||||
}
|
||||
|
||||
proxyName := getStringFromMap(reqMap, "proxyName")
|
||||
if proxyName == "" {
|
||||
postLog.Error("[SwitchProxyHandler] proxyName is required")
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "proxyName is required")
|
||||
return
|
||||
}
|
||||
|
||||
action := getIntFromMap(reqMap, "action")
|
||||
if action != 0 && action != 1 {
|
||||
postLog.Error("[SwitchProxyHandler] action must be 0 or 1")
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "action must be 0 (disable) or 1 (enable)")
|
||||
return
|
||||
}
|
||||
|
||||
var instance database.FrpcInstance
|
||||
instance, err = database.DBQueryFrpcInstanceByID(instanceID)
|
||||
if err != nil {
|
||||
postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to query instance: %v", err))
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
|
||||
return
|
||||
}
|
||||
|
||||
configContent, err := os.ReadFile(instance.ConfigPath)
|
||||
if err != nil {
|
||||
postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err))
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(string(configContent), "\n")
|
||||
startLine, endLine := findProxyBlock(lines, proxyName)
|
||||
if startLine < 0 {
|
||||
postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Proxy %s not found in config", proxyName))
|
||||
utils.SendErrorResponse(w, http.StatusNotFound, "Proxy not found in config")
|
||||
return
|
||||
}
|
||||
|
||||
if action == 0 {
|
||||
for i := startLine; i <= endLine; i++ {
|
||||
if !strings.HasPrefix(lines[i], "#") {
|
||||
lines[i] = "#" + lines[i]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := startLine; i <= endLine; i++ {
|
||||
lines[i] = strings.TrimPrefix(lines[i], "#")
|
||||
}
|
||||
}
|
||||
|
||||
updatedContent := strings.Join(lines, "\n")
|
||||
if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil {
|
||||
postLog.Error(fmt.Sprintf("[SwitchProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err))
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
|
||||
return
|
||||
}
|
||||
|
||||
statusText := "off"
|
||||
if action == 1 {
|
||||
statusText = "on"
|
||||
}
|
||||
utils.SendSuccessResponse(w, "Proxy status has been switched to "+statusText+".", map[string]interface{}{
|
||||
"instanceID": instance.ID,
|
||||
"proxyName": proxyName,
|
||||
"status": statusText,
|
||||
})
|
||||
postLog.Info(fmt.Sprintf("[SwitchProxyHandler] Proxy %s switched %s for instance %d", proxyName, statusText, instance.ID))
|
||||
}
|
||||
|
||||
func findProxyBlock(lines []string, proxyName string) (int, int) {
|
||||
targetLine := -1
|
||||
for i, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
uncommented := strings.TrimPrefix(trimmed, "#")
|
||||
uncommented = strings.TrimSpace(uncommented)
|
||||
if strings.HasPrefix(uncommented, "name ") && strings.Contains(uncommented, proxyName) {
|
||||
targetLine = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetLine < 0 {
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
startLine := targetLine
|
||||
for i := targetLine; i >= 0; i-- {
|
||||
trimmed := strings.TrimSpace(lines[i])
|
||||
uncommented := strings.TrimPrefix(trimmed, "#")
|
||||
uncommented = strings.TrimSpace(uncommented)
|
||||
if uncommented == "[[proxies]]" {
|
||||
startLine = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
endLine := targetLine
|
||||
for i := targetLine + 1; i < len(lines); i++ {
|
||||
trimmed := strings.TrimSpace(lines[i])
|
||||
uncommented := strings.TrimPrefix(trimmed, "#")
|
||||
uncommented = strings.TrimSpace(uncommented)
|
||||
if uncommented == "[[proxies]]" {
|
||||
endLine = i - 1
|
||||
break
|
||||
}
|
||||
endLine = i
|
||||
}
|
||||
|
||||
return startLine, endLine
|
||||
}
|
||||
|
||||
func parseDisabledProxies(lines []string) []map[string]interface{} {
|
||||
var disabled []map[string]interface{}
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
trimmed := strings.TrimSpace(lines[i])
|
||||
if trimmed != "#[[proxies]]" {
|
||||
continue
|
||||
}
|
||||
|
||||
proxy := map[string]interface{}{"enabled": false}
|
||||
for j := i + 1; j < len(lines); j++ {
|
||||
line := lines[j]
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
break
|
||||
}
|
||||
uncommented := strings.TrimPrefix(strings.TrimSpace(line), "#")
|
||||
uncommented = strings.TrimSpace(uncommented)
|
||||
if uncommented == "" || strings.HasPrefix(uncommented, "[[") {
|
||||
break
|
||||
}
|
||||
parts := strings.SplitN(uncommented, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
val := strings.TrimSpace(parts[1])
|
||||
proxy[key] = parseTOMLValue(val)
|
||||
}
|
||||
|
||||
if proxy["name"] != nil && proxy["name"] != "" {
|
||||
disabled = append(disabled, proxy)
|
||||
}
|
||||
}
|
||||
|
||||
return disabled
|
||||
}
|
||||
|
||||
func parseTOMLValue(val string) interface{} {
|
||||
if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' {
|
||||
return val[1 : len(val)-1]
|
||||
}
|
||||
if n, err := strconv.Atoi(val); err == nil {
|
||||
return n
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func setupRoutes() {
|
||||
http.HandleFunc("/api/frpcAct/proxyMgr/modify", handlers.ModifyProxyHandler)
|
||||
http.HandleFunc("/api/frpcAct/proxyMgr/delete", handlers.DeleteProxyHandler)
|
||||
http.HandleFunc("/api/frpcAct/proxyMgr/list", handlers.ListProxiesHandler)
|
||||
http.HandleFunc("/api/frpcAct/proxyMgr/switch", handlers.SwitchProxyHandler)
|
||||
|
||||
http.HandleFunc("/", web.ServeStatic)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user