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"`
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 客户端数据

View File

@@ -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":

View File

@@ -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

View File

@@ -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()

View File

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

View File

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

View File

@@ -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) => {
<th>名称</th>
<th>版本</th>
<th>状态</th>
<th>启用</th>
<th>操作</th>
</tr>
</thead>
@@ -578,6 +594,10 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
<tr v-for="plugin in clientPlugins" :key="plugin.name">
<td>{{ plugin.name }}</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>
<n-switch :value="plugin.enabled" @update:value="toggleClientPlugin(plugin)" />
</td>
@@ -587,11 +607,15 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
<template #icon><n-icon><SettingsOutline /></n-icon></template>
配置
</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>
重启
</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>
停止
</n-button>