From 2aa4abb88e71ace2720b1f1d43d23b147ec82b16 Mon Sep 17 00:00:00 2001 From: Flik Date: Sat, 3 Jan 2026 02:51:47 +0800 Subject: [PATCH] 1 --- internal/server/db/interface.go | 3 +- internal/server/router/handler/client.go | 6 ++- internal/server/router/handler/interfaces.go | 1 + internal/server/tunnel/server.go | 14 +++++++ web/src/api/index.ts | 2 + web/src/types/index.ts | 1 + web/src/views/ClientView.vue | 40 ++++++++++++++++---- 7 files changed, 56 insertions(+), 11 deletions(-) diff --git a/internal/server/db/interface.go b/internal/server/db/interface.go index 6923f32..49bf303 100644 --- a/internal/server/db/interface.go +++ b/internal/server/db/interface.go @@ -7,7 +7,8 @@ type ClientPlugin struct { Name string `json:"name"` Version string `json:"version"` Enabled bool `json:"enabled"` - Config map[string]string `json:"config,omitempty"` // 插件配置 + Running bool `json:"running"` // 运行状态 + Config map[string]string `json:"config,omitempty"` // 插件配置 } // Client 客户端数据 diff --git a/internal/server/router/handler/client.go b/internal/server/router/handler/client.go index 285e834..3d861f5 100644 --- a/internal/server/router/handler/client.go +++ b/internal/server/router/handler/client.go @@ -299,14 +299,14 @@ func (h *ClientHandler) InstallPlugins(c *gin.Context) { // PluginAction 客户端插件操作 // @Summary 插件操作 -// @Description 对客户端插件执行操作(stop/restart/config/delete) +// @Description 对客户端插件执行操作(start/stop/restart/config/delete) // @Tags 客户端 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Param pluginName path string true "插件名称" -// @Param action path string true "操作类型" Enums(stop, restart, config, delete) +// @Param action path string true "操作类型" Enums(start, stop, restart, config, delete) // @Param request body dto.ClientPluginActionRequest false "操作参数" // @Success 200 {object} Response // @Failure 400 {object} Response @@ -325,6 +325,8 @@ func (h *ClientHandler) PluginAction(c *gin.Context) { var err error switch action { + case "start": + err = h.app.GetServer().StartClientPlugin(clientID, pluginName, req.RuleName) case "stop": err = h.app.GetServer().StopClientPlugin(clientID, pluginName, req.RuleName) case "restart": diff --git a/internal/server/router/handler/interfaces.go b/internal/server/router/handler/interfaces.go index d53d102..d298e66 100644 --- a/internal/server/router/handler/interfaces.go +++ b/internal/server/router/handler/interfaces.go @@ -36,6 +36,7 @@ type ServerInterface interface { SyncPluginConfigToClient(clientID string, pluginName string, config map[string]string) error InstallJSPluginToClient(clientID string, req JSPluginInstallRequest) error RestartClient(clientID string) error + StartClientPlugin(clientID, pluginName, ruleName string) error StopClientPlugin(clientID, pluginName, ruleName string) error RestartClientPlugin(clientID, pluginName, ruleName string) error UpdateClientPluginConfig(clientID, pluginName, ruleName string, config map[string]string, restart bool) error diff --git a/internal/server/tunnel/server.go b/internal/server/tunnel/server.go index 418859d..cd607d7 100644 --- a/internal/server/tunnel/server.go +++ b/internal/server/tunnel/server.go @@ -1231,6 +1231,20 @@ func (s *Server) RestartClient(clientID string) error { return nil } +// StartClientPlugin 启动客户端插件 +func (s *Server) StartClientPlugin(clientID, pluginName, ruleName string) error { + s.mu.RLock() + _, ok := s.clients[clientID] + s.mu.RUnlock() + + if !ok { + return fmt.Errorf("client %s not found or not online", clientID) + } + + // 重新发送安装请求来启动插件 + return s.reinstallJSPlugin(clientID, pluginName, ruleName) +} + // StopClientPlugin 停止客户端插件 func (s *Server) StopClientPlugin(clientID, pluginName, ruleName string) error { s.mu.RLock() diff --git a/web/src/api/index.ts b/web/src/api/index.ts index a244004..010b3b2 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -31,6 +31,8 @@ export const installPluginsToClient = (id: string, plugins: string[]) => export const getRuleSchemas = () => get('/rule-schemas') // 客户端插件控制 +export const startClientPlugin = (clientId: string, pluginName: string, ruleName: string) => + post(`/client/${clientId}/plugin/${pluginName}/start`, { rule_name: ruleName }) export const stopClientPlugin = (clientId: string, pluginName: string, ruleName: string) => post(`/client/${clientId}/plugin/${pluginName}/stop`, { rule_name: ruleName }) export const restartClientPlugin = (clientId: string, pluginName: string, ruleName: string) => diff --git a/web/src/types/index.ts b/web/src/types/index.ts index 1e61928..86e99c2 100644 --- a/web/src/types/index.ts +++ b/web/src/types/index.ts @@ -14,6 +14,7 @@ export interface ClientPlugin { name: string version: string enabled: boolean + running: boolean config?: Record } diff --git a/web/src/views/ClientView.vue b/web/src/views/ClientView.vue index 2211578..c30148d 100644 --- a/web/src/views/ClientView.vue +++ b/web/src/views/ClientView.vue @@ -9,12 +9,12 @@ import { import { ArrowBackOutline, CreateOutline, TrashOutline, PushOutline, PowerOutline, AddOutline, SaveOutline, CloseOutline, - SettingsOutline, StorefrontOutline, RefreshOutline, StopOutline + SettingsOutline, StorefrontOutline, RefreshOutline, StopOutline, PlayOutline } from '@vicons/ionicons5' import { getClient, updateClient, deleteClient, pushConfigToClient, disconnectClient, restartClient, getClientPluginConfig, updateClientPluginConfig, - getStorePlugins, installStorePlugin, getRuleSchemas, restartClientPlugin, stopClientPlugin, deleteClientPlugin + getStorePlugins, installStorePlugin, getRuleSchemas, startClientPlugin, restartClientPlugin, stopClientPlugin, deleteClientPlugin } from '../api' import type { ProxyRule, ClientPlugin, ConfigField, StorePluginInfo, RuleSchemasMap } from '../types' @@ -272,28 +272,43 @@ const handleRestartClient = () => { }) } +// 启动客户端插件 +const handleStartPlugin = async (plugin: ClientPlugin) => { + const rule = rules.value.find(r => r.type === plugin.name) + const ruleName = rule?.name || plugin.name + try { + await startClientPlugin(clientId, plugin.name, ruleName) + message.success(`已启动 ${plugin.name}`) + plugin.running = true + } catch (e: any) { + message.error(e.message || '启动失败') + } +} + // 重启客户端插件 const handleRestartPlugin = async (plugin: ClientPlugin) => { // 找到使用此插件的规则 const rule = rules.value.find(r => r.type === plugin.name) - const ruleName = rule?.name || '' + const ruleName = rule?.name || plugin.name try { await restartClientPlugin(clientId, plugin.name, ruleName) message.success(`已重启 ${plugin.name}`) + plugin.running = true } catch (e: any) { - message.error(e.response?.data || '重启失败') + message.error(e.message || '重启失败') } } // 停止客户端插件 const handleStopPlugin = async (plugin: ClientPlugin) => { const rule = rules.value.find(r => r.type === plugin.name) - const ruleName = rule?.name || '' + const ruleName = rule?.name || plugin.name try { await stopClientPlugin(clientId, plugin.name, ruleName) message.success(`已停止 ${plugin.name}`) + plugin.running = false } catch (e: any) { - message.error(e.response?.data || '停止失败') + message.error(e.message || '停止失败') } } @@ -571,6 +586,7 @@ const handleDeletePlugin = (plugin: ClientPlugin) => { 名称 版本 状态 + 启用 操作 @@ -578,6 +594,10 @@ const handleDeletePlugin = (plugin: ClientPlugin) => { {{ plugin.name }} v{{ plugin.version }} + + 运行中 + 已停止 + @@ -587,11 +607,15 @@ const handleDeletePlugin = (plugin: ClientPlugin) => { 配置 - + 重启 - + + + 启动 + + 停止