- go.mod: require git.zkcoi.com/zkcoi/meshray/core, replace => ../Meshray - internal/ctr/corebind: EnhancedBind + registry + adapter (wg-go integration isolated) - cmd/mr-wg migrated from old core/cmd/meshray-core - core/ removed; CHANGELOG updated; fix checkdb vet warning
482 lines
25 KiB
HTML
482 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MeshRay - 简单、高效异地组网</title>
|
|
|
|
<!-- Tailwind CSS -->
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
<!-- Vue 3 -->
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
|
|
<!-- Element Plus -->
|
|
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
|
|
<script src="https://unpkg.com/element-plus"></script>
|
|
|
|
<!-- Element Plus Icons -->
|
|
<script src="https://unpkg.com/@element-plus/icons-vue"></script>
|
|
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
}
|
|
|
|
.sidebar {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.logo {
|
|
color: white;
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
padding: 20px;
|
|
text-align: center;
|
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.menu-item {
|
|
color: rgba(255,255,255,0.8);
|
|
padding: 15px 20px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.menu-item:hover, .menu-item.active {
|
|
background: rgba(255,255,255,0.2);
|
|
color: white;
|
|
}
|
|
|
|
.main-content {
|
|
background: #f5f7fa;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
text-align: center;
|
|
padding: 30px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 36px;
|
|
font-weight: bold;
|
|
color: #667eea;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #909399;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
/* 登录页面样式 */
|
|
.login-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
|
|
.login-box {
|
|
background: white;
|
|
padding: 40px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
|
width: 400px;
|
|
}
|
|
|
|
.login-title {
|
|
text-align: center;
|
|
color: #667eea;
|
|
font-size: 32px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.login-subtitle {
|
|
text-align: center;
|
|
color: #909399;
|
|
font-size: 14px;
|
|
margin-bottom: 30px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<!-- 登录页面 -->
|
|
<div v-if="!isLoggedIn" class="login-container">
|
|
<div class="login-box">
|
|
<h1 class="login-title">🔺 MeshRay</h1>
|
|
<p class="login-subtitle">去中心化异地组网平台</p>
|
|
|
|
<el-form :model="loginForm" label-width="80px">
|
|
<el-form-item label="用户名">
|
|
<el-input v-model="loginForm.username" placeholder="请输入用户名" prefix-icon="User"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="密码">
|
|
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码" prefix-icon="Lock" @keyup.enter="handleLogin"></el-input>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" style="width: 100%" :loading="loggingIn" @click="handleLogin">登录</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主界面 -->
|
|
<el-container v-else>
|
|
<!-- 侧边栏 -->
|
|
<el-aside width="250px" class="sidebar">
|
|
<div class="logo">🔺 MeshRay</div>
|
|
<el-menu
|
|
:default-active="currentPage"
|
|
background-color="transparent"
|
|
text-color="rgba(255,255,255,0.8)"
|
|
active-text-color="#fff"
|
|
>
|
|
<el-menu-item index="dashboard" @click="currentPage = 'dashboard'">
|
|
<el-icon><DataBoard /></el-icon>
|
|
<span>仪表盘</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="networks" @click="currentPage = 'networks'">
|
|
<el-icon><Connection /></el-icon>
|
|
<span>组网管理</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="devices" @click="currentPage = 'devices'">
|
|
<el-icon><Cpu /></el-icon>
|
|
<span>设备管理</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="services" @click="currentPage = 'services'">
|
|
<el-icon><Setting /></el-icon>
|
|
<span>服务管理</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="settings" @click="currentPage = 'settings'">
|
|
<el-icon><Tools /></el-icon>
|
|
<span>系统设置</span>
|
|
</el-menu-item>
|
|
</el-menu>
|
|
</el-aside>
|
|
|
|
<!-- 主内容区 -->
|
|
<el-container>
|
|
<!-- 顶部导航栏 -->
|
|
<el-header style="background: white; border-bottom: 1px solid #e4e7ed; display: flex; justify-content: space-between; align-items: center; padding: 0 20px;">
|
|
<div style="font-size: 18px; font-weight: bold;">欢迎使用 MeshRay</div>
|
|
<el-button type="danger" size="small" @click="handleLogout">退出登录</el-button>
|
|
</el-header>
|
|
<el-main class="main-content">
|
|
<!-- 仪表盘页面 -->
|
|
<div v-if="currentPage === 'dashboard'">
|
|
<h2 class="text-2xl font-bold mb-6">仪表盘</h2>
|
|
|
|
<el-row :gutter="20">
|
|
<el-col :span="6">
|
|
<div class="card stat-card">
|
|
<div class="stat-number">{{ stats.networkCount }}</div>
|
|
<div class="stat-label">组网数量</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="card stat-card">
|
|
<div class="stat-number">{{ stats.deviceCount }}</div>
|
|
<div class="stat-label">设备数量</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="card stat-card">
|
|
<div class="stat-number">{{ stats.onlineDevices }}</div>
|
|
<div class="stat-label">在线设备</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="card stat-card">
|
|
<div class="stat-number">{{ stats.pendingApprovals }}</div>
|
|
<div class="stat-label">待审核</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<!-- 最近活动 -->
|
|
<div class="card mt-6 p-6">
|
|
<h3 class="text-lg font-bold mb-4">最近活动</h3>
|
|
<el-timeline>
|
|
<el-timeline-item timestamp="2026-03-20 10:00" type="primary">
|
|
系统启动成功
|
|
</el-timeline-item>
|
|
<el-timeline-item timestamp="2026-03-20 09:30" type="success">
|
|
组网 "生产网络" 创建成功
|
|
</el-timeline-item>
|
|
<el-timeline-item timestamp="2026-03-20 09:00" type="warning">
|
|
新设备申请加入 "测试网络"
|
|
</el-timeline-item>
|
|
</el-timeline>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 组网管理页面 -->
|
|
<div v-if="currentPage === 'networks'">
|
|
<h2 class="text-2xl font-bold mb-6">组网管理</h2>
|
|
|
|
<div class="card p-6">
|
|
<el-button type="primary" @click="showCreateNetworkDialog = true">
|
|
<el-icon><Plus /></el-icon>
|
|
创建组网
|
|
</el-button>
|
|
|
|
<el-table :data="networks" style="width: 100%; margin-top: 20px;">
|
|
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
|
<el-table-column prop="name" label="名称"></el-table-column>
|
|
<el-table-column prop="subnet_ipv4" label="子网"></el-table-column>
|
|
<el-table-column prop="mesh_mode" label="模式">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.mesh_mode === 'enhanced' ? 'success' : 'info'">
|
|
{{ row.mesh_mode === 'enhanced' ? '增强' : '原生' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="300">
|
|
<template #default="{ row }">
|
|
<el-button size="small" @click="viewNetwork(row)">详情</el-button>
|
|
<el-button size="small" type="success" @click="generateMeshSeed(row)">
|
|
MeshSeed
|
|
</el-button>
|
|
<el-button size="small" type="danger" @click="deleteNetwork(row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 设备管理页面 -->
|
|
<div v-if="currentPage === 'devices'">
|
|
<h2 class="text-2xl font-bold mb-6">设备管理</h2>
|
|
<div class="card p-6">
|
|
<el-button type="primary" @click="openCreateDevice">
|
|
<el-icon><Plus /></el-icon>
|
|
创建设备
|
|
</el-button>
|
|
<el-table :data="devices" style="width: 100%; margin-top: 20px;">
|
|
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
|
<el-table-column prop="name" label="名称"></el-table-column>
|
|
<el-table-column prop="network_id" label="网络ID" width="100"></el-table-column>
|
|
<el-table-column prop="virtual_ip" label="虚拟IP"></el-table-column>
|
|
<el-table-column prop="status" label="状态" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.status === 'online' ? 'success' : 'info'">
|
|
{{ row.status === 'online' ? '在线' : '离线' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="220">
|
|
<template #default="{ row }">
|
|
<el-button size="small" @click="downloadDeviceConfig(row)">配置</el-button>
|
|
<el-button size="small" type="danger" @click="deleteDevice(row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<div v-if="devices.length === 0" class="text-center text-gray-400 py-8">
|
|
暂无设备,点击「创建设备」开始
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 服务管理页面 -->
|
|
<div v-if="currentPage === 'services'">
|
|
<h2 class="text-2xl font-bold mb-6">服务管理</h2>
|
|
<div class="card p-6">
|
|
<el-button type="primary" @click="openCreateService">
|
|
<el-icon><Plus /></el-icon>
|
|
创建服务
|
|
</el-button>
|
|
<el-table :data="services" style="width: 100%; margin-top: 20px;">
|
|
<el-table-column prop="id" label="ID" width="200"></el-table-column>
|
|
<el-table-column prop="name" label="名称"></el-table-column>
|
|
<el-table-column prop="type" label="类型" width="100"></el-table-column>
|
|
<el-table-column prop="address" label="地址"></el-table-column>
|
|
<el-table-column prop="port" label="端口" width="80"></el-table-column>
|
|
<el-table-column label="操作" width="260">
|
|
<template #default="{ row }">
|
|
<el-button size="small" @click="editService(row)">编辑</el-button>
|
|
<el-button size="small" @click="testService(row)">测试</el-button>
|
|
<el-button size="small" type="danger" @click="deleteService(row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<div v-if="services.length === 0" class="text-center text-gray-400 py-8">
|
|
暂无服务
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 系统设置页面 -->
|
|
<div v-if="currentPage === 'settings'">
|
|
<h2 class="text-2xl font-bold mb-6">系统设置</h2>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<div class="card p-6">
|
|
<h3 class="text-lg font-bold mb-4">基础设置</h3>
|
|
<el-form label-width="100px">
|
|
<el-form-item label="监听端口">
|
|
<el-input-number v-model="settings.server_port" :min="1" :max="65535"></el-input-number>
|
|
</el-form-item>
|
|
<el-form-item label="日志级别">
|
|
<el-select v-model="settings.log_level" style="width: 100%">
|
|
<el-option label="Debug" value="debug"></el-option>
|
|
<el-option label="Info" value="info"></el-option>
|
|
<el-option label="Warn" value="warn"></el-option>
|
|
<el-option label="Error" value="error"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="主题">
|
|
<el-select v-model="settings.theme" style="width: 100%">
|
|
<el-option label="浅色" value="light"></el-option>
|
|
<el-option label="深色" value="dark"></el-option>
|
|
<el-option label="跟随系统" value="auto"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="语言">
|
|
<el-select v-model="settings.language" style="width: 100%">
|
|
<el-option label="简体中文" value="zh-CN"></el-option>
|
|
<el-option label="English" value="en-US"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="saveSettings">保存设置</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<div class="card p-6">
|
|
<h3 class="text-lg font-bold mb-4">安全</h3>
|
|
<el-form label-width="100px">
|
|
<el-form-item label="旧密码">
|
|
<el-input v-model="passwordForm.old_password" type="password" show-password></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="新密码">
|
|
<el-input v-model="passwordForm.new_password" type="password" show-password></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="确认新密码">
|
|
<el-input v-model="passwordForm.confirm" type="password" show-password></el-input>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="changePassword">修改密码</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
<div class="card p-6 mt-6">
|
|
<h3 class="text-lg font-bold mb-4">运行状态</h3>
|
|
<p class="mb-2">WireGuard 模式:<el-tag>{{ wgModeDisplay || wgMode || '未知' }}</el-tag></p>
|
|
<el-button type="warning" @click="restartCore">重启核心服务</el-button>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
|
|
<!-- 创建设备对话框 -->
|
|
<el-dialog v-model="showCreateDeviceDialog" title="创建设备" width="500px">
|
|
<el-form label-width="100px">
|
|
<el-form-item label="所属组网">
|
|
<el-select v-model="deviceForm.network_id" placeholder="请选择组网" style="width: 100%">
|
|
<el-option v-for="n in networks" :key="n.id" :label="n.name" :value="n.id"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="设备名称">
|
|
<el-input v-model="deviceForm.name" placeholder="请输入设备名称"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="虚拟IP">
|
|
<el-input v-model="deviceForm.virtual_ip" placeholder="留空则自动分配"></el-input>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="showCreateDeviceDialog = false">取消</el-button>
|
|
<el-button type="primary" @click="createDevice">创建</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 设备配置对话框 -->
|
|
<el-dialog v-model="showDeviceConfigDialog" title="设备 WireGuard 配置" width="600px">
|
|
<el-input type="textarea" :rows="12" v-model="deviceConfigText" readonly></el-input>
|
|
<template #footer>
|
|
<el-button type="primary" @click="() => { navigator.clipboard && navigator.clipboard.writeText(deviceConfigText); ElementPlus.ElMessage.success('已复制到剪贴板') }">复制</el-button>
|
|
<el-button @click="showDeviceConfigDialog = false">关闭</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 服务对话框 -->
|
|
<el-dialog v-model="showServiceDialog" :title="serviceDialogTitle" width="500px">
|
|
<el-form label-width="100px">
|
|
<el-form-item label="服务名称">
|
|
<el-input v-model="serviceForm.name" placeholder="请输入服务名称"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="类型">
|
|
<el-select v-model="serviceForm.type" style="width: 100%">
|
|
<el-option v-for="t in serviceTypes" :key="t.value" :label="t.label" :value="t.value"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="地址">
|
|
<el-input v-model="serviceForm.address" placeholder="例如 stun.example.com"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="端口">
|
|
<el-input-number v-model="serviceForm.port" :min="0" :max="65535"></el-input-number>
|
|
</el-form-item>
|
|
<el-form-item label="用户名">
|
|
<el-input v-model="serviceForm.auth_username" placeholder="可选"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="密码">
|
|
<el-input v-model="serviceForm.auth_password" type="password" show-password placeholder="可选,留空不修改"></el-input>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="showServiceDialog = false">取消</el-button>
|
|
<el-button type="primary" @click="saveService">保存</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</el-main>
|
|
</el-container>
|
|
</el-container>
|
|
|
|
<!-- 创建组网对话框 -->
|
|
<el-dialog v-model="showCreateNetworkDialog" title="创建组网" width="500px">
|
|
<el-form :model="newNetwork" label-width="100px">
|
|
<el-form-item label="组网名称">
|
|
<el-input v-model="newNetwork.name" placeholder="请输入组网名称"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="子网">
|
|
<el-input v-model="newNetwork.subnet_ipv4" placeholder="例如:10.0.0.0/24"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="监听端口">
|
|
<el-input-number v-model="newNetwork.listen_port" :min="1024" :max="65535" value="51820"></el-input-number>
|
|
</el-form-item>
|
|
<el-form-item label="组网模式">
|
|
<el-radio-group v-model="newNetwork.mesh_mode">
|
|
<el-radio label="native">原生模式</el-radio>
|
|
<el-radio label="enhanced">增强模式</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="showCreateNetworkDialog = false">取消</el-button>
|
|
<el-button type="primary" @click="createNetwork">创建</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
|
|
<script src="/static/js/app.js"></script>
|
|
</body>
|
|
</html>
|