feat: update plugin handling to use unique PluginID across client and server interactions
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 7m28s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 2m44s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 7m27s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m18s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 5m49s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 2m17s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 5m4s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 5m57s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 4m36s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Failing after 13m47s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Failing after 26m24s
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 7m28s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 2m44s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 7m27s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m18s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 5m49s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 2m17s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 5m4s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 5m57s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 4m36s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Failing after 13m47s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Failing after 26m24s
This commit is contained in:
@@ -550,10 +550,15 @@ func (c *Client) handleJSPluginInstall(stream net.Conn, msg *protocol.Message) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logf("[Client] Installing JS plugin: %s", req.PluginName)
|
c.logf("[Client] Installing JS plugin: %s (ID: %s)", req.PluginName, req.PluginID)
|
||||||
|
|
||||||
|
// 使用 PluginID 作为 key(如果有),否则回退到 pluginName:ruleName
|
||||||
|
key := req.PluginID
|
||||||
|
if key == "" {
|
||||||
|
key = req.PluginName + ":" + req.RuleName
|
||||||
|
}
|
||||||
|
|
||||||
// 如果插件已经在运行,先停止它
|
// 如果插件已经在运行,先停止它
|
||||||
key := req.PluginName + ":" + req.RuleName
|
|
||||||
c.pluginMu.Lock()
|
c.pluginMu.Lock()
|
||||||
if existingHandler, ok := c.runningPlugins[key]; ok {
|
if existingHandler, ok := c.runningPlugins[key]; ok {
|
||||||
c.logf("[Client] Stopping existing plugin %s before reinstall", key)
|
c.logf("[Client] Stopping existing plugin %s before reinstall", key)
|
||||||
@@ -625,12 +630,16 @@ func (c *Client) startJSPlugin(handler plugin.ClientPlugin, req protocol.JSPlugi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := req.PluginName + ":" + req.RuleName
|
// 使用 PluginID 作为 key(如果有),否则回退到 pluginName:ruleName
|
||||||
|
key := req.PluginID
|
||||||
|
if key == "" {
|
||||||
|
key = req.PluginName + ":" + req.RuleName
|
||||||
|
}
|
||||||
c.pluginMu.Lock()
|
c.pluginMu.Lock()
|
||||||
c.runningPlugins[key] = handler
|
c.runningPlugins[key] = handler
|
||||||
c.pluginMu.Unlock()
|
c.pluginMu.Unlock()
|
||||||
|
|
||||||
c.logf("[Client] JS plugin %s started at %s", req.PluginName, localAddr)
|
c.logf("[Client] JS plugin %s (ID: %s) started at %s", req.PluginName, req.PluginID, localAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyJSPluginSignature 验证 JS 插件签名
|
// verifyJSPluginSignature 验证 JS 插件签名
|
||||||
@@ -1055,18 +1064,27 @@ func (c *Client) handlePluginAPIRequest(stream net.Conn, msg *protocol.Message)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logf("[Client] Plugin API request: %s %s for plugin %s", req.Method, req.Path, req.PluginName)
|
c.logf("[Client] Plugin API request: %s %s for plugin %s (ID: %s)", req.Method, req.Path, req.PluginName, req.PluginID)
|
||||||
|
|
||||||
// 查找运行中的插件
|
// 查找运行中的插件
|
||||||
c.pluginMu.RLock()
|
c.pluginMu.RLock()
|
||||||
var handler plugin.ClientPlugin
|
var handler plugin.ClientPlugin
|
||||||
|
|
||||||
|
// 优先使用 PluginID 查找
|
||||||
|
if req.PluginID != "" {
|
||||||
|
handler = c.runningPlugins[req.PluginID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没找到,尝试通过 PluginName 匹配(向后兼容)
|
||||||
|
if handler == nil && req.PluginName != "" {
|
||||||
for key, p := range c.runningPlugins {
|
for key, p := range c.runningPlugins {
|
||||||
// key 格式为 "pluginName:ruleName"
|
// key 可能是 PluginID 或 "pluginName:ruleName" 格式
|
||||||
if strings.HasPrefix(key, req.PluginName+":") {
|
if strings.HasPrefix(key, req.PluginName+":") {
|
||||||
handler = p
|
handler = p
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
c.pluginMu.RUnlock()
|
c.pluginMu.RUnlock()
|
||||||
|
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type ConfigField struct {
|
|||||||
|
|
||||||
// ClientPlugin 客户端已安装的插件
|
// ClientPlugin 客户端已安装的插件
|
||||||
type ClientPlugin struct {
|
type ClientPlugin struct {
|
||||||
|
ID string `json:"id"` // 插件实例唯一 ID
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
|||||||
@@ -333,25 +333,43 @@ func (h *ClientHandler) InstallPlugins(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path string true "客户端ID"
|
// @Param id path string true "客户端ID"
|
||||||
// @Param pluginName path string true "插件名称"
|
// @Param pluginID path string true "插件实例ID"
|
||||||
// @Param action path string true "操作类型" Enums(start, stop, restart, config, delete)
|
// @Param action path string true "操作类型" Enums(start, stop, restart, config, delete)
|
||||||
// @Param request body dto.ClientPluginActionRequest false "操作参数"
|
// @Param request body dto.ClientPluginActionRequest false "操作参数"
|
||||||
// @Success 200 {object} Response
|
// @Success 200 {object} Response
|
||||||
// @Failure 400 {object} Response
|
// @Failure 400 {object} Response
|
||||||
// @Router /api/client/{id}/plugin/{pluginName}/{action} [post]
|
// @Router /api/client/{id}/plugin/{pluginID}/{action} [post]
|
||||||
func (h *ClientHandler) PluginAction(c *gin.Context) {
|
func (h *ClientHandler) PluginAction(c *gin.Context) {
|
||||||
clientID := c.Param("id")
|
clientID := c.Param("id")
|
||||||
pluginName := c.Param("pluginName")
|
pluginID := c.Param("pluginID")
|
||||||
action := c.Param("action")
|
action := c.Param("action")
|
||||||
|
|
||||||
var req dto.ClientPluginActionRequest
|
var req dto.ClientPluginActionRequest
|
||||||
c.ShouldBindJSON(&req) // 忽略错误,使用默认值
|
c.ShouldBindJSON(&req) // 忽略错误,使用默认值
|
||||||
|
|
||||||
|
// 通过 pluginID 查找插件信息
|
||||||
|
client, err := h.app.GetClientStore().GetClient(clientID)
|
||||||
|
if err != nil {
|
||||||
|
NotFound(c, "client not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginName string
|
||||||
|
for _, p := range client.Plugins {
|
||||||
|
if p.ID == pluginID {
|
||||||
|
pluginName = p.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pluginName == "" {
|
||||||
|
NotFound(c, "plugin not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if req.RuleName == "" {
|
if req.RuleName == "" {
|
||||||
req.RuleName = pluginName
|
req.RuleName = pluginName
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
switch action {
|
switch action {
|
||||||
case "start":
|
case "start":
|
||||||
err = h.app.GetServer().StartClientPlugin(clientID, pluginName, req.RuleName)
|
err = h.app.GetServer().StartClientPlugin(clientID, pluginName, req.RuleName)
|
||||||
@@ -366,7 +384,7 @@ func (h *ClientHandler) PluginAction(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err = h.app.GetServer().UpdateClientPluginConfig(clientID, pluginName, req.RuleName, req.Config, req.Restart)
|
err = h.app.GetServer().UpdateClientPluginConfig(clientID, pluginName, req.RuleName, req.Config, req.Restart)
|
||||||
case "delete":
|
case "delete":
|
||||||
err = h.deleteClientPlugin(clientID, pluginName)
|
err = h.deleteClientPlugin(clientID, pluginID)
|
||||||
default:
|
default:
|
||||||
BadRequest(c, "unknown action: "+action)
|
BadRequest(c, "unknown action: "+action)
|
||||||
return
|
return
|
||||||
@@ -380,11 +398,12 @@ func (h *ClientHandler) PluginAction(c *gin.Context) {
|
|||||||
Success(c, gin.H{
|
Success(c, gin.H{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"action": action,
|
"action": action,
|
||||||
|
"plugin_id": pluginID,
|
||||||
"plugin": pluginName,
|
"plugin": pluginName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ClientHandler) deleteClientPlugin(clientID, pluginName string) error {
|
func (h *ClientHandler) deleteClientPlugin(clientID, pluginID string) error {
|
||||||
client, err := h.app.GetClientStore().GetClient(clientID)
|
client, err := h.app.GetClientStore().GetClient(clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("client not found")
|
return fmt.Errorf("client not found")
|
||||||
@@ -393,7 +412,7 @@ func (h *ClientHandler) deleteClientPlugin(clientID, pluginName string) error {
|
|||||||
var newPlugins []db.ClientPlugin
|
var newPlugins []db.ClientPlugin
|
||||||
found := false
|
found := false
|
||||||
for _, p := range client.Plugins {
|
for _, p := range client.Plugins {
|
||||||
if p.Name == pluginName {
|
if p.ID == pluginID {
|
||||||
found = true
|
found = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -401,7 +420,7 @@ func (h *ClientHandler) deleteClientPlugin(clientID, pluginName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("plugin %s not found", pluginName)
|
return fmt.Errorf("plugin %s not found", pluginID)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Plugins = newPlugins
|
client.Plugins = newPlugins
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ type PluginInfo struct {
|
|||||||
|
|
||||||
// JSPluginInstallRequest JS 插件安装请求
|
// JSPluginInstallRequest JS 插件安装请求
|
||||||
type JSPluginInstallRequest struct {
|
type JSPluginInstallRequest struct {
|
||||||
|
PluginID string `json:"plugin_id"`
|
||||||
PluginName string `json:"plugin_name"`
|
PluginName string `json:"plugin_name"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ func NewPluginAPIHandler(app AppInterface) *PluginAPIHandler {
|
|||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param clientID path string true "客户端 ID"
|
// @Param id path string true "客户端 ID"
|
||||||
// @Param pluginName path string true "插件名称"
|
// @Param pluginID path string true "插件实例 ID"
|
||||||
// @Param route path string true "插件路由"
|
// @Param route path string true "插件路由"
|
||||||
// @Success 200 {object} object
|
// @Success 200 {object} object
|
||||||
// @Failure 404 {object} Response
|
// @Failure 404 {object} Response
|
||||||
// @Failure 502 {object} Response
|
// @Failure 502 {object} Response
|
||||||
// @Router /api/client/{clientID}/plugin/{pluginName}/{route} [get]
|
// @Router /api/client/{id}/plugin-api/{pluginID}/{route} [get]
|
||||||
func (h *PluginAPIHandler) ProxyRequest(c *gin.Context) {
|
func (h *PluginAPIHandler) ProxyRequest(c *gin.Context) {
|
||||||
clientID := c.Param("clientID")
|
clientID := c.Param("id")
|
||||||
pluginName := c.Param("pluginName")
|
pluginID := c.Param("pluginID")
|
||||||
route := c.Param("route")
|
route := c.Param("route")
|
||||||
|
|
||||||
// 确保路由以 / 开头
|
// 确保路由以 / 开头
|
||||||
@@ -68,7 +68,7 @@ func (h *PluginAPIHandler) ProxyRequest(c *gin.Context) {
|
|||||||
|
|
||||||
// 构建 API 请求
|
// 构建 API 请求
|
||||||
apiReq := protocol.PluginAPIRequest{
|
apiReq := protocol.PluginAPIRequest{
|
||||||
PluginName: pluginName,
|
PluginID: pluginID,
|
||||||
Method: c.Request.Method,
|
Method: c.Request.Method,
|
||||||
Path: route,
|
Path: route,
|
||||||
Query: c.Request.URL.RawQuery,
|
Query: c.Request.URL.RawQuery,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/gotunnel/internal/server/db"
|
"github.com/gotunnel/internal/server/db"
|
||||||
"github.com/gotunnel/internal/server/router/dto"
|
"github.com/gotunnel/internal/server/router/dto"
|
||||||
"github.com/gotunnel/pkg/protocol"
|
"github.com/gotunnel/pkg/protocol"
|
||||||
@@ -125,8 +126,24 @@ func (h *StoreHandler) Install(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查插件是否已存在,决定使用已有 ID 还是生成新 ID
|
||||||
|
pluginID := ""
|
||||||
|
dbClient, err := h.app.GetClientStore().GetClient(req.ClientID)
|
||||||
|
if err == nil {
|
||||||
|
for _, p := range dbClient.Plugins {
|
||||||
|
if p.Name == req.PluginName && p.ID != "" {
|
||||||
|
pluginID = p.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pluginID == "" {
|
||||||
|
pluginID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
// 安装到客户端
|
// 安装到客户端
|
||||||
installReq := JSPluginInstallRequest{
|
installReq := JSPluginInstallRequest{
|
||||||
|
PluginID: pluginID,
|
||||||
PluginName: req.PluginName,
|
PluginName: req.PluginName,
|
||||||
Source: string(source),
|
Source: string(source),
|
||||||
Signature: string(signature),
|
Signature: string(signature),
|
||||||
@@ -152,14 +169,19 @@ func (h *StoreHandler) Install(c *gin.Context) {
|
|||||||
h.app.GetJSPluginStore().SaveJSPlugin(jsPlugin)
|
h.app.GetJSPluginStore().SaveJSPlugin(jsPlugin)
|
||||||
|
|
||||||
// 将插件信息保存到客户端记录
|
// 将插件信息保存到客户端记录
|
||||||
dbClient, err := h.app.GetClientStore().GetClient(req.ClientID)
|
// 重新获取 dbClient(可能已被修改)
|
||||||
|
dbClient, err = h.app.GetClientStore().GetClient(req.ClientID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 检查插件是否已存在
|
// 检查插件是否已存在(通过名称匹配)
|
||||||
pluginExists := false
|
pluginExists := false
|
||||||
for i, p := range dbClient.Plugins {
|
for i, p := range dbClient.Plugins {
|
||||||
if p.Name == req.PluginName {
|
if p.Name == req.PluginName {
|
||||||
dbClient.Plugins[i].Enabled = true
|
dbClient.Plugins[i].Enabled = true
|
||||||
dbClient.Plugins[i].RemotePort = req.RemotePort
|
dbClient.Plugins[i].RemotePort = req.RemotePort
|
||||||
|
// 确保有 ID
|
||||||
|
if dbClient.Plugins[i].ID == "" {
|
||||||
|
dbClient.Plugins[i].ID = pluginID
|
||||||
|
}
|
||||||
pluginExists = true
|
pluginExists = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -183,6 +205,7 @@ func (h *StoreHandler) Install(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
dbClient.Plugins = append(dbClient.Plugins, db.ClientPlugin{
|
dbClient.Plugins = append(dbClient.Plugins, db.ClientPlugin{
|
||||||
|
ID: pluginID,
|
||||||
Name: req.PluginName,
|
Name: req.PluginName,
|
||||||
Version: version,
|
Version: version,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@@ -242,6 +265,7 @@ func (h *StoreHandler) Install(c *gin.Context) {
|
|||||||
Success(c, gin.H{
|
Success(c, gin.H{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"plugin": req.PluginName,
|
"plugin": req.PluginName,
|
||||||
|
"plugin_id": pluginID,
|
||||||
"client": req.ClientID,
|
"client": req.ClientID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (r *GinRouter) SetupRoutes(app handler.AppInterface, jwtAuth *auth.JWTAuth,
|
|||||||
api.POST("/client/:id/disconnect", clientHandler.Disconnect)
|
api.POST("/client/:id/disconnect", clientHandler.Disconnect)
|
||||||
api.POST("/client/:id/restart", clientHandler.Restart)
|
api.POST("/client/:id/restart", clientHandler.Restart)
|
||||||
api.POST("/client/:id/install-plugins", clientHandler.InstallPlugins)
|
api.POST("/client/:id/install-plugins", clientHandler.InstallPlugins)
|
||||||
api.POST("/client/:id/plugin/:pluginName/:action", clientHandler.PluginAction)
|
api.POST("/client/:id/plugin/:pluginID/:action", clientHandler.PluginAction)
|
||||||
|
|
||||||
// 配置管理
|
// 配置管理
|
||||||
configHandler := handler.NewConfigHandler(app)
|
configHandler := handler.NewConfigHandler(app)
|
||||||
@@ -112,7 +112,7 @@ func (r *GinRouter) SetupRoutes(app handler.AppInterface, jwtAuth *auth.JWTAuth,
|
|||||||
|
|
||||||
// 插件 API 代理 (通过 Web API 访问客户端插件)
|
// 插件 API 代理 (通过 Web API 访问客户端插件)
|
||||||
pluginAPIHandler := handler.NewPluginAPIHandler(app)
|
pluginAPIHandler := handler.NewPluginAPIHandler(app)
|
||||||
api.Any("/client/:clientID/plugin/:pluginName/*route", pluginAPIHandler.ProxyRequest)
|
api.Any("/client/:id/plugin-api/:pluginID/*route", pluginAPIHandler.ProxyRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -991,6 +991,7 @@ func (s *Server) InstallJSPluginToClient(clientID string, req router.JSPluginIns
|
|||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
installReq := protocol.JSPluginInstallRequest{
|
installReq := protocol.JSPluginInstallRequest{
|
||||||
|
PluginID: req.PluginID,
|
||||||
PluginName: req.PluginName,
|
PluginName: req.PluginName,
|
||||||
Source: req.Source,
|
Source: req.Source,
|
||||||
Signature: req.Signature,
|
Signature: req.Signature,
|
||||||
@@ -1304,6 +1305,7 @@ func (s *Server) pushClientInstalledPlugins(cs *ClientSession, alreadyPushed map
|
|||||||
}
|
}
|
||||||
|
|
||||||
req := router.JSPluginInstallRequest{
|
req := router.JSPluginInstallRequest{
|
||||||
|
PluginID: cp.ID,
|
||||||
PluginName: cp.Name,
|
PluginName: cp.Name,
|
||||||
Source: jsPlugin.Source,
|
Source: jsPlugin.Source,
|
||||||
Signature: jsPlugin.Signature,
|
Signature: jsPlugin.Signature,
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ type PluginStatusQueryResponse struct {
|
|||||||
|
|
||||||
// JSPluginInstallRequest JS 插件安装请求
|
// JSPluginInstallRequest JS 插件安装请求
|
||||||
type JSPluginInstallRequest struct {
|
type JSPluginInstallRequest struct {
|
||||||
|
PluginID string `json:"plugin_id"` // 插件实例唯一 ID
|
||||||
PluginName string `json:"plugin_name"` // 插件名称
|
PluginName string `json:"plugin_name"` // 插件名称
|
||||||
Source string `json:"source"` // JS 源码
|
Source string `json:"source"` // JS 源码
|
||||||
Signature string `json:"signature"` // 官方签名 (Base64)
|
Signature string `json:"signature"` // 官方签名 (Base64)
|
||||||
@@ -361,7 +362,8 @@ type LogStopRequest struct {
|
|||||||
|
|
||||||
// PluginAPIRequest 插件 API 请求
|
// PluginAPIRequest 插件 API 请求
|
||||||
type PluginAPIRequest struct {
|
type PluginAPIRequest struct {
|
||||||
PluginName string `json:"plugin_name"` // 插件名称
|
PluginID string `json:"plugin_id"` // 插件实例唯一 ID
|
||||||
|
PluginName string `json:"plugin_name"` // 插件名称 (向后兼容)
|
||||||
Method string `json:"method"` // HTTP 方法: GET, POST, PUT, DELETE
|
Method string `json:"method"` // HTTP 方法: GET, POST, PUT, DELETE
|
||||||
Path string `json:"path"` // 路由路径
|
Path string `json:"path"` // 路由路径
|
||||||
Query string `json:"query"` // 查询参数
|
Query string `json:"query"` // 查询参数
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ export const installPluginsToClient = (id: string, plugins: string[]) =>
|
|||||||
// 规则配置模式
|
// 规则配置模式
|
||||||
export const getRuleSchemas = () => get<RuleSchemasMap>('/rule-schemas')
|
export const getRuleSchemas = () => get<RuleSchemasMap>('/rule-schemas')
|
||||||
|
|
||||||
// 客户端插件控制
|
// 客户端插件控制(使用 pluginID)
|
||||||
export const startClientPlugin = (clientId: string, pluginName: string, ruleName: string) =>
|
export const startClientPlugin = (clientId: string, pluginId: string, ruleName: string) =>
|
||||||
post(`/client/${clientId}/plugin/${pluginName}/start`, { rule_name: ruleName })
|
post(`/client/${clientId}/plugin/${pluginId}/start`, { rule_name: ruleName })
|
||||||
export const stopClientPlugin = (clientId: string, pluginName: string, ruleName: string) =>
|
export const stopClientPlugin = (clientId: string, pluginId: string, ruleName: string) =>
|
||||||
post(`/client/${clientId}/plugin/${pluginName}/stop`, { rule_name: ruleName })
|
post(`/client/${clientId}/plugin/${pluginId}/stop`, { rule_name: ruleName })
|
||||||
export const restartClientPlugin = (clientId: string, pluginName: string, ruleName: string) =>
|
export const restartClientPlugin = (clientId: string, pluginId: string, ruleName: string) =>
|
||||||
post(`/client/${clientId}/plugin/${pluginName}/restart`, { rule_name: ruleName })
|
post(`/client/${clientId}/plugin/${pluginId}/restart`, { rule_name: ruleName })
|
||||||
export const deleteClientPlugin = (clientId: string, pluginName: string) =>
|
export const deleteClientPlugin = (clientId: string, pluginId: string) =>
|
||||||
post(`/client/${clientId}/plugin/${pluginName}/delete`)
|
post(`/client/${clientId}/plugin/${pluginId}/delete`)
|
||||||
export const updateClientPluginConfigWithRestart = (clientId: string, pluginName: string, ruleName: string, config: Record<string, string>, restart: boolean) =>
|
export const updateClientPluginConfigWithRestart = (clientId: string, pluginId: string, ruleName: string, config: Record<string, string>, restart: boolean) =>
|
||||||
post(`/client/${clientId}/plugin/${pluginName}/config`, { rule_name: ruleName, config, restart })
|
post(`/client/${clientId}/plugin/${pluginId}/config`, { rule_name: ruleName, config, restart })
|
||||||
|
|
||||||
// 插件管理
|
// 插件管理
|
||||||
export const getPlugins = () => get<PluginInfo[]>('/plugins')
|
export const getPlugins = () => get<PluginInfo[]>('/plugins')
|
||||||
@@ -71,6 +71,23 @@ export const updateJSPluginConfig = (name: string, config: Record<string, string
|
|||||||
export const setJSPluginEnabled = (name: string, enabled: boolean) =>
|
export const setJSPluginEnabled = (name: string, enabled: boolean) =>
|
||||||
post(`/js-plugin/${name}/${enabled ? 'enable' : 'disable'}`)
|
post(`/js-plugin/${name}/${enabled ? 'enable' : 'disable'}`)
|
||||||
|
|
||||||
|
// 插件 API 代理(通过 pluginID 调用插件自定义 API)
|
||||||
|
export const callPluginAPI = <T = any>(clientId: string, pluginId: string, method: string, route: string, body?: any) => {
|
||||||
|
const path = `/client/${clientId}/plugin-api/${pluginId}${route.startsWith('/') ? route : '/' + route}`
|
||||||
|
switch (method.toUpperCase()) {
|
||||||
|
case 'GET':
|
||||||
|
return get<T>(path)
|
||||||
|
case 'POST':
|
||||||
|
return post<T>(path, body)
|
||||||
|
case 'PUT':
|
||||||
|
return put<T>(path, body)
|
||||||
|
case 'DELETE':
|
||||||
|
return del<T>(path)
|
||||||
|
default:
|
||||||
|
return get<T>(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新管理
|
// 更新管理
|
||||||
export interface UpdateInfo {
|
export interface UpdateInfo {
|
||||||
available: boolean
|
available: boolean
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ export interface ProxyRule {
|
|||||||
|
|
||||||
// 客户端已安装的插件
|
// 客户端已安装的插件
|
||||||
export interface ClientPlugin {
|
export interface ClientPlugin {
|
||||||
|
id: string // 插件实例唯一 ID
|
||||||
name: string
|
name: string
|
||||||
version: string
|
version: string
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
running: boolean
|
running: boolean
|
||||||
config?: Record<string, string>
|
config?: Record<string, string>
|
||||||
|
remote_port?: number // 远程监听端口
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插件配置字段
|
// 插件配置字段
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ const handleStartPlugin = async (plugin: ClientPlugin) => {
|
|||||||
const rule = rules.value.find(r => r.type === plugin.name)
|
const rule = rules.value.find(r => r.type === plugin.name)
|
||||||
const ruleName = rule?.name || plugin.name
|
const ruleName = rule?.name || plugin.name
|
||||||
try {
|
try {
|
||||||
await startClientPlugin(clientId, plugin.name, ruleName)
|
await startClientPlugin(clientId, plugin.id, ruleName)
|
||||||
message.success(`已启动 ${plugin.name}`)
|
message.success(`已启动 ${plugin.name}`)
|
||||||
plugin.running = true
|
plugin.running = true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -292,7 +292,7 @@ const handleRestartPlugin = async (plugin: ClientPlugin) => {
|
|||||||
const rule = rules.value.find(r => r.type === plugin.name)
|
const rule = rules.value.find(r => r.type === plugin.name)
|
||||||
const ruleName = rule?.name || plugin.name
|
const ruleName = rule?.name || plugin.name
|
||||||
try {
|
try {
|
||||||
await restartClientPlugin(clientId, plugin.name, ruleName)
|
await restartClientPlugin(clientId, plugin.id, ruleName)
|
||||||
message.success(`已重启 ${plugin.name}`)
|
message.success(`已重启 ${plugin.name}`)
|
||||||
plugin.running = true
|
plugin.running = true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -305,7 +305,7 @@ const handleStopPlugin = async (plugin: ClientPlugin) => {
|
|||||||
const rule = rules.value.find(r => r.type === plugin.name)
|
const rule = rules.value.find(r => r.type === plugin.name)
|
||||||
const ruleName = rule?.name || plugin.name
|
const ruleName = rule?.name || plugin.name
|
||||||
try {
|
try {
|
||||||
await stopClientPlugin(clientId, plugin.name, ruleName)
|
await stopClientPlugin(clientId, plugin.id, ruleName)
|
||||||
message.success(`已停止 ${plugin.name}`)
|
message.success(`已停止 ${plugin.name}`)
|
||||||
plugin.running = false
|
plugin.running = false
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -316,7 +316,7 @@ const handleStopPlugin = async (plugin: ClientPlugin) => {
|
|||||||
const toggleClientPlugin = async (plugin: ClientPlugin) => {
|
const toggleClientPlugin = async (plugin: ClientPlugin) => {
|
||||||
const newEnabled = !plugin.enabled
|
const newEnabled = !plugin.enabled
|
||||||
const updatedPlugins = clientPlugins.value.map(p =>
|
const updatedPlugins = clientPlugins.value.map(p =>
|
||||||
p.name === plugin.name ? { ...p, enabled: newEnabled } : p
|
p.id === plugin.id ? { ...p, enabled: newEnabled } : p
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
await updateClient(clientId, {
|
await updateClient(clientId, {
|
||||||
@@ -377,7 +377,7 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
|
|||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
try {
|
try {
|
||||||
await deleteClientPlugin(clientId, plugin.name)
|
await deleteClientPlugin(clientId, plugin.id)
|
||||||
message.success(`已删除 ${plugin.name}`)
|
message.success(`已删除 ${plugin.name}`)
|
||||||
await loadClient()
|
await loadClient()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -596,7 +596,7 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="plugin in clientPlugins" :key="plugin.name">
|
<tr v-for="plugin in clientPlugins" :key="plugin.id">
|
||||||
<td>{{ plugin.name }}</td>
|
<td>{{ plugin.name }}</td>
|
||||||
<td>v{{ plugin.version }}</td>
|
<td>v{{ plugin.version }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in New Issue
Block a user