feat: remove unused plugin version comparison and types, refactor proxy server to support authentication
- Deleted version comparison logic from `pkg/plugin/sign/version.go`. - Removed unused types and constants from `pkg/plugin/types.go`. - Updated `pkg/protocol/message.go` to remove plugin-related message types. - Enhanced `pkg/proxy/http.go` and `pkg/proxy/socks5.go` to include username/password authentication for HTTP and SOCKS5 proxies. - Modified `pkg/proxy/server.go` to pass authentication parameters to server constructors. - Added new API endpoint to generate installation commands with a token for clients. - Created database functions to manage installation tokens in `internal/server/db/install_token.go`. - Implemented the installation command generation logic in `internal/server/router/handler/install.go`. - Updated web frontend to support installation command generation and display in `web/src/views/ClientsView.vue`.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { get, post, put, del, getToken } from '../config/axios'
|
||||
import type { ClientConfig, ClientStatus, ClientDetail, ServerStatus, PluginInfo, StorePluginInfo, PluginConfigResponse, JSPlugin, RuleSchemasMap, LogEntry, LogStreamOptions, ConfigField } from '../types'
|
||||
import type { ClientConfig, ClientStatus, ClientDetail, ServerStatus, LogEntry, LogStreamOptions, InstallCommandResponse } from '../types'
|
||||
|
||||
// 重新导出 token 管理方法
|
||||
export { getToken, setToken, removeToken } from '../config/axios'
|
||||
@@ -24,80 +24,8 @@ export const reloadConfig = () => post('/config/reload')
|
||||
export const pushConfigToClient = (id: string) => post(`/client/${id}/push`)
|
||||
export const disconnectClient = (id: string) => post(`/client/${id}/disconnect`)
|
||||
export const restartClient = (id: string) => post(`/client/${id}/restart`)
|
||||
export const installPluginsToClient = (id: string, plugins: string[]) =>
|
||||
post(`/client/${id}/install-plugins`, { plugins })
|
||||
|
||||
// 规则配置模式
|
||||
export const getRuleSchemas = () => get<RuleSchemasMap>('/rule-schemas')
|
||||
|
||||
// 客户端插件控制(使用 pluginID)
|
||||
export const startClientPlugin = (clientId: string, pluginId: string, ruleName: string) =>
|
||||
post(`/client/${clientId}/plugin/${pluginId}/start`, { rule_name: ruleName })
|
||||
export const stopClientPlugin = (clientId: string, pluginId: string, ruleName: string) =>
|
||||
post(`/client/${clientId}/plugin/${pluginId}/stop`, { rule_name: ruleName })
|
||||
export const restartClientPlugin = (clientId: string, pluginId: string, ruleName: string) =>
|
||||
post(`/client/${clientId}/plugin/${pluginId}/restart`, { rule_name: ruleName })
|
||||
export const deleteClientPlugin = (clientId: string, pluginId: string) =>
|
||||
post(`/client/${clientId}/plugin/${pluginId}/delete`)
|
||||
export const updateClientPluginConfigWithRestart = (clientId: string, pluginId: string, ruleName: string, config: Record<string, string>, restart: boolean) =>
|
||||
post(`/client/${clientId}/plugin/${pluginId}/config`, { rule_name: ruleName, config, restart })
|
||||
|
||||
// 插件管理
|
||||
export const getPlugins = () => get<PluginInfo[]>('/plugins')
|
||||
export const enablePlugin = (name: string) => post(`/plugin/${name}/enable`)
|
||||
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,
|
||||
remotePort?: number,
|
||||
version?: string,
|
||||
configSchema?: ConfigField[],
|
||||
authEnabled?: boolean,
|
||||
authUsername?: string,
|
||||
authPassword?: string
|
||||
) =>
|
||||
post('/store/install', {
|
||||
plugin_name: pluginName,
|
||||
version: version || '',
|
||||
download_url: downloadUrl,
|
||||
signature_url: signatureUrl,
|
||||
client_id: clientId,
|
||||
remote_port: remotePort || 0,
|
||||
config_schema: configSchema || [],
|
||||
auth_enabled: authEnabled || false,
|
||||
auth_username: authUsername || '',
|
||||
auth_password: authPassword || ''
|
||||
})
|
||||
|
||||
// 客户端插件配置
|
||||
export const getClientPluginConfig = (clientId: string, pluginName: string) =>
|
||||
get<PluginConfigResponse>(`/client-plugin/${clientId}/${pluginName}/config`)
|
||||
export const updateClientPluginConfig = (clientId: string, pluginName: string, config: Record<string, string>) =>
|
||||
put(`/client-plugin/${clientId}/${pluginName}/config`, { config })
|
||||
|
||||
// JS 插件管理
|
||||
export const getJSPlugins = () => get<JSPlugin[]>('/js-plugins')
|
||||
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, 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) =>
|
||||
post(`/js-plugin/${name}/${enabled ? 'enable' : 'disable'}`)
|
||||
|
||||
// 插件 API 代理(通过 pluginID 调用插件自定义 API)
|
||||
export const callPluginAPI = <T = any>(clientId: string, pluginId: string, method: string, route: string, body?: any) => {
|
||||
const path = `/client/${clientId}/plugin-api/${pluginId}${route.startsWith('/') ? route : '/' + route}`
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET':
|
||||
// 更新管理
|
||||
return get<T>(path)
|
||||
case 'POST':
|
||||
return post<T>(path, body)
|
||||
@@ -265,3 +193,7 @@ export interface UpdateServerConfigRequest {
|
||||
|
||||
export const getServerConfig = () => get<ServerConfigResponse>('/config')
|
||||
export const updateServerConfig = (config: UpdateServerConfigRequest) => put('/config', config)
|
||||
|
||||
// 安装命令生成
|
||||
export const generateInstallCommand = (clientId: string) =>
|
||||
post<InstallCommandResponse>('/install/generate', { client_id: clientId })
|
||||
|
||||
@@ -6,43 +6,6 @@ export interface ProxyRule {
|
||||
remote_port: number
|
||||
type?: string
|
||||
enabled?: boolean
|
||||
plugin_config?: Record<string, string>
|
||||
plugin_managed?: boolean // 插件管理标记 - 由插件自动创建的规则
|
||||
}
|
||||
|
||||
// 客户端已安装的插件
|
||||
export interface ClientPlugin {
|
||||
id: string // 插件实例唯一 ID
|
||||
name: string
|
||||
version: string
|
||||
enabled: boolean
|
||||
running: boolean
|
||||
config?: Record<string, string>
|
||||
remote_port?: number // 远程监听端口
|
||||
}
|
||||
|
||||
// 插件配置字段
|
||||
export interface ConfigField {
|
||||
key: string
|
||||
label: string
|
||||
type: 'string' | 'number' | 'bool' | 'select' | 'password'
|
||||
default?: string
|
||||
required?: boolean
|
||||
options?: string[]
|
||||
description?: string
|
||||
}
|
||||
|
||||
// 规则表单模式
|
||||
export interface RuleSchema {
|
||||
needs_local_addr: boolean
|
||||
extra_fields?: ConfigField[]
|
||||
}
|
||||
|
||||
// 插件配置响应
|
||||
export interface PluginConfigResponse {
|
||||
plugin_name: string
|
||||
schema: ConfigField[]
|
||||
config: Record<string, string>
|
||||
}
|
||||
|
||||
// 客户端配置
|
||||
@@ -50,7 +13,6 @@ export interface ClientConfig {
|
||||
id: string
|
||||
nickname?: string
|
||||
rules: ProxyRule[]
|
||||
plugins?: ClientPlugin[]
|
||||
}
|
||||
|
||||
// 客户端状态
|
||||
@@ -70,7 +32,6 @@ export interface ClientDetail {
|
||||
id: string
|
||||
nickname?: string
|
||||
rules: ProxyRule[]
|
||||
plugins?: ClientPlugin[]
|
||||
online: boolean
|
||||
last_ping?: string
|
||||
remote_addr?: string
|
||||
@@ -88,64 +49,12 @@ export interface ServerStatus {
|
||||
client_count: number
|
||||
}
|
||||
|
||||
// 插件类型
|
||||
export const PluginType = {
|
||||
Proxy: 'proxy',
|
||||
App: 'app',
|
||||
Service: 'service',
|
||||
Tool: 'tool'
|
||||
} as const
|
||||
|
||||
export type PluginTypeValue = typeof PluginType[keyof typeof PluginType]
|
||||
|
||||
// 插件信息
|
||||
export interface PluginInfo {
|
||||
name: string
|
||||
version: string
|
||||
type: string
|
||||
description: string
|
||||
source: string
|
||||
icon?: string
|
||||
enabled: boolean
|
||||
rule_schema?: RuleSchema
|
||||
}
|
||||
|
||||
// 扩展商店插件信息
|
||||
export interface StorePluginInfo {
|
||||
name: string
|
||||
version: string
|
||||
type: string
|
||||
description: string
|
||||
author: string
|
||||
icon?: string
|
||||
download_url?: string
|
||||
signature_url?: string
|
||||
config_schema?: ConfigField[]
|
||||
}
|
||||
|
||||
// JS 插件信息
|
||||
export interface JSPlugin {
|
||||
name: string
|
||||
source: string
|
||||
signature?: string
|
||||
description: string
|
||||
author: string
|
||||
version?: string
|
||||
auto_push: string[]
|
||||
config: Record<string, string>
|
||||
auto_start: boolean
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
// 规则配置模式集合
|
||||
export type RuleSchemasMap = Record<string, RuleSchema>
|
||||
|
||||
// 日志条目
|
||||
export interface LogEntry {
|
||||
ts: number // Unix 时间戳 (毫秒)
|
||||
level: string // 日志级别: debug, info, warn, error
|
||||
msg: string // 日志消息
|
||||
src: string // 来源: client, plugin:<name>
|
||||
src: string // 来源: client
|
||||
}
|
||||
|
||||
// 日志流选项
|
||||
@@ -154,3 +63,15 @@ export interface LogStreamOptions {
|
||||
follow?: boolean // 是否持续推送
|
||||
level?: string // 日志级别过滤
|
||||
}
|
||||
|
||||
// 安装命令响应
|
||||
export interface InstallCommandResponse {
|
||||
token: string
|
||||
commands: {
|
||||
linux: string
|
||||
macos: string
|
||||
windows: string
|
||||
}
|
||||
expires_at: number
|
||||
server_addr: string
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getClients } from '../api'
|
||||
import type { ClientStatus } from '../types'
|
||||
import { getClients, generateInstallCommand } from '../api'
|
||||
import type { ClientStatus, InstallCommandResponse } from '../types'
|
||||
|
||||
const router = useRouter()
|
||||
const clients = ref<ClientStatus[]>([])
|
||||
const loading = ref(true)
|
||||
const showInstallModal = ref(false)
|
||||
const installData = ref<InstallCommandResponse | null>(null)
|
||||
const installClientId = ref('')
|
||||
const generatingInstall = ref(false)
|
||||
|
||||
const loadClients = async () => {
|
||||
loading.value = true
|
||||
@@ -26,6 +30,29 @@ const viewClient = (id: string) => {
|
||||
router.push(`/client/${id}`)
|
||||
}
|
||||
|
||||
const openInstallModal = async () => {
|
||||
installClientId.value = `client-${Date.now()}`
|
||||
generatingInstall.value = true
|
||||
try {
|
||||
const { data } = await generateInstallCommand(installClientId.value)
|
||||
installData.value = data
|
||||
showInstallModal.value = true
|
||||
} catch (e) {
|
||||
console.error('Failed to generate install command', e)
|
||||
} finally {
|
||||
generatingInstall.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const copyCommand = (cmd: string) => {
|
||||
navigator.clipboard.writeText(cmd)
|
||||
}
|
||||
|
||||
const closeInstallModal = () => {
|
||||
showInstallModal.value = false
|
||||
installData.value = null
|
||||
}
|
||||
|
||||
onMounted(loadClients)
|
||||
</script>
|
||||
|
||||
@@ -65,7 +92,12 @@ onMounted(loadClients)
|
||||
<div class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3>客户端列表</h3>
|
||||
<button class="glass-btn small" @click="loadClients">刷新</button>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="glass-btn small" @click="openInstallModal" :disabled="generatingInstall">
|
||||
{{ generatingInstall ? '生成中...' : '安装命令' }}
|
||||
</button>
|
||||
<button class="glass-btn small" @click="loadClients">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div v-if="loading" class="loading-state">加载中...</div>
|
||||
@@ -101,6 +133,42 @@ onMounted(loadClients)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Install Command Modal -->
|
||||
<div v-if="showInstallModal" class="modal-overlay" @click="closeInstallModal">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>客户端安装命令</h3>
|
||||
<button class="close-btn" @click="closeInstallModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body" v-if="installData">
|
||||
<p class="install-hint">选择您的操作系统,复制命令并在目标机器上执行:</p>
|
||||
<div class="install-section">
|
||||
<h4>Linux</h4>
|
||||
<div class="command-box">
|
||||
<code>{{ installData.commands.linux }}</code>
|
||||
<button class="copy-btn" @click="copyCommand(installData.commands.linux)">复制</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="install-section">
|
||||
<h4>macOS</h4>
|
||||
<div class="command-box">
|
||||
<code>{{ installData.commands.macos }}</code>
|
||||
<button class="copy-btn" @click="copyCommand(installData.commands.macos)">复制</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="install-section">
|
||||
<h4>Windows</h4>
|
||||
<div class="command-box">
|
||||
<code>{{ installData.commands.windows }}</code>
|
||||
<button class="copy-btn" @click="copyCommand(installData.commands.windows)">复制</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="token-info">客户端ID: <strong>{{ installClientId }}</strong></p>
|
||||
<p class="token-warning">⚠️ 此命令包含一次性token,使用后需重新生成</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -427,4 +495,117 @@ onMounted(loadClients)
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Install Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
max-width: 800px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.install-hint {
|
||||
margin-bottom: 20px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.install-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.install-section h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.command-box {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.command-box code {
|
||||
flex: 1;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: var(--glass-bg-hover);
|
||||
}
|
||||
|
||||
.token-info {
|
||||
margin-top: 20px;
|
||||
padding: 12px;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.token-warning {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user