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'
|
||||
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
5
.gitignore
vendored
@@ -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/**
|
||||
|
||||
# 日志
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -208,5 +215,6 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
|
||||
"status": "ok",
|
||||
"plugin": pluginName,
|
||||
"client": clientID,
|
||||
"remote_port": pushReq.RemotePort,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
1
web/components.d.ts
vendored
@@ -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']
|
||||
}
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user