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
This commit is contained in:
2026-07-15 16:08:13 +08:00
parent 15dab96872
commit 59e3059246
39 changed files with 2350 additions and 4361 deletions
+450 -133
View File
@@ -12,130 +12,136 @@ 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;
// 可以在这里验证 token 是否有效
}
// 统计数据
// 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'
});
// API 基础 URL
const API_BASE = '/api/v1';
// 加载统计数据
const loadStats = async () => {
try {
const response = await fetch(`${API_BASE}/dashboard/stats`);
if (response.ok) {
const result = await response.json();
if (result.data) {
stats.networkCount = result.data.network_count || 0;
stats.deviceCount = result.data.device_count || 0;
stats.onlineDevices = result.data.online_devices || 0;
stats.pendingApprovals = result.data.pending_approvals || 0;
}
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 response = await fetch(`${API_BASE}/networks`);
if (response.ok) {
const result = await response.json();
networks.value = result.data || [];
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 response = await fetch(`${API_BASE}/networks`, {
const r = await apiRequest('/networks', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newNetwork)
});
if (response.ok) {
const result = await response.json();
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 {
const result = await response.json();
ElementPlus.ElMessage.error(result.error || '创建失败');
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: '确定'
}
{ confirmButtonText: '确定' }
);
};
// 生成 MeshSeed
const generateMeshSeed = (network) => {
ElementPlus.ElMessageBox.prompt(
'请输入 MeshSeed 有效期(小时)',
@@ -150,23 +156,12 @@ const app = createApp({
try {
const hours = parseInt(value);
const expiresAt = new Date(Date.now() + hours * 3600 * 1000).toISOString();
const response = await fetch(`${API_BASE}/networks/${network.id}/mesh-seed`, {
const r = await apiRequest(`/networks/${network.id}/mesh-seed`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
max_uses: 10,
expires_at: expiresAt,
ddns_enabled: false
})
body: JSON.stringify({ max_uses: 10, expires_at: expiresAt, ddns_enabled: false })
});
if (response.ok) {
const result = await response.json();
const seedUrl = result.data?.seed_url || result.data?.seed;
if (r.ok) {
const seedUrl = (r.data && (r.data.seed_url || r.data.seed)) || '';
if (seedUrl) {
ElementPlus.ElMessageBox.alert(
`MeshSeed 生成成功:<br><br>` +
@@ -174,15 +169,13 @@ const app = createApp({
`style="width: 100%; padding: 8px; margin-top: 10px; border: 1px solid #ddd;" ` +
`onclick="this.select()">`,
'MeshSeed',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '复制并关闭'
}
{ dangerouslyUseHTMLString: true, confirmButtonText: '复制并关闭' }
);
} else {
ElementPlus.ElMessage.success('MeshSeed 生成成功');
}
} else {
const result = await response.json();
ElementPlus.ElMessage.error(result.error || '生成失败');
ElementPlus.ElMessage.error(r.error || '生成失败');
}
} catch (error) {
console.error('生成 MeshSeed 失败:', error);
@@ -190,31 +183,21 @@ const app = createApp({
}
}).catch(() => {});
};
// 删除组网
const deleteNetwork = async (network) => {
try {
await ElementPlus.ElMessageBox.confirm(
`确定要删除组网 "${network.name}" 吗?此操作不可恢复。`,
'警告',
{
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}
{ confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }
);
const response = await fetch(`${API_BASE}/networks/${network.id}`, {
method: 'DELETE'
});
if (response.ok) {
const r = await apiRequest(`/networks/${network.id}`, { method: 'DELETE' });
if (r.ok) {
ElementPlus.ElMessage.success('删除成功');
await loadNetworks();
await loadStats();
} else {
const result = await response.json();
ElementPlus.ElMessage.error(result.error || '删除失败');
ElementPlus.ElMessage.error(r.error || '删除失败');
}
} catch (error) {
if (error !== 'cancel') {
@@ -223,50 +206,366 @@ const app = createApp({
}
}
};
// 组件挂载时加载数据
onMounted(() => {
if (isLoggedIn.value) {
loadStats();
loadNetworks();
// ===================== 设备管理 =====================
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 response = await fetch(`${API_BASE}/auth/login`, {
const r = await apiRequest('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: loginForm.username,
password: loginForm.password
})
});
const result = await response.json();
if (response.ok && result.data) {
// 保存 token
authToken = result.data.token || result.data.access_token;
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 loadStats();
await loadNetworks();
await loadAll();
} else {
ElementPlus.ElMessage.error(result.error || '登录失败');
ElementPlus.ElMessage.error(r.error || '登录失败');
}
} catch (error) {
console.error('登录错误:', error);
@@ -275,31 +574,20 @@ const app = createApp({
loggingIn.value = false;
}
};
// 处理退出
const handleLogout = () => {
ElementPlus.ElMessageBox.confirm(
'确定要退出登录吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
// 清除 token
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,
@@ -314,7 +602,36 @@ const app = createApp({
createNetwork,
viewNetwork,
generateMeshSeed,
deleteNetwork
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
};
}
});