1
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 2m1s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 53s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 2m41s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 2m17s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m35s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m5s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m10s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m54s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 2m56s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m30s

This commit is contained in:
2026-01-03 19:29:23 +08:00
parent f2720a3d15
commit ae6cb9d422
8 changed files with 133 additions and 23 deletions

View File

@@ -56,7 +56,25 @@ jobs:
with:
go-version: '1.24'
cache: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Build frontend
run: |
cd web
npm ci
npm run build
mkdir -p ../internal/server/app/dist
cp -r dist/* ../internal/server/app/dist/
cd ..
echo "Frontend build completed"
ls -la internal/server/app/dist/
- name: Build all platforms
run: |
mkdir -p dist

5
.gitignore vendored
View File

@@ -26,10 +26,11 @@ Thumbs.db
# 前端 node_modules
web/node_modules/
# 构建产物 (源码在 web/dist嵌入用的在 pkg/webserver/dist)
# 构建产物 (源码在 web/dist嵌入用的在 internal/server/app/dist)
web/dist/
pkg/webserver/dist/
**/dist/**
internal/server/app/dist/*
!internal/server/app/dist/.gitkeep
build/**
# 日志

View File

@@ -102,4 +102,11 @@ type StoreInstallRequest struct {
DownloadURL string `json:"download_url" binding:"required,url"`
SignatureURL string `json:"signature_url" binding:"required,url"`
ClientID string `json:"client_id" binding:"required"`
RemotePort int `json:"remote_port"`
}
// JSPluginPushRequest 推送 JS 插件到客户端请求
// @Description 推送 JS 插件到指定客户端
type JSPluginPushRequest struct {
RemotePort int `json:"remote_port"`
}

View File

@@ -158,10 +158,12 @@ func (h *JSPluginHandler) Delete(c *gin.Context) {
// @Summary 推送插件到客户端
// @Description 将 JS 插件推送到指定客户端
// @Tags JS插件
// @Accept json
// @Produce json
// @Security Bearer
// @Param name path string true "插件名称"
// @Param clientID path string true "客户端ID"
// @Param request body dto.JSPluginPushRequest false "推送配置"
// @Success 200 {object} Response
// @Failure 400 {object} Response
// @Failure 404 {object} Response
@@ -170,6 +172,10 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
pluginName := c.Param("name")
clientID := c.Param("clientID")
// 解析请求体(可选)
var pushReq dto.JSPluginPushRequest
c.ShouldBindJSON(&pushReq) // 忽略错误,允许空请求体
// 检查客户端是否在线
online, _, _ := h.app.GetServer().GetClientStatus(clientID)
if !online {
@@ -195,6 +201,7 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
Source: plugin.Source,
Signature: plugin.Signature,
RuleName: plugin.Name,
RemotePort: pushReq.RemotePort,
Config: plugin.Config,
AutoStart: plugin.AutoStart,
}
@@ -205,8 +212,9 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
}
Success(c, gin.H{
"status": "ok",
"plugin": pluginName,
"client": clientID,
"status": "ok",
"plugin": pluginName,
"client": clientID,
"remote_port": pushReq.RemotePort,
})
}

View File

@@ -131,6 +131,7 @@ func (h *StoreHandler) Install(c *gin.Context) {
Source: string(source),
Signature: string(signature),
RuleName: req.PluginName,
RemotePort: req.RemotePort,
AutoStart: true,
}

1
web/components.d.ts vendored
View File

@@ -12,6 +12,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
LogViewer: typeof import('./src/components/LogViewer.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@@ -49,8 +49,8 @@ export const disablePlugin = (name: string) => post(`/plugin/${name}/disable`)
// 扩展商店
export const getStorePlugins = () => get<{ plugins: StorePluginInfo[] }>('/store/plugins')
export const installStorePlugin = (pluginName: string, downloadUrl: string, signatureUrl: string, clientId: string) =>
post('/store/install', { plugin_name: pluginName, download_url: downloadUrl, signature_url: signatureUrl, client_id: clientId })
export const installStorePlugin = (pluginName: string, downloadUrl: string, signatureUrl: string, clientId: string, remotePort?: number) =>
post('/store/install', { plugin_name: pluginName, download_url: downloadUrl, signature_url: signatureUrl, client_id: clientId, remote_port: remotePort || 0 })
// 客户端插件配置
export const getClientPluginConfig = (clientId: string, pluginName: string) =>
@@ -64,8 +64,8 @@ export const createJSPlugin = (plugin: JSPlugin) => post('/js-plugins', plugin)
export const getJSPlugin = (name: string) => get<JSPlugin>(`/js-plugin/${name}`)
export const updateJSPlugin = (name: string, plugin: JSPlugin) => put(`/js-plugin/${name}`, plugin)
export const deleteJSPlugin = (name: string) => del(`/js-plugin/${name}`)
export const pushJSPluginToClient = (pluginName: string, clientId: string) =>
post(`/js-plugin/${pluginName}/push/${clientId}`)
export const pushJSPluginToClient = (pluginName: string, clientId: string, remotePort?: number) =>
post(`/js-plugin/${pluginName}/push/${clientId}`, { remote_port: remotePort || 0 })
export const updateJSPluginConfig = (name: string, config: Record<string, string>) =>
put(`/js-plugin/${name}/config`, { config })
export const setJSPluginEnabled = (name: string, enabled: boolean) =>

View File

@@ -4,7 +4,7 @@ import { useRouter } from 'vue-router'
import {
NCard, NButton, NSpace, NTag, NStatistic, NGrid, NGi,
NEmpty, NSpin, NIcon, NSwitch, NTabs, NTabPane, useMessage,
NSelect, NModal, NInput
NSelect, NModal, NInput, NInputNumber
} from 'naive-ui'
import { ArrowBackOutline, ExtensionPuzzleOutline, StorefrontOutline, CodeSlashOutline, SettingsOutline } from '@vicons/ionicons5'
import {
@@ -131,12 +131,34 @@ const loadClients = async () => {
}
}
const handlePushJSPlugin = async (pluginName: string, clientId: string) => {
// JS 插件推送相关
const showPushModal = ref(false)
const selectedJSPlugin = ref<JSPlugin | null>(null)
const pushClientId = ref('')
const pushRemotePort = ref<number | null>(8080)
const pushing = ref(false)
const openPushModal = (plugin: JSPlugin) => {
selectedJSPlugin.value = plugin
pushClientId.value = ''
pushRemotePort.value = 8080
showPushModal.value = true
}
const handlePushJSPlugin = async () => {
if (!selectedJSPlugin.value || !pushClientId.value) {
message.warning('请选择要推送到的客户端')
return
}
pushing.value = true
try {
await pushJSPluginToClient(pluginName, clientId)
message.success(`已推送 ${pluginName}${clientId}`)
} catch (e) {
message.error('推送失败')
await pushJSPluginToClient(selectedJSPlugin.value.name, pushClientId.value, pushRemotePort.value || 0)
message.success(`已推送 ${selectedJSPlugin.value.name}${pushClientId.value},监听端口: ${pushRemotePort.value || '未指定'}`)
showPushModal.value = false
} catch (e: any) {
message.error(e.response?.data || '推送失败')
} finally {
pushing.value = false
}
}
@@ -208,11 +230,13 @@ const toggleJSPlugin = async (plugin: JSPlugin) => {
const showInstallModal = ref(false)
const selectedStorePlugin = ref<StorePluginInfo | null>(null)
const selectedClientId = ref('')
const storePluginRemotePort = ref<number | null>(8080)
const installing = ref(false)
const openInstallModal = (plugin: StorePluginInfo) => {
selectedStorePlugin.value = plugin
selectedClientId.value = ''
storePluginRemotePort.value = 8080
showInstallModal.value = true
}
@@ -235,7 +259,8 @@ const handleInstallStorePlugin = async () => {
selectedStorePlugin.value.name,
selectedStorePlugin.value.download_url,
selectedStorePlugin.value.signature_url,
selectedClientId.value
selectedClientId.value,
storePluginRemotePort.value || 0
)
message.success(`已安装 ${selectedStorePlugin.value.name} 到客户端`)
showInstallModal.value = false
@@ -404,14 +429,14 @@ onMounted(() => {
<template #icon><n-icon><SettingsOutline /></n-icon></template>
配置
</n-button>
<n-select
<n-button
v-if="onlineClients.length > 0"
placeholder="推送到..."
size="small"
style="width: 140px;"
:options="onlineClients.map(c => ({ label: c.nickname || c.id, value: c.id }))"
@update:value="(v: string) => handlePushJSPlugin(plugin.name, v)"
/>
type="primary"
@click="openPushModal(plugin)"
>
推送到客户端
</n-button>
</n-space>
</template>
</n-card>
@@ -439,6 +464,16 @@ onMounted(() => {
placeholder="选择要安装到的客户端"
:options="onlineClients.map(c => ({ label: c.nickname || c.id, value: c.id }))"
/>
<div>
<p style="margin: 0 0 8px 0; color: #666; font-size: 13px;">远程端口服务端监听端口:</p>
<n-input-number
v-model:value="storePluginRemotePort"
:min="1"
:max="65535"
placeholder="输入端口号"
style="width: 100%;"
/>
</div>
</n-space>
<template #footer>
<n-space justify="end">
@@ -477,5 +512,44 @@ onMounted(() => {
</n-space>
</template>
</n-modal>
<!-- JS 插件推送模态框 -->
<n-modal v-model:show="showPushModal" preset="card" title="推送插件到客户端" style="width: 400px;">
<n-space vertical :size="16">
<div v-if="selectedJSPlugin">
<p style="margin: 0 0 8px 0;"><strong>插件:</strong> {{ selectedJSPlugin.name }}</p>
<p style="margin: 0; color: #666;">{{ selectedJSPlugin.description || '无描述' }}</p>
</div>
<n-select
v-model:value="pushClientId"
placeholder="选择要推送到的客户端"
:options="onlineClients.map(c => ({ label: c.nickname || c.id, value: c.id }))"
/>
<div>
<p style="margin: 0 0 8px 0; color: #666; font-size: 13px;">远程端口服务端监听端口:</p>
<n-input-number
v-model:value="pushRemotePort"
:min="1"
:max="65535"
placeholder="输入端口号"
style="width: 100%;"
/>
<p style="margin: 8px 0 0 0; color: #999; font-size: 12px;">用户可以通过 服务端IP:端口 访问此插件提供的服务</p>
</div>
</n-space>
<template #footer>
<n-space justify="end">
<n-button @click="showPushModal = false">取消</n-button>
<n-button
type="primary"
:loading="pushing"
:disabled="!pushClientId"
@click="handlePushJSPlugin"
>
推送
</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>