diff --git a/src/api/admin/kvmService.js b/src/api/admin/kvmService.js index b524627..abe1bee 100644 --- a/src/api/admin/kvmService.js +++ b/src/api/admin/kvmService.js @@ -126,7 +126,7 @@ export const deleteRemoteHostGroup = (params) => { /** * ================================ - * 主控服务接口 - 远程宿主机管理 + * 主控服务接口 - 宿主机管理 * ================================ */ @@ -139,3 +139,427 @@ export const getRemoteHostList = (params) => { export const getRemoteHostDetail = (params) => { return http2.get('/api/v1/admin/server/host_service/point/host/detail', { params }) } + +/** 获取宿主机指标数据 */ +export const getRemoteHostMetrics = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/host/metrics', { params }) +} + +/** 新增宿主机 */ +export const addRemoteHost = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/host/add', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改宿主机 */ +export const updateRemoteHost = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/host/update', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除宿主机 */ +export const deleteRemoteHost = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/host/delete', { params }) +} + +/** + * ================================ + * 主控服务接口 - 镜像管理 + * ================================ + */ + +/** 获取镜像列表 */ +export const getImageList = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/image/list', { params }) +} + +/** 获取镜像详情 */ +export const getImageDetail = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/image/detail', { params }) +} + +/** 获取镜像在指定宿主机上的状态 */ +export const getImageHostStatus = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/image/host_status', { params }) +} + +/** 创建镜像 */ +export const createImage = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/image/create', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改镜像 */ +export const updateImage = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/image/update', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除镜像 */ +export const deleteImage = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/image/delete', { params }) +} + +/** 重新下载镜像 */ +export const reloadImage = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/image/reload', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 向宿主机同步镜像 */ +export const syncImageToHost = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/image/sync', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 指定宿主机重新下载指定镜像 */ +export const reloadImageOnHost = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/image/reload_host', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** + * ================================ + * 主控服务接口 - 网络管理 + * ================================ + */ + +/** 获取网络列表 */ +export const getNetworkList = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/network/list', { params }) +} + +/** 获取网络详情 */ +export const getNetworkDetail = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/network/detail', { params }) +} + +/** 创建网络 */ +export const createNetwork = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/network/create', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改网络 */ +export const updateNetwork = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/network/update', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除网络 */ +export const deleteNetwork = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/network/delete', { params }) +} + +/** + * ================================ + * 主控服务接口 - 数据卷管理 + * ================================ + */ + +/** 获取数据卷列表 */ +export const getVolumeList = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/volume/list', { params }) +} + +/** 获取数据卷详情 */ +export const getVolumeDetail = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/volume/detail', { params }) +} + +/** 创建数据卷 */ +export const createVolume = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/volume/create', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 调整数据卷大小 */ +export const resizeVolume = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/volume/resize', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 挂载卷到虚拟机 */ +export const mountVolume = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/volume/mount', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 卸载卷 */ +export const unmountVolume = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/volume/unmount', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 迁移卷 */ +export const transferVolume = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/volume/transfer', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除卷 */ +export const deleteVolume = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/volume/delete', { params }) +} + +/** + * ================================ + * 主控服务接口 - 虚拟机管理 + * ================================ + */ + +/** 获取虚拟机列表 */ +export const getVmList = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/vm/list', { params }) +} + +/** 获取虚拟机详情 */ +export const getVmDetail = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/vm/detail', { params }) +} + +/** 获取虚拟机状态 */ +export const getVmStatus = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/vm/status', { params }) +} + +/** 获取虚拟机指标数据 */ +export const getVmMetrics = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/vm/metrics', { params }) +} + +/** 创建虚拟机 */ +export const createVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/create', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改虚拟机 */ +export const updateVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/update', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 重建虚拟机 */ +export const rebuildVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/rebuild', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 重构虚拟机 */ +export const refactorVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/refactor', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改虚拟机带宽 */ +export const updateVmTraffic = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/update_traffic', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 启动虚拟机 */ +export const startVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/start', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 停止虚拟机 */ +export const stopVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/stop', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 重启虚拟机 */ +export const rebootVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/reboot', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 暂停虚拟机 */ +export const suspendVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/suspend', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 恢复虚拟机 */ +export const resumeVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/resume', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 虚拟机进入救援系统 */ +export const rescueVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/rescue', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 虚拟机退出救援系统 */ +export const exitRescueVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/exit_rescue', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除虚拟机 */ +export const deleteVm = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vm/delete', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** + * ================================ + * 主控服务接口 - 安全组管理 + * ================================ + */ + +/** 获取安全组列表 */ +export const getSecurityGroupList = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/post_group/list', { params }) +} + +/** 获取安全组详情 */ +export const getSecurityGroupDetail = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/post_group/detail', { params }) +} + +/** 创建安全组 */ +export const createSecurityGroup = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/create', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 同步安全组 */ +export const syncSecurityGroup = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/sync', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 绑定安全组到虚拟机 */ +export const bindSecurityGroup = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/bind', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 解绑安全组 */ +export const unbindSecurityGroup = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/unbind', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除安全组 */ +export const deleteSecurityGroup = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/post_group/delete', { params }) +} + +/** 开启安全组白名单 */ +export const enableSecurityGroupWhitelist = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/enable_whitelist', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 关闭安全组白名单 */ +export const disableSecurityGroupWhitelist = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/disable_whitelist', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 新增安全组规则 */ +export const createSecurityGroupRule = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/create_rule', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改安全组规则 */ +export const updateSecurityGroupRule = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/update_rule', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除安全组规则 */ +export const deleteSecurityGroupRule = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/post_group/delete_rule', { params }) +} + +/** 应用安全组 */ +export const applySecurityGroup = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/post_group/apply', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** + * ================================ + * 主控服务接口 - VNC 节点管理 + * ================================ + */ + +/** 获取 VNC 节点列表 */ +export const getVncNodeList = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/vnc/list', { params }) +} + +/** 获取虚拟机 VNC 连接信息 */ +export const getVmVnc = (params) => { + return http2.get('/api/v1/admin/server/host_service/point/vnc/vm_vnc', { params }) +} + +/** 新增 VNC 节点 */ +export const addVncNode = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vnc/add', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 测试 VNC 节点连接 */ +export const testVncNode = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vnc/test', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 修改 VNC 节点 */ +export const updateVncNode = (data) => { + return http2.post('/api/v1/admin/server/host_service/point/vnc/update', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 删除 VNC 节点 */ +export const deleteVncNode = (params) => { + return http2.delete('/api/v1/admin/server/host_service/point/vnc/delete', { params }) +} diff --git a/src/components/admin/HostGroupSelectorPopup.vue b/src/components/admin/HostGroupSelectorPopup.vue new file mode 100644 index 0000000..dfa062a --- /dev/null +++ b/src/components/admin/HostGroupSelectorPopup.vue @@ -0,0 +1,73 @@ + + + + + + + + {{ row.note || row.Note || '-' }} + + + + + 取消 + 确认选择 + + + + + + + diff --git a/src/components/admin/ImageSelectorPopup.vue b/src/components/admin/ImageSelectorPopup.vue new file mode 100644 index 0000000..3c33675 --- /dev/null +++ b/src/components/admin/ImageSelectorPopup.vue @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + {{ row.os_type }} + + + + + {{ row.type === 'system' ? '系统' : '数据' }} + + + + + + {{ { ready: '就绪', error: '错误', downloading: '下载中', pending: '等待中' }[row.status] || row.status }} + + + + + + + 取消 + 确认选择 + + + + + + + diff --git a/src/components/admin/VmSelectorPopup.vue b/src/components/admin/VmSelectorPopup.vue new file mode 100644 index 0000000..a2787b5 --- /dev/null +++ b/src/components/admin/VmSelectorPopup.vue @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + {{ row.vcpu }}核 / {{ formatMem(row.memory) }} + + + + + {{ statusLabel(row.status) }} + + + + + + 取消 + 确认选择 + + + + + + + diff --git a/src/router/index.js b/src/router/index.js index 83e39e9..0d5124a 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -407,12 +407,133 @@ const routes = [ title: '主控服务管理' } }, + { + path: 'kvm-service-detail', + name: 'KvmServiceDetail', + component: () => import('../views/virtualization/KvmServiceDetail.vue'), + meta: { + title: '主控服务详情', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, { path: 'host-group-mapping', name: 'HostGroupMapping', component: () => import('../views/virtualization/HostGroupMapping.vue'), meta: { title: '宿主机组映射管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'host-manage', + name: 'HostManage', + component: () => import('../views/virtualization/HostManage.vue'), + meta: { + title: '宿主机管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'image-manage', + name: 'ImageManage', + component: () => import('../views/virtualization/ImageManage.vue'), + meta: { + title: '镜像管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'network-manage', + name: 'NetworkManage', + component: () => import('../views/virtualization/NetworkManage.vue'), + meta: { + title: '网络管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'volume-manage', + name: 'VolumeManage', + component: () => import('../views/virtualization/VolumeManage.vue'), + meta: { + title: '数据卷管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'vm-manage', + name: 'VmManage', + component: () => import('../views/virtualization/VmManage.vue'), + meta: { + title: '虚拟机管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'security-group', + name: 'SecurityGroupManage', + component: () => import('../views/virtualization/SecurityGroupManage.vue'), + meta: { + title: '安全组管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'vnc-node', + name: 'VncNodeManage', + component: () => import('../views/virtualization/VncNodeManage.vue'), + meta: { + title: 'VNC节点管理', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'host-detail', + name: 'VirtHostDetail', + component: () => import('../views/virtualization/HostDetail.vue'), + meta: { + title: '宿主机详情', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'image-detail', + name: 'VirtImageDetail', + component: () => import('../views/virtualization/ImageDetail.vue'), + meta: { + title: '镜像详情', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'vm-detail', + name: 'VirtVmDetail', + component: () => import('../views/virtualization/VmDetail.vue'), + meta: { + title: '虚拟机详情', + hidden: true, + activeMenu: '/virtualization/kvm-service' + } + }, + { + path: 'security-group-detail', + name: 'VirtSecurityGroupDetail', + component: () => import('../views/virtualization/SecurityGroupDetail.vue'), + meta: { + title: '安全组详情', + hidden: true, activeMenu: '/virtualization/kvm-service' } } diff --git a/src/views/virtualization/HostDetail.vue b/src/views/virtualization/HostDetail.vue new file mode 100644 index 0000000..fa578c2 --- /dev/null +++ b/src/views/virtualization/HostDetail.vue @@ -0,0 +1,319 @@ + + + + + 返回宿主机列表 + + 宿主机详情 + + + 编辑 + 刷新 + 删除 + + + + + + + 基本信息 + + {{ detail.id }} + {{ detail.name }} + {{ detail.ip || '-' }} + {{ detail.base_url || '-' }} + {{ detail.port || '-' }} + {{ detail.user || '-' }} + + + 未设置 + + + + 未设置 + + {{ detail.private_key_path || '-' }} + {{ detail.host_group_id ? `#${detail.host_group_id}` : '-' }} + + {{ detail.is_active ? '启用' : '禁用' }} + + {{ detail.description || '-' }} + {{ formatTimestamp(detail.created_at) }} + {{ formatTimestamp(detail.updated_at) }} + + + + + + 资源限制 + + 最大CPU{{ detail.max_cpu ? detail.max_cpu + ' 核' : '-' }} + 最大内存{{ formatMemMB(detail.max_memory) }} + 最大磁盘{{ formatDiskGB(detail.max_disk) }} + 下行带宽{{ detail.rx_bandwidth || 0 }} Mbps + 上行带宽{{ detail.tx_bandwidth || 0 }} Mbps + + + + + + + + 实时指标 + 刷新指标 + + + + + + + + CPU + + {{ (metricsData.cpu.cpu_usage_percent ?? 0).toFixed(1) }}% + {{ metricsData.cpu.cpu_count ?? '-' }} + + + + + + 内存 + + {{ formatBytesRaw(metricsData.memory.total) }} + {{ formatBytesRaw(metricsData.memory.used) }} + {{ formatBytesRaw(metricsData.memory.free) }} + {{ metricsData.memory.percent ?? '-' }}% + + + + + + + + 磁盘 + + {{ path }} + + {{ formatBytesRaw(info.total) }} + {{ formatBytesRaw(info.used) }} + {{ formatBytesRaw(info.free) }} + {{ info.percent ?? '-' }}% + + + + + + + 网络 + + + {{ formatBytesRaw(metricsData.network.rx_bytes) }} + {{ formatBytesRaw(metricsData.network.tx_bytes) }} + + + {{ formatBytesRaw(metricsData.internet_speed.rx_bytes) }}/s + {{ formatBytesRaw(metricsData.internet_speed.tx_bytes) }}/s + + + + + + + + + + + + + + + + + + + SSH 配置 + + + + + 资源限制 + + + + + + + + + + + + + 选择 + 清除 + + + + + + 取消 + 确定 + + + + formData.host_group_id = g.id" /> + + + + + + diff --git a/src/views/virtualization/HostGroupMapping.vue b/src/views/virtualization/HostGroupMapping.vue index 583dc9a..1c76241 100644 --- a/src/views/virtualization/HostGroupMapping.vue +++ b/src/views/virtualization/HostGroupMapping.vue @@ -1,7 +1,7 @@ - + 返回 @@ -18,6 +18,10 @@ + + 从远程同步 + 刷新 + @@ -204,7 +208,7 @@ + + diff --git a/src/views/virtualization/ImageDetail.vue b/src/views/virtualization/ImageDetail.vue new file mode 100644 index 0000000..7a0c8ac --- /dev/null +++ b/src/views/virtualization/ImageDetail.vue @@ -0,0 +1,294 @@ + + + + + 返回镜像列表 + + 镜像详情 + + + 编辑 + 同步到宿主机 + 重下载 + 刷新 + 删除 + + + + + + + 基本信息 + + {{ detail.id }} + {{ detail.name }} + + {{ detail.os_type || '-' }} + + + {{ detail.type === 'system' ? '系统镜像' : '数据镜像' }} + + + {{ statusLabel(detail.status) }} + + {{ detail.size ? formatSize(detail.size) : '-' }} + {{ detail.path || '-' }} + {{ detail.description || '-' }} + {{ formatTimestamp(detail.created_at) }} + {{ formatTimestamp(detail.updated_at) }} + + + + + + + + 宿主机同步状态 + 刷新状态 + + + + + + {{ getHostName(row.host_id) }} + + + + {{ statusLabel(row.status) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + {{ detail?.name || '-' }} + + + + + + + + 取消 + 确定同步 + + + + + + + {{ detail?.name || '-' }} + + + + + + + + 取消 + 确定重下载 + + + + + + + + diff --git a/src/views/virtualization/ImageManage.vue b/src/views/virtualization/ImageManage.vue new file mode 100644 index 0000000..deb80e9 --- /dev/null +++ b/src/views/virtualization/ImageManage.vue @@ -0,0 +1,531 @@ + + + + + 返回 + + 镜像管理 + 所属主控服务:{{ serviceName }} + + + + 创建镜像 + 刷新 + + + + 创建镜像 + 刷新 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ row.os_type || '-' }} + + + + + {{ row.type === 'system' ? '系统' : '数据' }} + + + + + {{ statusLabel(row.status) }} + + + + + {{ row.size ? formatSize(row.size) : '-' }} + + + + 编辑 + 删除 + + + + + + + { queryParams.count = s; queryParams.page = 1; loadList() }" + @current-change="p => { queryParams.page = p; loadList() }" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + {{ currentDetail.id }} + {{ currentDetail.name }} + + {{ currentDetail.os_type || '-' }} + + + {{ currentDetail.type === 'system' ? '系统镜像' : '数据镜像' }} + + + {{ statusLabel(currentDetail.status) }} + + {{ currentDetail.size ? formatSize(currentDetail.size) : '-' }} + + {{ currentDetail.path || '-' }} + + {{ currentDetail.description || '-' }} + {{ formatTimestamp(currentDetail.created_at) }} + {{ formatTimestamp(currentDetail.updated_at) }} + + + + + 宿主机同步状态 + + + + {{ getHostName(row.host_id) }} + + + + {{ statusLabel(row.status) }} + + + + + + + + 关闭 + + + + + + + + + + + + + + + + + 取消 + 确定同步 + + + + + + + + + + + + + + + + + 取消 + 确定重下载 + + + + + + + + diff --git a/src/views/virtualization/KvmService.vue b/src/views/virtualization/KvmService.vue index e52c42f..2296daf 100644 --- a/src/views/virtualization/KvmService.vue +++ b/src/views/virtualization/KvmService.vue @@ -51,11 +51,10 @@ {{ formatTime(row.CreatedAt || row.created_at) }} - + 编辑 - 详情 - 主机组 + 详情/管理 删除 @@ -104,24 +103,6 @@ - - - - {{ currentDetail.Id ?? currentDetail.id }} - {{ currentDetail.Name }} - {{ currentDetail.Host }} - {{ currentDetail.Port }} - - - 未设置 - - {{ currentDetail.Note || '-' }} - {{ formatTime(currentDetail.CreatedAt) }} - - - 关闭 - - @@ -132,7 +113,6 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { Plus, Refresh, Search } from '@element-plus/icons-vue' import { getKvmServiceList, - getKvmServiceDetail, createKvmService, updateKvmService, deleteKvmService @@ -143,7 +123,6 @@ const router = useRouter() const loading = ref(false) const submitLoading = ref(false) -const detailLoading = ref(false) const serviceList = ref([]) const total = ref(0) const searchKey = ref('') @@ -159,9 +138,6 @@ const dialogVisible = ref(false) const dialogType = ref('add') const formRef = ref(null) -const detailDialogVisible = ref(false) -const currentDetail = ref(null) - const formData = reactive({ id: undefined, name: '', @@ -355,45 +331,16 @@ const handleDelete = (row) => { }).catch(() => {}) } -// 查看详情 -const handleViewDetail = async (row) => { - // 优先使用原始 Id(PascalCase),回退到规范化后的 id - const rawId = row.Id ?? row.id - console.debug('[KvmService] handleViewDetail rawId:', rawId, 'row:', row) - if (rawId === undefined || rawId === null || rawId === '') { +// 查看详情 —— 跳转到详情页面 +const handleViewDetail = (row) => { + const id = Number(row.Id ?? row.id) + const name = row.Name ?? row.name + if (!id) { ElMessage.error('无法获取服务ID,请刷新列表后重试') return } - detailDialogVisible.value = true - detailLoading.value = true - currentDetail.value = null - try { - const res = await getKvmServiceDetail({ id: Number(rawId) }) - const body = res?.data - console.debug('[KvmService] detail response body:', JSON.stringify(body)) - if (body?.code === 200 && body?.data) { - currentDetail.value = normalizeService(body.data) - } else { - // 接口返回非200,显示错误但仍展示列表行数据 - ElMessage.error(body?.message || '获取详情失败') - currentDetail.value = normalizeService(row) - } - } catch (error) { - console.error('获取详情失败:', error) - const errMsg = error?.response?.data?.message || error?.message || '未知错误' - ElMessage.error('获取详情失败: ' + errMsg) - currentDetail.value = normalizeService(row) - } finally { - detailLoading.value = false - } -} - -// 跳转到宿主机组映射管理 -const goHostGroupMapping = (row) => { - const id = Number(row.Id ?? row.id) - const name = row.Name ?? row.name router.push({ - path: '/virtualization/host-group-mapping', + path: '/virtualization/kvm-service-detail', query: { service_id: id, service_name: name } }) } diff --git a/src/views/virtualization/KvmServiceDetail.vue b/src/views/virtualization/KvmServiceDetail.vue new file mode 100644 index 0000000..006b812 --- /dev/null +++ b/src/views/virtualization/KvmServiceDetail.vue @@ -0,0 +1,505 @@ + + + + + + + 返回列表 + + + 主控服务详情 + + + + 刷新数据 + + + + + + + + + + + + + + + {{ serviceInfo.Name || serviceInfo.name || '未命名服务' }} + 运行中 + + + ID: + {{ serviceInfo.Id ?? serviceInfo.id }} + + 地址: + {{ serviceInfo.Host || serviceInfo.host }}:{{ serviceInfo.Port || serviceInfo.port }} + + 创建: + {{ formatTime(serviceInfo.CreatedAt || serviceInfo.created_at) }} + + + + + + + 认证Token + + 已设置 + 未设置 + + + + 备注 + {{ serviceInfo.Note || serviceInfo.note || '-' }} + + + + + + + + + 编辑服务 + 删除服务 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + diff --git a/src/views/virtualization/NetworkManage.vue b/src/views/virtualization/NetworkManage.vue new file mode 100644 index 0000000..dbfdacb --- /dev/null +++ b/src/views/virtualization/NetworkManage.vue @@ -0,0 +1,331 @@ + + + + + 返回 + + 网络管理 + 主控服务:{{ serviceName }} | 宿主机:{{ selectedHostName || '请选择' }} + + + + 创建网络 + 刷新 + + + + 创建网络 + 刷新 + + + + + + + + + + + + + + + + + + + + + + + + {{ row.type === 'bridge' ? '网桥' : 'NAT' }} + + + + + + + + + + {{ getHostLabel(row.host_id) }} + + + + 详情 + 编辑 + 删除 + + + + + + { queryParams.count = s; queryParams.page = 1; loadList() }" + @current-change="p => { queryParams.page = p; loadList() }" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 高级配置(可选) + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + {{ currentDetail.id }} + {{ currentDetail.name }} + + + {{ currentDetail.type === 'bridge' ? '网桥' : 'NAT' }} + + + {{ getHostLabel(currentDetail.host_id) }} + {{ currentDetail.address }} + {{ currentDetail.gateway }} + {{ currentDetail.nameservers || '-' }} + {{ currentDetail.mac_address || '-' }} + {{ currentDetail.bridge_name || '-' }} + {{ currentDetail.ls_bridge_name || '-' }} + {{ currentDetail.ls_name || '-' }} + {{ currentDetail.target_device || '-' }} + + 关闭 + + + + + + + diff --git a/src/views/virtualization/RemoteHostGroupManage.vue b/src/views/virtualization/RemoteHostGroupManage.vue new file mode 100644 index 0000000..804c28e --- /dev/null +++ b/src/views/virtualization/RemoteHostGroupManage.vue @@ -0,0 +1,250 @@ + + + + + 返回 + + 宿主机组管理 + 所属主控服务:{{ serviceName }} + + + + 新建宿主机组 + 刷新 + + + + 新建宿主机组 + 刷新 + + + + + + + + {{ row.note || '-' }} + + + {{ row.parent_id || '-' }} + + + {{ formatTimestamp(row.created_at) }} + + + + 详情 + 编辑 + 最优主机 + 删除 + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + {{ currentDetail.id }} + {{ currentDetail.name }} + {{ currentDetail.parent_id || '-' }} + {{ currentDetail.note || '-' }} + {{ formatTimestamp(currentDetail.created_at) }} + {{ formatTimestamp(currentDetail.updated_at) }} + + 关闭 + + + + + + + + {{ typeof val === 'object' ? JSON.stringify(val) : val }} + + + + + 关闭 + + + + + + + diff --git a/src/views/virtualization/SecurityGroupDetail.vue b/src/views/virtualization/SecurityGroupDetail.vue new file mode 100644 index 0000000..dfea286 --- /dev/null +++ b/src/views/virtualization/SecurityGroupDetail.vue @@ -0,0 +1,330 @@ + + + + + 返回安全组列表 + + 安全组详情 + + + 刷新 + + + + + + + 基本信息 + + {{ detail.id }} + {{ detail.name }} + + {{ detail.lock ? '是' : '否' }} + + + {{ detail.drop_all ? '开启' : '关闭' }} + + {{ getHostLabel(detail.host_id) }} + {{ detail.direction || '-' }} + {{ formatTimestamp(detail.created_at) }} + {{ formatTimestamp(detail.updated_at) }} + + + + + + 操作 + + 同步到宿主机 + 绑定VM + 解绑VM + + {{ detail.drop_all ? '关闭白名单' : '开启白名单' }} + + 应用规则 + 删除安全组 + + + + + + + + 安全组规则 + 新增规则 + + + + + + {{ (row.protocol || '-').toUpperCase() }} + + + + {{ row.action === 'allow' ? '允许' : '拒绝' }} + + + + + + + + 编辑 + 删除 + + + + + + + + + + + {{ detail?.name || '-' }} + + + + + + + + 取消 + 同步 + + + + + + + {{ detail?.name || '-' }} + + + + 选择 + + + + + 取消 + + {{ bindType === 'bind' ? '绑定' : '解绑' }} + + + + + { bindVmId = vm.id; bindVmName = vm.name || '' }" /> + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + diff --git a/src/views/virtualization/SecurityGroupManage.vue b/src/views/virtualization/SecurityGroupManage.vue new file mode 100644 index 0000000..1376600 --- /dev/null +++ b/src/views/virtualization/SecurityGroupManage.vue @@ -0,0 +1,521 @@ + + + + + 返回 + + 安全组管理 + 主控服务:{{ serviceName }} + + + + 创建安全组 + 刷新 + + + + 创建安全组 + 刷新 + + + + + + + + + + + + + + + + + + + + + + + + {{ row.lock ? '是' : '否' }} + + + + + {{ row.drop_all ? '开启' : '关闭' }} + + + + {{ getHostLabel(row.host_id) }} + + + + + 编辑 + 删除 + + + + + + { queryParams.page_size = s; queryParams.page = 1; loadList() }" + @current-change="p => { queryParams.page = p; loadList() }" /> + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + {{ syncTarget?.name || '-' }} + + + + + + + + 取消 + 同步 + + + + + + + {{ bindTarget?.name || '-' }} + + + + 选择 + + + + + 取消 + + {{ bindType === 'bind' ? '绑定' : '解绑' }} + + + + + + + + + + + {{ currentDetail.id }} + {{ currentDetail.name }} + + {{ currentDetail.lock ? '是' : '否' }} + + + {{ currentDetail.drop_all ? '开启' : '关闭' }} + + {{ getHostLabel(currentDetail.host_id) }} + {{ currentDetail.direction || '-' }} + + + + + + 安全组规则 + 新增规则 + + + + + + {{ (row.protocol || '-').toUpperCase() }} + + + + + {{ row.action === 'allow' ? '允许' : '拒绝' }} + + + + + + + + 编辑 + 删除 + + + + + 关闭 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + diff --git a/src/views/virtualization/VmDetail.vue b/src/views/virtualization/VmDetail.vue new file mode 100644 index 0000000..796eef4 --- /dev/null +++ b/src/views/virtualization/VmDetail.vue @@ -0,0 +1,319 @@ + + + + + 返回虚拟机列表 + + 虚拟机详情 + {{ vmStatusLabel(detail.status) }} + + + 刷新 + + + + + + + 基本信息 + + {{ detail.id }} + {{ detail.name }} + {{ detail.vcpu ? detail.vcpu + ' 核' : '-' }} + {{ formatMemory(detail.memory) }} + {{ detail.system_size ? detail.system_size + ' MB' : '-' }} + {{ detail.image_id || '-' }} + {{ getHostLabel(detail.host_id) }} + {{ detail.host_group_id || '-' }} + {{ detail.rx_bandwidth || 0 }} Mbps + {{ detail.tx_bandwidth || 0 }} Mbps + {{ detail.user_id || '-' }} + {{ detail.ip_num || '-' }} + {{ formatTimestamp(detail.created_at) }} + {{ formatTimestamp(detail.updated_at) }} + + + + + + 电源控制 + + 启动 + 停止 + 重启 + 暂停 + 恢复 + + 重建 + 救援模式 + 退出救援 + + 刷新状态 + + + + + + + + 实时指标 + 刷新指标 + + + + + + + + CPU + + {{ (metricsData.cpu.cpu_usage_percent ?? 0).toFixed(1) }}% + {{ metricsData.cpu.cpu_count ?? '-' }} + + + + + + 内存 + + {{ formatBytesRaw(metricsData.memory.total) }} + {{ formatBytesRaw(metricsData.memory.used) }} + + + + + + + + + + + + 关联资源 + + 网络 + + + + + + + + + 数据卷 + + + + + + + + + + + + + + + {{ detail?.name || '-' }} + + + + 选择 + + + + + 取消 + 确定重建 + + + + { rebuildImageId = img.id; rebuildImageName = img.name }" /> + + + + + + diff --git a/src/views/virtualization/VmManage.vue b/src/views/virtualization/VmManage.vue new file mode 100644 index 0000000..2d2fc75 --- /dev/null +++ b/src/views/virtualization/VmManage.vue @@ -0,0 +1,618 @@ + + + + + 返回 + + 虚拟机管理 + 主控服务:{{ serviceName }} | 宿主机:{{ selectedHostName || '请选择' }} + + + + 创建虚拟机 + 刷新 + + + + 创建虚拟机 + 刷新 + + + + + + + + + + + + + + + + + + + + + + + {{ row.vcpu }}核 + {{ formatMemory(row.memory) }} + {{ row.system_size }}MB盘 + + + + + + + ↓{{ row.rx_bandwidth || 0 }} Mbps / ↑{{ row.tx_bandwidth || 0 }} Mbps + + - + + + + + {{ vmStatusLabel(row.status) }} + + + + {{ getHostLabel(row.host_id) }} + + + + + 编辑 + 删除 + + + + + + { queryParams.count = s; queryParams.page = 1; loadList() }" + @current-change="p => { queryParams.page = p; loadList() }" /> + + + + + + + + + + + + + + + 选择 + 清除 + + + 资源配置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 可选配置 + + + + 选择 + 清除 + + + + + + + 取消 + 创建 + + + + + + + + {{ rebuildTarget?.name }} (#{{ rebuildTarget?.id }}) + + + + 选择 + + + + + 取消 + 确认重建 + + + + + + + + {{ currentDetail.id }} + {{ currentDetail.name }} + {{ currentDetail.vcpu }} 核 + {{ formatMemory(currentDetail.memory) }} + {{ currentDetail.system_size }} MB + + {{ vmStatusLabel(currentDetail.status) }} + + {{ currentDetail.rx_bandwidth || 0 }} Mbps + {{ currentDetail.tx_bandwidth || 0 }} Mbps + {{ getHostLabel(currentDetail.host_id) }} + {{ currentDetail.image_id || '-' }} + {{ currentDetail.user_id || '-' }} + {{ currentDetail.host_group_id || '-' }} + {{ currentDetail.ip || '-' }} + {{ formatTimestamp(currentDetail.created_at) }} + {{ formatTimestamp(currentDetail.updated_at) }} + + + + + 🌐 网络 + + + + + + + + + + + + 💿 数据卷 + + + + + {{ row.size }} GB + + + {{ row.is_system ? '是' : '否' }} + + + + + + + + 🖼️ 镜像 + + {{ currentDetail.image.id }} + {{ currentDetail.image.name }} + {{ currentDetail.image.os_type }} / {{ currentDetail.image.type }} + {{ currentDetail.image.status }} + + + + + 刷新状态 + 查看指标 + + + + + 实时指标 + + + {{ (vmMetricsData.cpu.cpu_usage_percent ?? 0).toFixed(1) }}% + + + {{ formatBytesRaw(vmMetricsData.memory.total) }} + {{ formatBytesRaw(vmMetricsData.memory.used) }} ({{ vmMetricsData.memory.percent || 0 }}%) + + + + {{ formatBytesRaw(info.used) }} / {{ formatBytesRaw(info.total) }} ({{ info.percent || 0 }}%) + + + + {{ formatBytesRaw(vmMetricsData.network.rx_bytes) }} + {{ formatBytesRaw(vmMetricsData.network.tx_bytes) }} + + + + + 关闭 + + + + + + + + + + + + + + diff --git a/src/views/virtualization/VncNodeManage.vue b/src/views/virtualization/VncNodeManage.vue new file mode 100644 index 0000000..5ce85b7 --- /dev/null +++ b/src/views/virtualization/VncNodeManage.vue @@ -0,0 +1,352 @@ + + + + + 返回 + + VNC 节点管理 + 主控服务:{{ serviceName }} + + + + 新增节点 + 刷新 + + + + + + 新增节点 + 刷新 + + + + + + + + + + + + + + + + + + + {{ row.ip || '-' }}:{{ row.port || '-' }} + + + + + {{ maskToken(row.token) }} + 未设置 + + + + + 编辑 + 测试连接 + VM VNC + 删除 + + + + + + { queryParams.page_size = s; queryParams.page = 1; loadList() }" + @current-change="p => { queryParams.page = p; loadList() }" /> + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + {{ testTarget?.name || '-' }} + + + + + + + + + + {{ testResult.message }} + + + 关闭 + 测试 + + + + + + + {{ vmVncTarget?.name || '-' }} + + + + 选择 + + + + + + + + {{ val }} + + {{ val }} + + + + + 关闭 + 获取 + + + + + + + + + + diff --git a/src/views/virtualization/VolumeManage.vue b/src/views/virtualization/VolumeManage.vue new file mode 100644 index 0000000..ac42764 --- /dev/null +++ b/src/views/virtualization/VolumeManage.vue @@ -0,0 +1,467 @@ + + + + + 返回 + + 数据卷管理 + 主控服务:{{ serviceName }} | 宿主机:{{ selectedHostName || '请选择' }} + + + + 创建数据卷 + 刷新 + + + + 创建数据卷 + 刷新 + + + + + + + + + + + + + + + + + + + {{ row.size ? row.size + ' GB' : '-' }} + + + + {{ row.is_system ? '是' : '否' }} + + + + + {{ volStatusLabel(row.status) }} + + + + + {{ getHostLabel(row.host_id) }} + + + + 详情 + 调整大小 + 挂载 + 卸载 + 迁移 + 删除 + + + + + + { queryParams.count = s; queryParams.page = 1; loadList() }" + @current-change="p => { queryParams.page = p; loadList() }" /> + + + + + + + + + + + + + + + + + 选择 + 清除 + + + + + + 选择 + 清除 + + + + + + 取消 + 确定 + + + + + + + {{ resizeTarget?.size || 0 }} GB + + + + + + 取消 + 确定 + + + + + + + {{ mountTarget?.name }} ({{ mountTarget?.size }} GB) + + + + 选择 + + + + + 取消 + 挂载 + + + + + + + 将数据卷迁移到另一台宿主机上。迁移过程中数据卷将不可用。 + + + {{ transferTarget?.name }} ({{ transferTarget?.size }} GB) + {{ getHostLabel(transferTarget?.host_id) }} + + + + + + + + 取消 + 确定迁移 + + + + + + + + {{ currentDetail.id }} + {{ currentDetail.name }} + {{ currentDetail.size }} GB + + {{ currentDetail.is_system ? '是' : '否' }} + + + {{ volStatusLabel(currentDetail.status) }} + + {{ getHostLabel(currentDetail.host_id) }} + + {{ currentDetail.path || '-' }} + + {{ currentDetail.target_device }} + {{ currentDetail.vm_id }} + {{ currentDetail.image_id }} + {{ formatTimestamp(currentDetail.created_at) }} + {{ formatTimestamp(currentDetail.updated_at) }} + + + 关闭 + + + + + + + + + + + + + + diff --git a/问题.MD b/问题.MD new file mode 100644 index 0000000..f4d8183 --- /dev/null +++ b/问题.MD @@ -0,0 +1,52 @@ +## 管理员前端控制台 - 问题跟踪 + +### 最新一轮 (图一到图七) + +1. ✅已完成 - 图一:安全组列表 "暂无数据" → 修复 `SecurityGroupManage.vue` 数据映射优先 `inner.groups` +2. ✅已完成 - 图二:安全组详情弹窗字段为空 → 修复 `SecurityGroupManage.vue` 详情映射优先 `res.data.data.group` +3. ✅已完成 - 图三:安全组绑定VM使用 `el-input-number` → 改用 `VmSelectorPopup` 组件 +4. ✅已完成 - 图四:安全组解绑VM使用 `el-input-number` → 改用 `VmSelectorPopup` 组件 +5. ✅已完成 - 图五:VNC 获取VM连接使用 `el-input-number` → 改用 `VmSelectorPopup` 组件 +6. ✅已完成 - 图六:VNC 测试宿主机默认值显示 "0" → 改为 `null` (空选择) +7. ✅已完成 - 图七:宿主机、镜像、虚拟机、安全组四个模块表格操作只保留"编辑"和"删除"按钮 + - 创建 `HostDetail.vue` 宿主机独立详情页 (含编辑、指标、详情) + - 创建 `ImageDetail.vue` 镜像独立详情页 (含编辑、同步到宿主机、重下载、宿主机状态) + - 创建 `VmDetail.vue` 虚拟机独立详情页 (含电源操作、重建、救援、指标) + - 创建 `SecurityGroupDetail.vue` 安全组独立详情页 (含同步、绑定/解绑VM、白名单切换、规则管理) + - 添加4个详情页路由 (`host-detail`, `image-detail`, `vm-detail`, `security-group-detail`) + - `HostManage.vue` 表格操作改为 "编辑"→跳转详情页 + "删除" + - `ImageManage.vue` 表格操作改为 "编辑"→跳转详情页 + "删除" + - `VmManage.vue` 表格操作改为 "编辑"→跳转详情页 + "删除",补充 `deleteVm` API + - `SecurityGroupManage.vue` 表格操作改为 "编辑"→跳转详情页 + "删除" + +--- + +### 前一轮 (图一到图五 - 列表数据显示) + +1. ✅已完成 - 远程宿主机组列表 "暂无数据" → 修复 `RemoteHostGroupManage.vue` 优先 `inner.host_groups` +2. ✅已完成 - 宿主机指标弹窗 emoji 图标 → 改为 Element Plus 图标 (`Monitor`, `Coin`, `Box`, `Connection`) +3. ✅已完成 - 虚拟机管理 "更多" 下拉按钮错位 → 修复 `el-dropdown` 内联样式对齐 +4. ✅已完成 - 安全组列表 "暂无数据" → 修复 `SecurityGroupManage.vue` 优先 `inner.groups` +5. ✅已完成 - VNC 节点列表 "暂无数据" → 修复 `VncNodeManage.vue` 优先 `inner.items` + +--- + +### 虚拟化平台管理 17项问题 (已全部完成) + +1. ✅已完成 - 宿主机管理数据无法正常显示 +2. ✅已完成 - 宿主机创建/编辑:数字输入框样式优化 + 带宽单位 Mbps +3. ✅已完成 - 宿主机创建:宿主机组ID改为选择器 +4. ✅已完成 - 宿主机详情:SSH密码显示 + 指标美化 + 时间戳格式化 +5. ✅已完成 - 远程宿主机组管理页面 +6. ✅已完成 - 镜像详情:data.image 嵌套映射 +7. ✅已完成 - 镜像同步到宿主机 + 重下载功能 +8. ✅已完成 - 网络管理:空高级参数不提交 + host_id 改为下拉选择 +9. ✅已完成 - 数据卷:host_id 改为下拉选择 +10. ✅已完成 - 数据卷:迁移功能 + 选择器组件 +11. ✅已完成 - 数据卷详情:data.volume 嵌套映射 + 时间戳 +12. ✅已完成 - 虚拟机:带宽显示 Mbps 单位 +13. ✅已完成 - 虚拟机:完整中文状态映射 +14. ✅已完成 - 虚拟机详情:data.vm 嵌套映射 + 子表格 +15. ✅已完成 - 虚拟机:恢复/救援/退出救援操作 +16. ✅已完成 - 虚拟机创建:镜像选择器 + 宿主机组选择器 +17. ✅已完成 - 虚拟机指标:CPU/内存/磁盘/网络卡片美化