feat(proxy): add modify proxy API endpoint and functionality
Implement proxy modification feature including: - New modifyFrpcProxy function in config.go - New ModifyProxyHandler in frpcProxyAct.go - New API endpoint in router.go - Updated API documentation in docs/api.md
This commit is contained in:
@@ -68,6 +68,7 @@ For detailed API documentation, please see [docs/api.md](docs/api.md)
|
|||||||
- [x] Add frpc instance log display API
|
- [x] Add frpc instance log display API
|
||||||
- [x] Fix random database lock when processing logs
|
- [x] Fix random database lock when processing logs
|
||||||
- [ ] Add frpc createdBy storage and display
|
- [ ] Add frpc createdBy storage and display
|
||||||
|
- [x] Add frpc proxy management API
|
||||||
- [x] Fix backend can still start frpc instance when it is already running
|
- [x] Fix backend can still start frpc instance when it is already running
|
||||||
- [ ] Develop an agent software to handle windows service management
|
- [ ] Develop an agent software to handle windows service management
|
||||||
- [ ] Refactor all log output level to be more clear
|
- [ ] Refactor all log output level to be more clear
|
||||||
|
|||||||
33
config.go
33
config.go
@@ -294,6 +294,39 @@ func removeFrpcProxy(configContent string, proxyName string) (string, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func modifyFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) {
|
||||||
|
config, err := decodeFrpcConfig(configContent)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for i, proxy := range config.Proxies {
|
||||||
|
if name, ok := proxy["name"].(string); ok && name == info.Name {
|
||||||
|
config.Proxies[i] = map[string]interface{}{
|
||||||
|
"name": info.Name,
|
||||||
|
"type": info.Type,
|
||||||
|
"localIP": info.LocalIP,
|
||||||
|
"localPort": info.LocalPort,
|
||||||
|
"remotePort": info.RemotePort,
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return "", fmt.Errorf("proxy %s not found", info.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := encodeFrpcConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to write config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getKeyText(configPath, key, section string) (value string, err error) {
|
func getKeyText(configPath, key, section string) (value string, err error) {
|
||||||
configContent, err := os.ReadFile(configPath)
|
configContent, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
85
docs/api.md
85
docs/api.md
@@ -1042,6 +1042,91 @@ remote_port = 6000
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Modify Proxy
|
||||||
|
|
||||||
|
**Endpoint:** `/frpcAct/proxyMgr/modify`
|
||||||
|
**Method:** POST
|
||||||
|
**Content-Type:** application/json
|
||||||
|
**Auth Required:** Yes (token)
|
||||||
|
**Permission Level:** Admin
|
||||||
|
|
||||||
|
Modify an existing proxy configuration for an frpc instance. The proxy configuration will be updated in the instance's config file.
|
||||||
|
|
||||||
|
**Request Headers:**
|
||||||
|
```
|
||||||
|
X-Token: your_token
|
||||||
|
X-Timestamp: 1704067200000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"instanceID": "1",
|
||||||
|
"proxyInfo": {
|
||||||
|
"name": "ssh_proxy",
|
||||||
|
"type": "tcp",
|
||||||
|
"localIP": "127.0.0.1",
|
||||||
|
"localPort": "22",
|
||||||
|
"remotePort": "6001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Header | Type | Required | Description |
|
||||||
|
|--------|------|----------|-------------|
|
||||||
|
| X-Token | string | Yes | Authentication token |
|
||||||
|
| X-Timestamp | int64 | Yes | Client timestamp in milliseconds |
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| instanceID | string | Yes | Instance ID (the ID of the frpc instance) |
|
||||||
|
| proxyInfo.name | string | Yes | Proxy name (used to identify which proxy to modify) |
|
||||||
|
| proxyInfo.type | string | Yes | Proxy type (e.g., tcp, udp, http, https) |
|
||||||
|
| proxyInfo.localIP | string | Yes | Local IP address to forward to |
|
||||||
|
| proxyInfo.localPort | string | Yes | Local port to forward from |
|
||||||
|
| proxyInfo.remotePort | string | Yes | Remote port on frps to expose |
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Proxy modified successfully",
|
||||||
|
"data": {
|
||||||
|
"instanceID": 1,
|
||||||
|
"configPath": "./configs/superfrpc_user_my_frpc.toml",
|
||||||
|
"proxyName": "ssh_proxy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| instanceID | int | Instance ID |
|
||||||
|
| configPath | string | Path to the configuration file |
|
||||||
|
| proxyName | string | Name of the modified proxy |
|
||||||
|
|
||||||
|
**Config File Format:**
|
||||||
|
|
||||||
|
The proxy configuration in the config file will be updated to the following format:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[proxies]]
|
||||||
|
name = ssh_proxy
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6001
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:**
|
||||||
|
> - The proxy configuration is updated in the existing config file
|
||||||
|
> - The instance must already exist before modifying a proxy
|
||||||
|
> - This endpoint does not modify the database, only the config file
|
||||||
|
> - The frpc service needs to be restarted for changes to take effect
|
||||||
|
> - The proxy name is used to identify which proxy to modify; if the proxy is not found, an error will be returned
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Delete Proxy
|
## Delete Proxy
|
||||||
|
|
||||||
**Endpoint:** `/frpcAct/proxyMgr/delete`
|
**Endpoint:** `/frpcAct/proxyMgr/delete`
|
||||||
|
|||||||
@@ -107,6 +107,101 @@ func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %d", proxyInfo.Name, instance.ID))
|
postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %d", proxyInfo.Name, instance.ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ModifyProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, err := Auth(w, r, http.MethodPost, "superuser", "admin")
|
||||||
|
if err != nil {
|
||||||
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
||||||
|
postLog.Warning(fmt.Sprintf("[ModifyProxyHandler] Auth failed: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to read request body: %v", err))
|
||||||
|
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("[ModifyProxyHandler] Failed to unmarshal request body: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceID := getStringFromMap(reqMap, "instanceID")
|
||||||
|
if instanceID == "" {
|
||||||
|
postLog.Error("[ModifyProxyHandler] instanceID is required")
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyInfoMap, ok := reqMap["proxyInfo"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
postLog.Error("[ModifyProxyHandler] Invalid proxyInfo format")
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid proxyInfo format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyInfo := FrpcProxyInfo{
|
||||||
|
Name: getStringFromMap(proxyInfoMap, "name"),
|
||||||
|
Type: getStringFromMap(proxyInfoMap, "type"),
|
||||||
|
LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
|
||||||
|
LocalPort: getStringFromMap(proxyInfoMap, "localPort"),
|
||||||
|
RemotePort: getStringFromMap(proxyInfoMap, "remotePort"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" ||
|
||||||
|
proxyInfo.LocalPort == "" || proxyInfo.RemotePort == "" {
|
||||||
|
postLog.Error("[ModifyProxyHandler] Missing required fields in proxyInfo")
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance FrpcInstance
|
||||||
|
instanceIDInt, _ := strconv.Atoi(instanceID)
|
||||||
|
instance, err = DBQueryFrpcInstanceByID(instanceIDInt)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to query instance: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if instance.UserID != userID {
|
||||||
|
postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Instance not found for user %d", userID))
|
||||||
|
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configContent, err := os.ReadFile(instance.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedContent, err := modifyFrpcProxy(string(configContent), proxyInfo)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to modify proxy: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to modify proxy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[ModifyProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SendSuccessResponse(w, "Proxy modified successfully", map[string]interface{}{
|
||||||
|
"instanceID": instance.ID,
|
||||||
|
"configPath": instance.ConfigPath,
|
||||||
|
"proxyName": proxyInfo.Name,
|
||||||
|
})
|
||||||
|
postLog.Info(fmt.Sprintf("[ModifyProxyHandler] Proxy %s modified successfully for instance %d", proxyInfo.Name, instance.ID))
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) {
|
func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
userID, err := Auth(w, r, http.MethodPost, "superuser", "admin")
|
userID, err := Auth(w, r, http.MethodPost, "superuser", "admin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func setupRoutes() {
|
|||||||
http.HandleFunc("/frpcAct/instanceMgr/getInfo", GetInstanceInfoHandler)
|
http.HandleFunc("/frpcAct/instanceMgr/getInfo", GetInstanceInfoHandler)
|
||||||
http.HandleFunc("/frpcAct/instanceMgr/logs", frpLogger.NewInstanceLogHandler(ValidateTokenFromMap).ServeHTTP)
|
http.HandleFunc("/frpcAct/instanceMgr/logs", frpLogger.NewInstanceLogHandler(ValidateTokenFromMap).ServeHTTP)
|
||||||
http.HandleFunc("/frpcAct/proxyMgr/create", CreateProxyHandler)
|
http.HandleFunc("/frpcAct/proxyMgr/create", CreateProxyHandler)
|
||||||
|
http.HandleFunc("/frpcAct/proxyMgr/modify", ModifyProxyHandler)
|
||||||
http.HandleFunc("/frpcAct/proxyMgr/delete", DeleteProxyHandler)
|
http.HandleFunc("/frpcAct/proxyMgr/delete", DeleteProxyHandler)
|
||||||
http.HandleFunc("/frpcAct/proxyMgr/list", ListProxiesHandler)
|
http.HandleFunc("/frpcAct/proxyMgr/list", ListProxiesHandler)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user