diff --git a/docs/api.md b/docs/api.md index 743f74f..5ed8a09 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1165,6 +1165,91 @@ X-Timestamp: 1704067200000 --- +## Get frpc Instance Information + +**Endpoint:** `/frpcAct/instanceMgr/getInfo` +**Method:** GET +**Auth Required:** Yes (token) +**Permission Level:** Visitor + +Retrieve information about a specific frpc instance. Returns different information based on user permission level. + +**Request Headers:** +``` +X-Token: your_token +X-Timestamp: 1704067200000 +``` + +**Query Parameters:** +``` +instanceID=1 +``` + +| Header | Type | Required | Description | +|--------|------|----------|-------------| +| X-Token | string | Yes | Authentication token | +| X-Timestamp | int64 | Yes | Client timestamp in milliseconds | + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| instanceID | string | Yes | ID of the instance to get information for | + +**Response (Admin/Superuser):** +```json +{ + "success": true, + "message": "Instance info retrieved successfully", + "data": { + "name": "my_frpc", + "serviceName": "superfrpc_admin_my_frpc", + "createdAt": "2024-01-01T00:00:00Z", + "createdBy": "admin", + "isRunning": true, + "serverAddr": "127.0.0.1", + "serverPort": "7000", + "auth_method": "token", + "bootAtStart": true, + "runUser": "root", + "configPath": "./configs/superfrpc_admin_my_frpc.toml" + } +} +``` + +**Response (Visitor):** +```json +{ + "success": true, + "message": "Instance info retrieved successfully", + "data": { + "name": "my_frpc", + "serviceName": "superfrpc_admin_my_frpc", + "createdAt": "2024-01-01T00:00:00Z", + "createdBy": "admin", + "isRunning": true, + "auth_method": "token", + "bootAtStart": true, + "runUser": "root", + "configPath": "./configs/superfrpc_admin_my_frpc.toml" + } +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| name | string | Name of the frpc instance | +| serviceName | string | Service name | +| createdAt | string | Instance creation time (ISO 8601 format) | +| createdBy | string | Username of the user who created this instance | +| isRunning | bool | Instance running status: true (running) or false (stopped) | +| serverAddr | string | frps server address (only returned for admin/superuser) | +| serverPort | string | frps server port (only returned for admin/superuser) | +| auth_method | string | Authentication method | +| bootAtStart | bool | Whether the instance starts automatically on system boot | +| runUser | string | User that the instance runs as | +| configPath | string | Path to the instance's configuration file | + +--- + ## Real-time Log Streaming (WebSocket) **Endpoint:** `/system/getLogs` diff --git a/frpAct.go b/frpAct.go index b3e625b..8d84921 100644 --- a/frpAct.go +++ b/frpAct.go @@ -972,3 +972,99 @@ func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) { SendSuccessResponse(w, "Instance status retrieved successfully", responseData) postLog.Info(fmt.Sprintf("[GetInstanceStatusHandler] Retrieved status for instance %d (name: %s), isRunning: %v", instanceID, instance.Name, isRunning)) } + +func GetInstanceInfoHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") + postLog.Debug(fmt.Sprintf("[GetInstanceInfoHandler] Invalid request method: %s", r.Method)) + return + } + + queryParams := r.URL.Query() + instanceIDStr := queryParams.Get("instanceID") + if instanceIDStr == "" { + SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") + return + } + + instanceID, err := strconv.Atoi(instanceIDStr) + if err != nil { + SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") + return + } + + userID, _, err := ValidateRequestWithHeader(w, r) + if err != nil { + postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to validate request header: %v", err)) + SendErrorResponse(w, http.StatusBadRequest, "Invalid request header") + return + } + + instance, err := DBQueryFrpcInstanceByID(instanceID) + if err == sql.ErrNoRows { + SendErrorResponse(w, http.StatusNotFound, "Instance not found") + postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] User %d tried to get info of a not existed instance: %d", userID, instanceID)) + return + } + if err != nil { + postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to query instance: %v", err)) + SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") + return + } + + if instance.UserID != userID { + SendErrorResponse(w, http.StatusForbidden, "Instance not found") + postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] User %d tried to get info of instance %d that does not belong to them", userID, instanceID)) + return + } + + userType, err := GetUserType(userID) + if err != nil { + postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get user type: %v", err)) + SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type") + return + } + + serviceName, err := GetServiceNameByInstanceID(instanceID) + if err != nil { + postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get service name: %v", err)) + SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") + return + } + + isRunning := IsInstanceRunning(instanceID) + + responseData := map[string]interface{}{ + "name": instance.Name, + "serviceName": serviceName, + "createdAt": instance.CreatedAt, + "createdBy": instance.CreatedBy, + "isRunning": isRunning, + "auth_method": "", + "bootAtStart": instance.BootAtStart, + "runUser": instance.RunUser, + "configPath": instance.ConfigPath, + } + + configContent, err := os.ReadFile(instance.ConfigPath) + if err == nil { + config, err := decodeFrpcConfig(string(configContent)) + if err == nil { + if authMethod, ok := config.Global["auth.method"]; ok { + responseData["auth_method"] = authMethod + } + + if userType == "admin" || userType == "superuser" { + if serverAddr, ok := config.Global["serverAddr"]; ok { + responseData["serverAddr"] = serverAddr + } + if serverPort, ok := config.Global["serverPort"]; ok { + responseData["serverPort"] = serverPort + } + } + } + } + + SendSuccessResponse(w, "Instance info retrieved successfully", responseData) + postLog.Info(fmt.Sprintf("[GetInstanceInfoHandler] Retrieved info for instance %d (name: %s), userType: %s", instanceID, instance.Name, userType)) +} diff --git a/router.go b/router.go index 20144a3..0d5b354 100644 --- a/router.go +++ b/router.go @@ -33,6 +33,7 @@ func setupRoutes() { http.HandleFunc("/frpcAct/instanceMgr/stop", StopInstanceHandler) http.HandleFunc("/frpcAct/instanceMgr/restart", RestartInstanceHandler) http.HandleFunc("/frpcAct/instanceMgr/status", GetInstanceStatusHandler) + http.HandleFunc("/frpcAct/instanceMgr/getInfo", GetInstanceInfoHandler) http.HandleFunc("/frpcAct/proxyMgr/create", CreateProxyHandler) http.HandleFunc("/frpcAct/proxyMgr/delete", DeleteProxyHandler) http.HandleFunc("/frpcAct/proxyMgr/list", ListProxiesHandler)