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
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:
@@ -57,6 +57,24 @@ jobs:
|
|||||||
go-version: '1.24'
|
go-version: '1.24'
|
||||||
cache: true
|
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
|
- name: Build all platforms
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -26,10 +26,11 @@ Thumbs.db
|
|||||||
# 前端 node_modules
|
# 前端 node_modules
|
||||||
web/node_modules/
|
web/node_modules/
|
||||||
|
|
||||||
# 构建产物 (源码在 web/dist,嵌入用的在 pkg/webserver/dist)
|
# 构建产物 (源码在 web/dist,嵌入用的在 internal/server/app/dist)
|
||||||
web/dist/
|
web/dist/
|
||||||
pkg/webserver/dist/
|
pkg/webserver/dist/
|
||||||
**/dist/**
|
internal/server/app/dist/*
|
||||||
|
!internal/server/app/dist/.gitkeep
|
||||||
build/**
|
build/**
|
||||||
|
|
||||||
# 日志
|
# 日志
|
||||||
|
|||||||
@@ -102,4 +102,11 @@ type StoreInstallRequest struct {
|
|||||||
DownloadURL string `json:"download_url" binding:"required,url"`
|
DownloadURL string `json:"download_url" binding:"required,url"`
|
||||||
SignatureURL string `json:"signature_url" binding:"required,url"`
|
SignatureURL string `json:"signature_url" binding:"required,url"`
|
||||||
ClientID string `json:"client_id" binding:"required"`
|
ClientID string `json:"client_id" binding:"required"`
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSPluginPushRequest 推送 JS 插件到客户端请求
|
||||||
|
// @Description 推送 JS 插件到指定客户端
|
||||||
|
type JSPluginPushRequest struct {
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,10 +158,12 @@ func (h *JSPluginHandler) Delete(c *gin.Context) {
|
|||||||
// @Summary 推送插件到客户端
|
// @Summary 推送插件到客户端
|
||||||
// @Description 将 JS 插件推送到指定客户端
|
// @Description 将 JS 插件推送到指定客户端
|
||||||
// @Tags JS插件
|
// @Tags JS插件
|
||||||
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param name path string true "插件名称"
|
// @Param name path string true "插件名称"
|
||||||
// @Param clientID path string true "客户端ID"
|
// @Param clientID path string true "客户端ID"
|
||||||
|
// @Param request body dto.JSPluginPushRequest false "推送配置"
|
||||||
// @Success 200 {object} Response
|
// @Success 200 {object} Response
|
||||||
// @Failure 400 {object} Response
|
// @Failure 400 {object} Response
|
||||||
// @Failure 404 {object} Response
|
// @Failure 404 {object} Response
|
||||||
@@ -170,6 +172,10 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
|
|||||||
pluginName := c.Param("name")
|
pluginName := c.Param("name")
|
||||||
clientID := c.Param("clientID")
|
clientID := c.Param("clientID")
|
||||||
|
|
||||||
|
// 解析请求体(可选)
|
||||||
|
var pushReq dto.JSPluginPushRequest
|
||||||
|
c.ShouldBindJSON(&pushReq) // 忽略错误,允许空请求体
|
||||||
|
|
||||||
// 检查客户端是否在线
|
// 检查客户端是否在线
|
||||||
online, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
online, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||||
if !online {
|
if !online {
|
||||||
@@ -195,6 +201,7 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
|
|||||||
Source: plugin.Source,
|
Source: plugin.Source,
|
||||||
Signature: plugin.Signature,
|
Signature: plugin.Signature,
|
||||||
RuleName: plugin.Name,
|
RuleName: plugin.Name,
|
||||||
|
RemotePort: pushReq.RemotePort,
|
||||||
Config: plugin.Config,
|
Config: plugin.Config,
|
||||||
AutoStart: plugin.AutoStart,
|
AutoStart: plugin.AutoStart,
|
||||||
}
|
}
|
||||||
@@ -208,5 +215,6 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
"plugin": pluginName,
|
"plugin": pluginName,
|
||||||
"client": clientID,
|
"client": clientID,
|
||||||
|
"remote_port": pushReq.RemotePort,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ func (h *StoreHandler) Install(c *gin.Context) {
|
|||||||
Source: string(source),
|
Source: string(source),
|
||||||
Signature: string(signature),
|
Signature: string(signature),
|
||||||
RuleName: req.PluginName,
|
RuleName: req.PluginName,
|
||||||
|
RemotePort: req.RemotePort,
|
||||||
AutoStart: true,
|
AutoStart: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -12,6 +12,7 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||||
|
LogViewer: typeof import('./src/components/LogViewer.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ export const disablePlugin = (name: string) => post(`/plugin/${name}/disable`)
|
|||||||
|
|
||||||
// 扩展商店
|
// 扩展商店
|
||||||
export const getStorePlugins = () => get<{ plugins: StorePluginInfo[] }>('/store/plugins')
|
export const getStorePlugins = () => get<{ plugins: StorePluginInfo[] }>('/store/plugins')
|
||||||
export const installStorePlugin = (pluginName: string, downloadUrl: string, signatureUrl: string, clientId: string) =>
|
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 })
|
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) =>
|
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 getJSPlugin = (name: string) => get<JSPlugin>(`/js-plugin/${name}`)
|
||||||
export const updateJSPlugin = (name: string, plugin: JSPlugin) => put(`/js-plugin/${name}`, plugin)
|
export const updateJSPlugin = (name: string, plugin: JSPlugin) => put(`/js-plugin/${name}`, plugin)
|
||||||
export const deleteJSPlugin = (name: string) => del(`/js-plugin/${name}`)
|
export const deleteJSPlugin = (name: string) => del(`/js-plugin/${name}`)
|
||||||
export const pushJSPluginToClient = (pluginName: string, clientId: string) =>
|
export const pushJSPluginToClient = (pluginName: string, clientId: string, remotePort?: number) =>
|
||||||
post(`/js-plugin/${pluginName}/push/${clientId}`)
|
post(`/js-plugin/${pluginName}/push/${clientId}`, { remote_port: remotePort || 0 })
|
||||||
export const updateJSPluginConfig = (name: string, config: Record<string, string>) =>
|
export const updateJSPluginConfig = (name: string, config: Record<string, string>) =>
|
||||||
put(`/js-plugin/${name}/config`, { config })
|
put(`/js-plugin/${name}/config`, { config })
|
||||||
export const setJSPluginEnabled = (name: string, enabled: boolean) =>
|
export const setJSPluginEnabled = (name: string, enabled: boolean) =>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import {
|
import {
|
||||||
NCard, NButton, NSpace, NTag, NStatistic, NGrid, NGi,
|
NCard, NButton, NSpace, NTag, NStatistic, NGrid, NGi,
|
||||||
NEmpty, NSpin, NIcon, NSwitch, NTabs, NTabPane, useMessage,
|
NEmpty, NSpin, NIcon, NSwitch, NTabs, NTabPane, useMessage,
|
||||||
NSelect, NModal, NInput
|
NSelect, NModal, NInput, NInputNumber
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { ArrowBackOutline, ExtensionPuzzleOutline, StorefrontOutline, CodeSlashOutline, SettingsOutline } from '@vicons/ionicons5'
|
import { ArrowBackOutline, ExtensionPuzzleOutline, StorefrontOutline, CodeSlashOutline, SettingsOutline } from '@vicons/ionicons5'
|
||||||
import {
|
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 {
|
try {
|
||||||
await pushJSPluginToClient(pluginName, clientId)
|
await pushJSPluginToClient(selectedJSPlugin.value.name, pushClientId.value, pushRemotePort.value || 0)
|
||||||
message.success(`已推送 ${pluginName} 到 ${clientId}`)
|
message.success(`已推送 ${selectedJSPlugin.value.name} 到 ${pushClientId.value},监听端口: ${pushRemotePort.value || '未指定'}`)
|
||||||
} catch (e) {
|
showPushModal.value = false
|
||||||
message.error('推送失败')
|
} 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 showInstallModal = ref(false)
|
||||||
const selectedStorePlugin = ref<StorePluginInfo | null>(null)
|
const selectedStorePlugin = ref<StorePluginInfo | null>(null)
|
||||||
const selectedClientId = ref('')
|
const selectedClientId = ref('')
|
||||||
|
const storePluginRemotePort = ref<number | null>(8080)
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
|
|
||||||
const openInstallModal = (plugin: StorePluginInfo) => {
|
const openInstallModal = (plugin: StorePluginInfo) => {
|
||||||
selectedStorePlugin.value = plugin
|
selectedStorePlugin.value = plugin
|
||||||
selectedClientId.value = ''
|
selectedClientId.value = ''
|
||||||
|
storePluginRemotePort.value = 8080
|
||||||
showInstallModal.value = true
|
showInstallModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +259,8 @@ const handleInstallStorePlugin = async () => {
|
|||||||
selectedStorePlugin.value.name,
|
selectedStorePlugin.value.name,
|
||||||
selectedStorePlugin.value.download_url,
|
selectedStorePlugin.value.download_url,
|
||||||
selectedStorePlugin.value.signature_url,
|
selectedStorePlugin.value.signature_url,
|
||||||
selectedClientId.value
|
selectedClientId.value,
|
||||||
|
storePluginRemotePort.value || 0
|
||||||
)
|
)
|
||||||
message.success(`已安装 ${selectedStorePlugin.value.name} 到客户端`)
|
message.success(`已安装 ${selectedStorePlugin.value.name} 到客户端`)
|
||||||
showInstallModal.value = false
|
showInstallModal.value = false
|
||||||
@@ -404,14 +429,14 @@ onMounted(() => {
|
|||||||
<template #icon><n-icon><SettingsOutline /></n-icon></template>
|
<template #icon><n-icon><SettingsOutline /></n-icon></template>
|
||||||
配置
|
配置
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-select
|
<n-button
|
||||||
v-if="onlineClients.length > 0"
|
v-if="onlineClients.length > 0"
|
||||||
placeholder="推送到..."
|
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 140px;"
|
type="primary"
|
||||||
:options="onlineClients.map(c => ({ label: c.nickname || c.id, value: c.id }))"
|
@click="openPushModal(plugin)"
|
||||||
@update:value="(v: string) => handlePushJSPlugin(plugin.name, v)"
|
>
|
||||||
/>
|
推送到客户端
|
||||||
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-card>
|
</n-card>
|
||||||
@@ -439,6 +464,16 @@ onMounted(() => {
|
|||||||
placeholder="选择要安装到的客户端"
|
placeholder="选择要安装到的客户端"
|
||||||
:options="onlineClients.map(c => ({ label: c.nickname || c.id, value: c.id }))"
|
: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>
|
</n-space>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-space justify="end">
|
<n-space justify="end">
|
||||||
@@ -477,5 +512,44 @@ onMounted(() => {
|
|||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user