Files
zkcoi 59e3059246 rebuild: split core into separate repo; move wg-go integration to internal/ctr/corebind
- 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
2026-07-15 16:08:13 +08:00

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>