Refactor install command generation and update response structure
Some checks failed
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-frontend (push) Has been cancelled
Some checks failed
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-frontend (push) Has been cancelled
This commit is contained in:
@@ -3,7 +3,6 @@ package handler
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -11,41 +10,38 @@ import (
|
||||
"github.com/gotunnel/internal/server/db"
|
||||
)
|
||||
|
||||
// InstallHandler 安装处理器
|
||||
type InstallHandler struct {
|
||||
app AppInterface
|
||||
}
|
||||
|
||||
// NewInstallHandler 创建安装处理器
|
||||
func NewInstallHandler(app AppInterface) *InstallHandler {
|
||||
return &InstallHandler{app: app}
|
||||
}
|
||||
|
||||
// InstallCommandResponse 安装命令响应
|
||||
type InstallCommandResponse struct {
|
||||
Token string `json:"token"`
|
||||
Commands map[string]string `json:"commands"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
ServerAddr string `json:"server_addr"`
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
TunnelPort int `json:"tunnel_port"`
|
||||
}
|
||||
|
||||
// GenerateInstallCommand 生成安装命令
|
||||
// @Summary 生成客户端安装命令
|
||||
// GenerateInstallCommand creates a one-time install token and returns
|
||||
// the tunnel port so the frontend can build a host-aware command.
|
||||
//
|
||||
// @Summary Generate install command payload
|
||||
// @Tags install
|
||||
// @Produce json
|
||||
// @Success 200 {object} InstallCommandResponse
|
||||
// @Router /api/install/generate [post]
|
||||
func (h *InstallHandler) GenerateInstallCommand(c *gin.Context) {
|
||||
// 生成随机token
|
||||
tokenBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(tokenBytes); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成token失败"})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
|
||||
return
|
||||
}
|
||||
token := hex.EncodeToString(tokenBytes)
|
||||
|
||||
// 保存到数据库
|
||||
token := hex.EncodeToString(tokenBytes)
|
||||
now := time.Now().Unix()
|
||||
|
||||
installToken := &db.InstallToken{
|
||||
Token: token,
|
||||
CreatedAt: now,
|
||||
@@ -54,36 +50,18 @@ func (h *InstallHandler) GenerateInstallCommand(c *gin.Context) {
|
||||
|
||||
store, ok := h.app.GetClientStore().(db.InstallTokenStore)
|
||||
if !ok {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "存储不支持安装token"})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "install token store is not supported"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.CreateInstallToken(installToken); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存token失败"})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to persist token"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取服务器地址
|
||||
serverAddr := fmt.Sprintf("%s:%d", h.app.GetConfig().Server.BindAddr, h.app.GetServer().GetBindPort())
|
||||
if h.app.GetConfig().Server.BindAddr == "" || h.app.GetConfig().Server.BindAddr == "0.0.0.0" {
|
||||
serverAddr = fmt.Sprintf("your-server-ip:%d", h.app.GetServer().GetBindPort())
|
||||
}
|
||||
|
||||
// 生成安装命令
|
||||
expiresAt := now + 3600 // 1小时过期
|
||||
commands := map[string]string{
|
||||
"linux": fmt.Sprintf("curl -fsSL https://raw.githubusercontent.com/gotunnel/gotunnel/main/scripts/install.sh | bash -s -- -s %s -t %s",
|
||||
serverAddr, token),
|
||||
"macos": fmt.Sprintf("curl -fsSL https://raw.githubusercontent.com/gotunnel/gotunnel/main/scripts/install.sh | bash -s -- -s %s -t %s",
|
||||
serverAddr, token),
|
||||
"windows": fmt.Sprintf("powershell -c \"irm https://raw.githubusercontent.com/gotunnel/gotunnel/main/scripts/install.ps1 | iex; Install-GoTunnel -Server '%s' -Token '%s'\"",
|
||||
serverAddr, token),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, InstallCommandResponse{
|
||||
Token: token,
|
||||
Commands: commands,
|
||||
ExpiresAt: expiresAt,
|
||||
ServerAddr: serverAddr,
|
||||
ExpiresAt: now + 3600,
|
||||
TunnelPort: h.app.GetServer().GetBindPort(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ onUnmounted(() => {
|
||||
|
||||
<div class="app-main">
|
||||
<header class="app-topbar glass-card">
|
||||
<div>
|
||||
<div class="topbar-intro">
|
||||
<span class="topbar-label">Workspace</span>
|
||||
<h1>{{ navItems.find((item) => item.key === activeNav)?.label || '控制台' }}</h1>
|
||||
</div>
|
||||
@@ -375,6 +375,34 @@ onUnmounted(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
z-index: 30;
|
||||
padding: 18px 22px;
|
||||
border-radius: 22px;
|
||||
background:
|
||||
radial-gradient(circle at top right, var(--color-accent-glow), transparent 38%),
|
||||
linear-gradient(135deg, var(--glass-bg) 0%, var(--glass-bg-light) 100%);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.app-topbar::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: auto 18px -18px auto;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 999px;
|
||||
background: var(--color-accent-glow);
|
||||
opacity: 0.18;
|
||||
filter: blur(28px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.topbar-intro {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-topbar h1 {
|
||||
@@ -387,6 +415,9 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.topbar-icon-btn,
|
||||
@@ -401,6 +432,15 @@ onUnmounted(() => {
|
||||
background: var(--glass-bg-light);
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.topbar-icon-btn:hover,
|
||||
.profile-button:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(59, 130, 246, 0.28);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.topbar-icon-btn svg,
|
||||
@@ -412,6 +452,7 @@ onUnmounted(() => {
|
||||
|
||||
.menu-wrap {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.floating-menu {
|
||||
@@ -424,7 +465,9 @@ onUnmounted(() => {
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 10;
|
||||
backdrop-filter: var(--glass-blur-light);
|
||||
-webkit-backdrop-filter: var(--glass-blur-light);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.floating-menu--right {
|
||||
@@ -514,6 +557,7 @@ onUnmounted(() => {
|
||||
.app-topbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
|
||||
@@ -67,11 +67,6 @@ export interface LogStreamOptions {
|
||||
// 安装命令响应
|
||||
export interface InstallCommandResponse {
|
||||
token: string
|
||||
commands: {
|
||||
linux: string
|
||||
macos: string
|
||||
windows: string
|
||||
}
|
||||
expires_at: number
|
||||
server_addr: string
|
||||
tunnel_port: number
|
||||
}
|
||||
|
||||
@@ -17,6 +17,27 @@ const showInstallModal = ref(false)
|
||||
const installData = ref<InstallCommandResponse | null>(null)
|
||||
const generatingInstall = ref(false)
|
||||
const search = ref('')
|
||||
const installScriptUrl = 'https://raw.githubusercontent.com/gotunnel/gotunnel/main/scripts/install.sh'
|
||||
const installPs1Url = 'https://raw.githubusercontent.com/gotunnel/gotunnel/main/scripts/install.ps1'
|
||||
|
||||
const quoteShellArg = (value: string) => `'${value.replace(/'/g, `'\"'\"'`)}'`
|
||||
|
||||
const resolveTunnelHost = () => window.location.hostname || 'localhost'
|
||||
|
||||
const formatServerAddr = (host: string, port: number) => {
|
||||
const normalizedHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host
|
||||
return `${normalizedHost}:${port}`
|
||||
}
|
||||
|
||||
const buildInstallCommands = (data: InstallCommandResponse) => {
|
||||
const serverAddr = formatServerAddr(resolveTunnelHost(), data.tunnel_port)
|
||||
|
||||
return {
|
||||
linux: `bash <(curl -fsSL ${installScriptUrl}) -s ${quoteShellArg(serverAddr)} -t ${quoteShellArg(data.token)}`,
|
||||
macos: `bash <(curl -fsSL ${installScriptUrl}) -s ${quoteShellArg(serverAddr)} -t ${quoteShellArg(data.token)}`,
|
||||
windows: `powershell -c \"irm ${installPs1Url} | iex; Install-GoTunnel -Server '${serverAddr}' -Token '${data.token}'\"`,
|
||||
}
|
||||
}
|
||||
|
||||
const loadClients = async () => {
|
||||
loading.value = true
|
||||
@@ -67,6 +88,7 @@ const filteredClients = computed(() => {
|
||||
|
||||
const onlineClients = computed(() => clients.value.filter((client) => client.online).length)
|
||||
const offlineClients = computed(() => Math.max(clients.value.length - onlineClients.value, 0))
|
||||
const installCommands = computed(() => (installData.value ? buildInstallCommands(installData.value) : null))
|
||||
|
||||
onMounted(loadClients)
|
||||
</script>
|
||||
@@ -126,11 +148,11 @@ onMounted(loadClients)
|
||||
</SectionCard>
|
||||
|
||||
<GlassModal :show="showInstallModal" title="安装命令" width="760px" @close="showInstallModal = false">
|
||||
<div v-if="installData" class="install-grid">
|
||||
<div v-if="installCommands" class="install-grid">
|
||||
<article v-for="item in [
|
||||
{ label: 'Linux', value: installData.commands.linux },
|
||||
{ label: 'macOS', value: installData.commands.macos },
|
||||
{ label: 'Windows', value: installData.commands.windows },
|
||||
{ label: 'Linux', value: installCommands.linux },
|
||||
{ label: 'macOS', value: installCommands.macos },
|
||||
{ label: 'Windows', value: installCommands.windows },
|
||||
]" :key="item.label" class="install-card">
|
||||
<header>
|
||||
<strong>{{ item.label }}</strong>
|
||||
|
||||
Reference in New Issue
Block a user