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

This commit is contained in:
2026-03-19 20:49:23 +08:00
parent 8901581d0c
commit 6558d1acdb
4 changed files with 87 additions and 48 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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>