1
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 28s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m3s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 46s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m26s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m27s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m43s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m3s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m25s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m1s

This commit is contained in:
2026-01-03 02:51:47 +08:00
parent 183215f410
commit 2aa4abb88e
7 changed files with 56 additions and 11 deletions

View File

@@ -7,7 +7,8 @@ type ClientPlugin struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Config map[string]string `json:"config,omitempty"` // 插件配置 Running bool `json:"running"` // 运行状态
Config map[string]string `json:"config,omitempty"` // 插件配置
} }
// Client 客户端数据 // Client 客户端数据

View File

@@ -299,14 +299,14 @@ func (h *ClientHandler) InstallPlugins(c *gin.Context) {
// PluginAction 客户端插件操作 // PluginAction 客户端插件操作
// @Summary 插件操作 // @Summary 插件操作
// @Description 对客户端插件执行操作(stop/restart/config/delete) // @Description 对客户端插件执行操作(start/stop/restart/config/delete)
// @Tags 客户端 // @Tags 客户端
// @Accept json // @Accept json
// @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 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 "操作参数" // @Param request body dto.ClientPluginActionRequest false "操作参数"
// @Success 200 {object} Response // @Success 200 {object} Response
// @Failure 400 {object} Response // @Failure 400 {object} Response
@@ -325,6 +325,8 @@ func (h *ClientHandler) PluginAction(c *gin.Context) {
var err error var err error
switch action { switch action {
case "start":
err = h.app.GetServer().StartClientPlugin(clientID, pluginName, req.RuleName)
case "stop": case "stop":
err = h.app.GetServer().StopClientPlugin(clientID, pluginName, req.RuleName) err = h.app.GetServer().StopClientPlugin(clientID, pluginName, req.RuleName)
case "restart": case "restart":

View File

@@ -36,6 +36,7 @@ type ServerInterface interface {
SyncPluginConfigToClient(clientID string, pluginName string, config map[string]string) error SyncPluginConfigToClient(clientID string, pluginName string, config map[string]string) error
InstallJSPluginToClient(clientID string, req JSPluginInstallRequest) error InstallJSPluginToClient(clientID string, req JSPluginInstallRequest) error
RestartClient(clientID string) error RestartClient(clientID string) error
StartClientPlugin(clientID, pluginName, ruleName string) error
StopClientPlugin(clientID, pluginName, ruleName string) error StopClientPlugin(clientID, pluginName, ruleName string) error
RestartClientPlugin(clientID, pluginName, ruleName string) error RestartClientPlugin(clientID, pluginName, ruleName string) error
UpdateClientPluginConfig(clientID, pluginName, ruleName string, config map[string]string, restart bool) error UpdateClientPluginConfig(clientID, pluginName, ruleName string, config map[string]string, restart bool) error

View File

@@ -1231,6 +1231,20 @@ func (s *Server) RestartClient(clientID string) error {
return nil 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 停止客户端插件 // StopClientPlugin 停止客户端插件
func (s *Server) StopClientPlugin(clientID, pluginName, ruleName string) error { func (s *Server) StopClientPlugin(clientID, pluginName, ruleName string) error {
s.mu.RLock() s.mu.RLock()

View File

@@ -31,6 +31,8 @@ export const installPluginsToClient = (id: string, plugins: string[]) =>
export const getRuleSchemas = () => get<RuleSchemasMap>('/rule-schemas') export const getRuleSchemas = () => get<RuleSchemasMap>('/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) => export const stopClientPlugin = (clientId: string, pluginName: string, ruleName: string) =>
post(`/client/${clientId}/plugin/${pluginName}/stop`, { rule_name: ruleName }) post(`/client/${clientId}/plugin/${pluginName}/stop`, { rule_name: ruleName })
export const restartClientPlugin = (clientId: string, pluginName: string, ruleName: string) => export const restartClientPlugin = (clientId: string, pluginName: string, ruleName: string) =>

View File

@@ -14,6 +14,7 @@ export interface ClientPlugin {
name: string name: string
version: string version: string
enabled: boolean enabled: boolean
running: boolean
config?: Record<string, string> config?: Record<string, string>
} }

View File

@@ -9,12 +9,12 @@ import {
import { import {
ArrowBackOutline, CreateOutline, TrashOutline, ArrowBackOutline, CreateOutline, TrashOutline,
PushOutline, PowerOutline, AddOutline, SaveOutline, CloseOutline, PushOutline, PowerOutline, AddOutline, SaveOutline, CloseOutline,
SettingsOutline, StorefrontOutline, RefreshOutline, StopOutline SettingsOutline, StorefrontOutline, RefreshOutline, StopOutline, PlayOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { import {
getClient, updateClient, deleteClient, pushConfigToClient, disconnectClient, restartClient, getClient, updateClient, deleteClient, pushConfigToClient, disconnectClient, restartClient,
getClientPluginConfig, updateClientPluginConfig, getClientPluginConfig, updateClientPluginConfig,
getStorePlugins, installStorePlugin, getRuleSchemas, restartClientPlugin, stopClientPlugin, deleteClientPlugin getStorePlugins, installStorePlugin, getRuleSchemas, startClientPlugin, restartClientPlugin, stopClientPlugin, deleteClientPlugin
} from '../api' } from '../api'
import type { ProxyRule, ClientPlugin, ConfigField, StorePluginInfo, RuleSchemasMap } from '../types' 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 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 || '' const ruleName = rule?.name || plugin.name
try { try {
await restartClientPlugin(clientId, plugin.name, ruleName) await restartClientPlugin(clientId, plugin.name, ruleName)
message.success(`已重启 ${plugin.name}`) message.success(`已重启 ${plugin.name}`)
plugin.running = true
} catch (e: any) { } catch (e: any) {
message.error(e.response?.data || '重启失败') message.error(e.message || '重启失败')
} }
} }
// 停止客户端插件 // 停止客户端插件
const handleStopPlugin = async (plugin: ClientPlugin) => { 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 || '' const ruleName = rule?.name || plugin.name
try { try {
await stopClientPlugin(clientId, plugin.name, ruleName) await stopClientPlugin(clientId, plugin.name, ruleName)
message.success(`已停止 ${plugin.name}`) message.success(`已停止 ${plugin.name}`)
plugin.running = false
} catch (e: any) { } catch (e: any) {
message.error(e.response?.data || '停止失败') message.error(e.message || '停止失败')
} }
} }
@@ -571,6 +586,7 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
<th>名称</th> <th>名称</th>
<th>版本</th> <th>版本</th>
<th>状态</th> <th>状态</th>
<th>启用</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
</thead> </thead>
@@ -578,6 +594,10 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
<tr v-for="plugin in clientPlugins" :key="plugin.name"> <tr v-for="plugin in clientPlugins" :key="plugin.name">
<td>{{ plugin.name }}</td> <td>{{ plugin.name }}</td>
<td>v{{ plugin.version }}</td> <td>v{{ plugin.version }}</td>
<td>
<n-tag v-if="plugin.running" type="success" size="small">运行中</n-tag>
<n-tag v-else type="default" size="small">已停止</n-tag>
</td>
<td> <td>
<n-switch :value="plugin.enabled" @update:value="toggleClientPlugin(plugin)" /> <n-switch :value="plugin.enabled" @update:value="toggleClientPlugin(plugin)" />
</td> </td>
@@ -587,11 +607,15 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
<template #icon><n-icon><SettingsOutline /></n-icon></template> <template #icon><n-icon><SettingsOutline /></n-icon></template>
配置 配置
</n-button> </n-button>
<n-button v-if="online && plugin.enabled" size="small" quaternary type="info" @click="handleRestartPlugin(plugin)"> <n-button v-if="online && plugin.enabled && plugin.running" size="small" quaternary type="info" @click="handleRestartPlugin(plugin)">
<template #icon><n-icon><RefreshOutline /></n-icon></template> <template #icon><n-icon><RefreshOutline /></n-icon></template>
重启 重启
</n-button> </n-button>
<n-button v-if="online && plugin.enabled" size="small" quaternary type="warning" @click="handleStopPlugin(plugin)"> <n-button v-if="online && plugin.enabled && !plugin.running" size="small" quaternary type="success" @click="handleStartPlugin(plugin)">
<template #icon><n-icon><PlayOutline /></n-icon></template>
启动
</n-button>
<n-button v-if="online && plugin.enabled && plugin.running" size="small" quaternary type="warning" @click="handleStopPlugin(plugin)">
<template #icon><n-icon><StopOutline /></n-icon></template> <template #icon><n-icon><StopOutline /></n-icon></template>
停止 停止
</n-button> </n-button>