// MeshRay 前端应用 - 原生 Vue3 + Element Plus const { createApp, ref, reactive, onMounted } = Vue; // 注册所有 Element Plus 图标 const icons = {}; for (const [key, value] of Object.entries(ElementPlusIconsVue)) { icons[key] = value; } const app = createApp({ setup() { // 当前页面 const currentPage = ref('dashboard'); // 登录状态 const isLoggedIn = ref(false); const loggingIn = ref(false); // 登录表单 const loginForm = reactive({ username: '', password: '' }); // Token 存储 let authToken = localStorage.getItem('meshray_token') || ''; // 检查是否已登录 if (authToken) { isLoggedIn.value = true; } // API 基础 URL const API_BASE = '/api/v1'; // 统一请求封装:自动注入 Bearer Token,并兼容 {data}|{error}|纯文本 三种响应 const apiRequest = async (path, options = {}) => { const headers = {}; if (options.body) headers['Content-Type'] = 'application/json'; if (authToken) headers['Authorization'] = 'Bearer ' + authToken; let resp; try { resp = await fetch(API_BASE + path, { ...options, headers }); } catch (e) { return { ok: false, error: '网络请求失败:' + e.message }; } const ct = resp.headers.get('content-type') || ''; let result = {}; try { result = ct.includes('application/json') ? await resp.json() : { text: await resp.text() }; } catch (e) { /* 忽略解析错误 */ } return { ok: resp.ok, status: resp.status, data: result.data, error: result.error || result.message, text: result.text, raw: result }; }; // ===================== 统计数据 / 组网 ===================== const stats = reactive({ networkCount: 0, deviceCount: 0, onlineDevices: 0, pendingApprovals: 0 }); const networks = ref([]); const showCreateNetworkDialog = ref(false); const newNetwork = reactive({ name: '', subnet_ipv4: '10.0.0.0/24', listen_port: 51820, mesh_mode: 'enhanced' }); const loadStats = async () => { try { const r = await apiRequest('/dashboard/stats'); if (r.ok && r.data) { stats.networkCount = r.data.total_networks || 0; stats.deviceCount = r.data.total_devices || 0; stats.onlineDevices = r.data.online_devices || 0; stats.pendingApprovals = r.data.pending_approvals || 0; } else if (!r.ok && r.status !== 401) { console.error('加载统计数据失败:', r.error); } } catch (error) { console.error('加载统计数据失败:', error); } }; const loadNetworks = async () => { try { const r = await apiRequest('/networks'); if (r.ok) { networks.value = r.data || []; } else if (r.status !== 401) { console.error('加载组网列表失败:', r.error); } } catch (error) { console.error('加载组网列表失败:', error); } }; const createNetwork = async () => { try { const r = await apiRequest('/networks', { method: 'POST', body: JSON.stringify(newNetwork) }); if (r.ok) { ElementPlus.ElMessage.success('组网创建成功'); showCreateNetworkDialog.value = false; newNetwork.name = ''; newNetwork.subnet_ipv4 = '10.0.0.0/24'; newNetwork.listen_port = 51820; await loadNetworks(); await loadStats(); } else { ElementPlus.ElMessage.error(r.error || '创建失败'); } } catch (error) { console.error('创建组网失败:', error); ElementPlus.ElMessage.error('创建失败'); } }; const viewNetwork = (network) => { ElementPlus.ElMessageBox.alert( `组网名称:${network.name}\n` + `子网:${network.subnet_ipv4}\n` + `模式:${network.mesh_mode === 'enhanced' ? '增强' : '原生'}`, '组网详情', { confirmButtonText: '确定' } ); }; const generateMeshSeed = (network) => { ElementPlus.ElMessageBox.prompt( '请输入 MeshSeed 有效期(小时)', '生成 MeshSeed', { confirmButtonText: '生成', cancelButtonText: '取消', inputPattern: /^\d+$/, inputErrorMessage: '请输入有效的数字' } ).then(async ({ value }) => { try { const hours = parseInt(value); const expiresAt = new Date(Date.now() + hours * 3600 * 1000).toISOString(); const r = await apiRequest(`/networks/${network.id}/mesh-seed`, { method: 'POST', body: JSON.stringify({ max_uses: 10, expires_at: expiresAt, ddns_enabled: false }) }); if (r.ok) { const seedUrl = (r.data && (r.data.seed_url || r.data.seed)) || ''; if (seedUrl) { ElementPlus.ElMessageBox.alert( `MeshSeed 生成成功:

` + ``, 'MeshSeed', { dangerouslyUseHTMLString: true, confirmButtonText: '复制并关闭' } ); } else { ElementPlus.ElMessage.success('MeshSeed 生成成功'); } } else { ElementPlus.ElMessage.error(r.error || '生成失败'); } } catch (error) { console.error('生成 MeshSeed 失败:', error); ElementPlus.ElMessage.error('生成失败'); } }).catch(() => {}); }; const deleteNetwork = async (network) => { try { await ElementPlus.ElMessageBox.confirm( `确定要删除组网 "${network.name}" 吗?此操作不可恢复。`, '警告', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' } ); const r = await apiRequest(`/networks/${network.id}`, { method: 'DELETE' }); if (r.ok) { ElementPlus.ElMessage.success('删除成功'); await loadNetworks(); await loadStats(); } else { ElementPlus.ElMessage.error(r.error || '删除失败'); } } catch (error) { if (error !== 'cancel') { console.error('删除组网失败:', error); ElementPlus.ElMessage.error('删除失败'); } } }; // ===================== 设备管理 ===================== const devices = ref([]); const showCreateDeviceDialog = ref(false); const deviceForm = reactive({ network_id: null, name: '', virtual_ip: '' }); const showDeviceConfigDialog = ref(false); const deviceConfigText = ref(''); const loadDevices = async () => { try { const r = await apiRequest('/devices'); if (r.ok) { devices.value = r.data || []; } else if (r.status !== 401) { console.error('加载设备列表失败:', r.error); } } catch (error) { console.error('加载设备列表失败:', error); } }; const openCreateDevice = () => { deviceForm.network_id = networks.value.length ? networks.value[0].id : null; deviceForm.name = ''; deviceForm.virtual_ip = ''; showCreateDeviceDialog.value = true; }; const createDevice = async () => { if (!deviceForm.network_id) { ElementPlus.ElMessage.warning('请先选择所属组网'); return; } try { const r = await apiRequest(`/devices?network_id=${deviceForm.network_id}`, { method: 'POST', body: JSON.stringify({ network_id: deviceForm.network_id, name: deviceForm.name, virtual_ip: deviceForm.virtual_ip }) }); if (r.ok) { const cfg = (r.data && r.data.wireguard_config) || (r.data && r.data.config_text) || ''; if (cfg) { deviceConfigText.value = cfg; showDeviceConfigDialog.value = true; } showCreateDeviceDialog.value = false; ElementPlus.ElMessage.success('设备创建成功'); await loadDevices(); await loadStats(); } else { ElementPlus.ElMessage.error(r.error || '创建失败'); } } catch (error) { console.error('创建设备失败:', error); ElementPlus.ElMessage.error('创建失败'); } }; const downloadDeviceConfig = async (device) => { try { const r = await apiRequest(`/devices/${device.id}/config`); if (!r.ok) { ElementPlus.ElMessage.error(r.error || '获取配置失败'); return; } const text = r.text || (r.raw && r.raw.text) || ''; const blob = new Blob([text], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${device.name || 'device'}-wg.conf`; a.click(); URL.revokeObjectURL(url); } catch (error) { console.error('下载设备配置失败:', error); ElementPlus.ElMessage.error('下载失败'); } }; const deleteDevice = async (device) => { try { await ElementPlus.ElMessageBox.confirm( `确定要删除设备 "${device.name}" 吗?`, '警告', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' } ); const r = await apiRequest(`/devices/${device.id}`, { method: 'DELETE' }); if (r.ok) { ElementPlus.ElMessage.success('删除成功'); await loadDevices(); await loadStats(); } else { ElementPlus.ElMessage.error(r.error || '删除失败'); } } catch (error) { if (error !== 'cancel') { console.error('删除设备失败:', error); ElementPlus.ElMessage.error('删除失败'); } } }; // ===================== 服务管理 ===================== const services = ref([]); const showServiceDialog = ref(false); const serviceDialogTitle = ref('创建服务'); const serviceForm = reactive({ id: '', name: '', type: 'turn', address: '', port: null, auth_username: '', auth_password: '' }); const serviceTypes = [ { value: 'tun', label: 'TUN 隧道' }, { value: 'turn', label: 'TURN 服务' }, { value: 'ddns', label: 'DDNS 服务' } ]; const loadServices = async () => { try { const r = await apiRequest('/services'); if (r.ok) { services.value = r.data || []; } else if (r.status !== 401) { console.error('加载服务列表失败:', r.error); } } catch (error) { console.error('加载服务列表失败:', error); } }; const openCreateService = () => { serviceDialogTitle.value = '创建服务'; serviceForm.id = ''; serviceForm.name = ''; serviceForm.type = 'turn'; serviceForm.address = ''; serviceForm.port = null; serviceForm.auth_username = ''; serviceForm.auth_password = ''; showServiceDialog.value = true; }; const editService = (svc) => { serviceDialogTitle.value = '编辑服务'; serviceForm.id = svc.id; serviceForm.name = svc.name; serviceForm.type = svc.type; serviceForm.address = svc.address; serviceForm.port = svc.port; serviceForm.auth_username = svc.username || ''; serviceForm.auth_password = ''; showServiceDialog.value = true; }; const saveService = async () => { const payload = { name: serviceForm.name, type: serviceForm.type, address: serviceForm.address, port: serviceForm.port }; if (serviceForm.auth_username) payload.username = serviceForm.auth_username; if (serviceForm.auth_password) payload.password = serviceForm.auth_password; const isEdit = !!serviceForm.id; const r = isEdit ? await apiRequest(`/services/${serviceForm.id}`, { method: 'PUT', body: JSON.stringify(payload) }) : await apiRequest('/services', { method: 'POST', body: JSON.stringify(payload) }); if (r.ok) { ElementPlus.ElMessage.success(isEdit ? '更新成功' : '创建成功'); showServiceDialog.value = false; await loadServices(); } else { ElementPlus.ElMessage.error(r.error || '操作失败'); } }; const deleteService = async (svc) => { try { await ElementPlus.ElMessageBox.confirm( `确定要删除服务 "${svc.name}" 吗?`, '警告', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' } ); const r = await apiRequest(`/services/${svc.id}`, { method: 'DELETE' }); if (r.ok) { ElementPlus.ElMessage.success('删除成功'); await loadServices(); } else { ElementPlus.ElMessage.error(r.error || '删除失败'); } } catch (error) { if (error !== 'cancel') { console.error('删除服务失败:', error); ElementPlus.ElMessage.error('删除失败'); } } }; const testService = async (svc) => { const r = await apiRequest(`/services/${svc.id}/test`, { method: 'POST' }); if (r.ok) { const status = (r.data && r.data.status) || 'unknown'; ElementPlus.ElMessage.info(`连通性状态:${status}`); } else { ElementPlus.ElMessage.error(r.error || '测试失败'); } }; // ===================== 系统设置 ===================== const settings = reactive({ server_port: 51820, log_level: 'info', theme: 'light', language: 'zh-CN', max_backups: 7, max_age: 30 }); const wgMode = ref(''); const wgModeDisplay = ref(''); const passwordForm = reactive({ old_password: '', new_password: '', confirm: '' }); const loadSettings = async () => { try { const r = await apiRequest('/settings'); if (r.ok && r.data) { Object.assign(settings, r.data); } } catch (error) { console.error('加载系统设置失败:', error); } }; const loadWGMode = async () => { try { const r = await apiRequest('/system-config/wg-mode'); if (r.ok && r.data) { wgMode.value = r.data.actual_mode || r.data.wg_mode || ''; wgModeDisplay.value = r.data.wg_mode_display || wgMode.value; } } catch (error) { console.error('加载 WG 模式失败:', error); } }; const saveSettings = async () => { const r = await apiRequest('/settings', { method: 'PUT', body: JSON.stringify({ server_port: settings.server_port, log_level: settings.log_level, theme: settings.theme, language: settings.language, max_backups: settings.max_backups, max_age: settings.max_age }) }); if (r.ok) { ElementPlus.ElMessage.success('设置已保存'); } else { ElementPlus.ElMessage.error(r.error || '保存失败'); } }; const changePassword = async () => { if (!passwordForm.old_password || !passwordForm.new_password) { ElementPlus.ElMessage.warning('请输入旧密码和新密码'); return; } if (passwordForm.new_password !== passwordForm.confirm) { ElementPlus.ElMessage.warning('两次输入的新密码不一致'); return; } const r = await apiRequest('/system/change-password', { method: 'POST', body: JSON.stringify({ old_password: passwordForm.old_password, new_password: passwordForm.new_password }) }); if (r.ok) { ElementPlus.ElMessage.success('密码修改成功'); passwordForm.old_password = ''; passwordForm.new_password = ''; passwordForm.confirm = ''; } else { ElementPlus.ElMessage.error(r.error || '修改失败'); } }; const restartCore = async () => { try { await ElementPlus.ElMessageBox.confirm( '确定要重启核心服务吗?重启过程约需数秒。', '重启核心', { confirmButtonText: '重启', cancelButtonText: '取消', type: 'warning' } ); const r = await apiRequest('/system/restart-core', { method: 'POST', body: JSON.stringify({ graceful: true }) }); if (r.ok) { ElementPlus.ElMessage.success('核心服务正在重启...'); } else { ElementPlus.ElMessage.error(r.error || '重启失败'); } } catch (error) { if (error !== 'cancel') { console.error('重启核心失败:', error); ElementPlus.ElMessage.error('重启失败'); } } }; // ===================== 登录 / 登出 ===================== const loadAll = async () => { await Promise.all([ loadStats(), loadNetworks(), loadDevices(), loadServices(), loadSettings(), loadWGMode() ]); }; onMounted(() => { if (isLoggedIn.value) loadAll(); }); const handleLogin = async () => { if (!loginForm.username || !loginForm.password) { ElementPlus.ElMessage.warning('请输入用户名和密码'); return; } loggingIn.value = true; try { const r = await apiRequest('/auth/login', { method: 'POST', body: JSON.stringify({ username: loginForm.username, password: loginForm.password }) }); if (r.ok && r.data) { authToken = r.data.token || r.data.access_token; localStorage.setItem('meshray_token', authToken); isLoggedIn.value = true; ElementPlus.ElMessage.success('登录成功'); await loadAll(); } else { ElementPlus.ElMessage.error(r.error || '登录失败'); } } catch (error) { console.error('登录错误:', error); ElementPlus.ElMessage.error('登录失败,请检查网络连接'); } finally { loggingIn.value = false; } }; const handleLogout = () => { ElementPlus.ElMessageBox.confirm('确定要退出登录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { localStorage.removeItem('meshray_token'); authToken = ''; isLoggedIn.value = false; loginForm.username = ''; loginForm.password = ''; ElementPlus.ElMessage.success('已退出登录'); }).catch(() => {}); }; return { currentPage, isLoggedIn, loggingIn, loginForm, stats, networks, showCreateNetworkDialog, newNetwork, handleLogin, handleLogout, createNetwork, viewNetwork, generateMeshSeed, deleteNetwork, // devices devices, showCreateDeviceDialog, deviceForm, showDeviceConfigDialog, deviceConfigText, openCreateDevice, createDevice, downloadDeviceConfig, deleteDevice, // services services, showServiceDialog, serviceDialogTitle, serviceForm, serviceTypes, openCreateService, editService, saveService, deleteService, testService, // settings settings, wgMode, wgModeDisplay, passwordForm, saveSettings, changePassword, restartCore }; } }); // 使用 Element Plus app.use(ElementPlus); // 注册所有图标 for (const [key, component] of Object.entries(icons)) { app.component(key, component); } // 挂载应用 app.mount('#app');