From d0171659bd17f3953e7e3dac7fff11b7bb1bba70 Mon Sep 17 00:00:00 2001 From: shiran Date: Sat, 29 Aug 2026 17:26:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=20AWS=20=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E4=BA=91=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/admin/awsService.js | 41 +++- src/api/admin/userVm.js | 10 + .../admin/ProductGoodsTreeSelector.vue | 99 +++++++++ src/config/menus.js | 3 +- src/router/index.js | 6 - src/views/aws/AwsGoods.vue | 93 ++++++-- src/views/aws/AwsInstanceDetail.vue | 132 ++++++++++++ src/views/aws/AwsInstances.vue | 45 ---- src/views/aws/AwsRuntimeVisualization.vue | 41 ++++ src/views/product/UserGoodsDetail.vue | 12 ++ .../product/components/AwsUserGoodsDetail.vue | 202 ++++++++++++++++++ src/views/user-vm/UserVmDetail.vue | 4 + .../user-vm/components/VmOperationRecords.vue | 70 ++++++ 默认模块.openapi.json | 2 +- 14 files changed, 688 insertions(+), 72 deletions(-) create mode 100644 src/components/admin/ProductGoodsTreeSelector.vue create mode 100644 src/views/aws/AwsInstanceDetail.vue delete mode 100644 src/views/aws/AwsInstances.vue create mode 100644 src/views/aws/AwsRuntimeVisualization.vue create mode 100644 src/views/product/components/AwsUserGoodsDetail.vue create mode 100644 src/views/user-vm/components/VmOperationRecords.vue diff --git a/src/api/admin/awsService.js b/src/api/admin/awsService.js index bd40ad3..d6f2d28 100644 --- a/src/api/admin/awsService.js +++ b/src/api/admin/awsService.js @@ -1,6 +1,13 @@ import { http2 } from '@/utils/request.js' const base = '/api/v1/admin/server/aws_service' +const runtimeBase = `${base}/instances/runtime` + +export const createAwsIdempotencyKey = (prefix = 'aws') => { + const random = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}` + return `${prefix}-${random}`.slice(0, 128) +} +const withKey = (key) => ({ headers: { 'Idempotency-Key': key || createAwsIdempotencyKey() } }) export const getAwsServiceList = (params) => http2.get(`${base}/list`, { params }) export const getAwsServiceDetail = (id) => http2.get(`${base}/detail`, { params: { id } }) @@ -15,5 +22,35 @@ export const createAwsGoods = (data) => http2.post(`${base}/goods/create`, data) export const updateAwsGoods = (data) => http2.post(`${base}/goods/update`, data) export const deleteAwsGoods = (id) => http2.delete(`${base}/goods/delete`, { params: { id } }) -export const getAwsInstanceList = (params) => http2.get(`${base}/instances/list`, { params }) -export const retryAwsInstance = (instanceId) => http2.post(`${base}/instances/retry`, null, { params: { instance_id: instanceId } }) +export const getAwsInstanceDetail = (identifier) => http2.get(`${base}/instances/detail`, { + params: typeof identifier === 'object' ? identifier : { instance_id: identifier } +}) +export const retryAwsInstance = (instanceId, note = '') => http2.post(`${base}/instances/retry`, { instance_id: instanceId, note }) +export const resyncAwsInstance = (instanceId, note = '') => http2.post(`${base}/instances/resync`, { instance_id: instanceId, note }) +export const resolveAwsInstance = (instanceId, resolution, note = '') => http2.post(`${base}/instances/resolve`, { instance_id: instanceId, resolution, note }) + +export const getAdminAwsRuntimeDetails = instanceId => http2.get(`${runtimeBase}/details`, { params: { instance_id: instanceId } }) +export const refreshAdminAwsRuntimeDetails = (instanceId, key) => http2.post(`${runtimeBase}/details/refresh`, { instance_id: instanceId }, withKey(key)) +export const getAdminAwsLoginInfo = instanceId => http2.post(`${runtimeBase}/login_info`, { instance_id: instanceId }) +export const resetAdminAwsPassword = (instanceId, key) => http2.post(`${runtimeBase}/password/reset`, { instance_id: instanceId }, withKey(key)) +export const getAdminAwsMonitoring = instanceId => http2.get(`${runtimeBase}/monitoring`, { params: { instance_id: instanceId } }) +export const getAdminAwsSecurityGroup = instanceId => http2.get(`${runtimeBase}/security_group`, { params: { instance_id: instanceId } }) +export const addAdminAwsSecurityGroupRule = (instanceId, rule, key) => http2.post(`${runtimeBase}/security_group/rules/add`, { instance_id: instanceId, rule }, withKey(key)) +export const deleteAdminAwsSecurityGroupRule = (instanceId, rule, key) => http2.post(`${runtimeBase}/security_group/rules/delete`, { instance_id: instanceId, rule }, withKey(key)) +export const getAdminAwsVolumes = instanceId => http2.get(`${runtimeBase}/volumes`, { params: { instance_id: instanceId } }) +export const createAdminAwsVolume = (data, key) => http2.post(`${runtimeBase}/volumes/create`, data, withKey(key)) +export const modifyAdminAwsVolume = (data, key) => http2.post(`${runtimeBase}/volumes/modify`, data, withKey(key)) +export const attachAdminAwsVolume = (data, key) => http2.post(`${runtimeBase}/volumes/attach`, data, withKey(key)) +export const detachAdminAwsVolume = (data, key) => http2.post(`${runtimeBase}/volumes/detach`, data, withKey(key)) +export const deleteAdminAwsVolume = (data, key) => http2.post(`${runtimeBase}/volumes/delete`, data, withKey(key)) +export const getAdminAwsEip = instanceId => http2.get(`${runtimeBase}/eip`, { params: { instance_id: instanceId } }) +export const replaceAdminAwsEip = (data, key) => http2.post(`${runtimeBase}/eip/replace`, data, withKey(key)) +export const disassociateAdminAwsEip = (instanceId, key) => http2.post(`${runtimeBase}/eip/disassociate`, { instance_id: instanceId }, withKey(key)) +export const releaseAdminAwsEip = (instanceId, key) => http2.post(`${runtimeBase}/eip/release`, { instance_id: instanceId }, withKey(key)) +export const updateAdminAwsEipReverseDns = (data, key) => http2.post(`${runtimeBase}/eip/reverse_dns`, data, withKey(key)) +export const getAdminAwsOperations = (instanceId, params = {}) => http2.get(`${runtimeBase}/operations`, { params: { instance_id: instanceId, ...params } }) +export const getAdminAwsOperationDetail = operationId => http2.get(`${runtimeBase}/operations/detail`, { params: { operation_id: operationId } }) +export const retryAdminAwsOperation = (operationId, note = '', key) => http2.post(`${runtimeBase}/operations/retry`, { operation_id: operationId, note }, withKey(key)) +export const resyncAdminAwsOperation = operationId => http2.post(`${runtimeBase}/operations/resync`, null, { params: { operation_id: operationId } }) +export const resolveAdminAwsOperation = (operationId, note = '') => http2.post(`${runtimeBase}/operations/resolve`, { operation_id: operationId, note }) +export const rejectAdminAwsOperation = (operationId, note = '') => http2.post(`${runtimeBase}/operations/reject`, { operation_id: operationId, note }) diff --git a/src/api/admin/userVm.js b/src/api/admin/userVm.js index 5527691..0cc3d8c 100644 --- a/src/api/admin/userVm.js +++ b/src/api/admin/userVm.js @@ -114,6 +114,16 @@ export const renewUserGoods = (data) => http2.post(`${GOODS_BASE}/renew`, fd(dat export const getUserVmMetricsHistory = (params) => http2.get(`${BASE}/metrics_history`, { params }) export const getUserVmTrafficHourly = (params) => http2.get(`${BASE}/traffic_hourly`, { params }) +// ========== 虚拟机专用操作日志 ========== +const OPERATION_RECORD_BASE = '/api/v1/admin/host_service/point/vm/operation_record' +export const getVmOperationRecordList = (params) => http2.get(`${OPERATION_RECORD_BASE}/list`, { params }) +export const getVmOperationRecordDetail = (id) => http2.get(`${OPERATION_RECORD_BASE}/detail`, { params: { id } }) +export const refreshVmOperationRecord = (id) => http2.post( + `${OPERATION_RECORD_BASE}/refresh`, + fd({ id }), + { headers: { 'Content-Type': 'multipart/form-data' } } +) + // ========== 流量策略 ========== // 测试未通过(接口新增,待联调) export const getUserVmTrafficPolicy = (params) => http2.get(`${BASE}/traffic_policy`, { params }) diff --git a/src/components/admin/ProductGoodsTreeSelector.vue b/src/components/admin/ProductGoodsTreeSelector.vue new file mode 100644 index 0000000..f98affa --- /dev/null +++ b/src/components/admin/ProductGoodsTreeSelector.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/config/menus.js b/src/config/menus.js index e2eff65..17fa551 100644 --- a/src/config/menus.js +++ b/src/config/menus.js @@ -186,8 +186,7 @@ export const menus = [ icon: 'Cloudy', children: [ { path: '/aws/services', title: '服务配置' }, - { path: '/aws/goods', title: '商品绑定' }, - { path: '/aws/instances', title: '履约实例' } + { path: '/aws/goods', title: '商品绑定' } ] }, { diff --git a/src/router/index.js b/src/router/index.js index 27c992c..7d2a034 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -689,12 +689,6 @@ const routes = [ name: 'AwsGoods', component: () => import('../views/aws/AwsGoods.vue'), meta: { title: '商品绑定' } - }, - { - path: 'instances', - name: 'AwsInstances', - component: () => import('../views/aws/AwsInstances.vue'), - meta: { title: '履约实例' } } ] }, diff --git a/src/views/aws/AwsGoods.vue b/src/views/aws/AwsGoods.vue index 01d099c..3f04634 100644 --- a/src/views/aws/AwsGoods.vue +++ b/src/views/aws/AwsGoods.vue @@ -10,52 +10,113 @@ +
- - - - - - + + + + + + + +
+ + 选择 +
+
+
+ +
+ + + + + + + + + 绑定 EIP加密系统盘安装 Agent启用绑定 + 登录凭据密码重置监控安全组数据卷读取
+ diff --git a/src/views/aws/AwsInstanceDetail.vue b/src/views/aws/AwsInstanceDetail.vue new file mode 100644 index 0000000..c71ce1a --- /dev/null +++ b/src/views/aws/AwsInstanceDetail.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/views/aws/AwsInstances.vue b/src/views/aws/AwsInstances.vue deleted file mode 100644 index 0329e6f..0000000 --- a/src/views/aws/AwsInstances.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/src/views/aws/AwsRuntimeVisualization.vue b/src/views/aws/AwsRuntimeVisualization.vue new file mode 100644 index 0000000..0e75dca --- /dev/null +++ b/src/views/aws/AwsRuntimeVisualization.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/views/product/UserGoodsDetail.vue b/src/views/product/UserGoodsDetail.vue index 8aad036..c22f3da 100644 --- a/src/views/product/UserGoodsDetail.vue +++ b/src/views/product/UserGoodsDetail.vue @@ -210,6 +210,8 @@ + + @@ -336,6 +338,7 @@ import { extractApiError } from '@/utils/kvmErrorUtil' import VmSelectorPopup from '@/components/admin/VmSelectorPopup.vue' import KvmServiceSelector from '@/components/admin/KvmServiceSelector.vue' import UserVmDetail from '@/views/user-vm/UserVmDetail.vue' +import AwsUserGoodsDetail from './components/AwsUserGoodsDetail.vue' import dayjs from 'dayjs' const route = useRoute() @@ -345,6 +348,7 @@ const goodsId = computed(() => parseInt(route.params.id) || 0) const loading = ref(false) const refreshing = ref(false) const vmDetailRef = ref(null) +const awsDetailRef = ref(null) const submitLoading = ref(false) const detail = ref(null) const detailExpanded = ref(false) @@ -387,6 +391,10 @@ const expireStatusClass = computed(() => { const goBack = () => router.push('/user-goods/list') const currentTag = computed(() => (detail.value?.tag || detail.value?.good?.tag || '').toLowerCase()) +const isAwsGoods = computed(() => { + const tag = currentTag.value.replace(/\s+/g, '') + return tag === 'aws' || tag.includes('aws云服务器') || tag.includes('aws服务器') +}) // 是否已删除(后端返回 deleteAt 字段,有值即已删除) const isDeleted = computed(() => !!(detail.value?.deleteAt || detail.value?.DeleteAt || detail.value?.deleted_at)) @@ -458,6 +466,10 @@ const refreshDetail = async () => { await nextTick() await vmDetailRef.value?.refresh?.() } + if (isAwsGoods.value && !isDeleted.value) { + await nextTick() + await awsDetailRef.value?.refresh?.() + } } finally { refreshing.value = false } diff --git a/src/views/product/components/AwsUserGoodsDetail.vue b/src/views/product/components/AwsUserGoodsDetail.vue new file mode 100644 index 0000000..e6a1e98 --- /dev/null +++ b/src/views/product/components/AwsUserGoodsDetail.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/src/views/user-vm/UserVmDetail.vue b/src/views/user-vm/UserVmDetail.vue index 89fdceb..c683908 100644 --- a/src/views/user-vm/UserVmDetail.vue +++ b/src/views/user-vm/UserVmDetail.vue @@ -564,6 +564,9 @@ + + + @@ -1215,6 +1218,7 @@ import UserSelector from '@/components/UserSelector/index.vue' import UserVmSecurityGroupSelector from '@/components/admin/UserVmSecurityGroupSelector.vue' import UserVmVolumeSelector from '@/components/admin/UserVmVolumeSelector.vue' import UserVmNetworkSelector from '@/components/admin/UserVmNetworkSelector.vue' +import VmOperationRecords from './components/VmOperationRecords.vue' import dayjs from 'dayjs' import * as echarts from 'echarts' diff --git a/src/views/user-vm/components/VmOperationRecords.vue b/src/views/user-vm/components/VmOperationRecords.vue new file mode 100644 index 0000000..a2819ad --- /dev/null +++ b/src/views/user-vm/components/VmOperationRecords.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/默认模块.openapi.json b/默认模块.openapi.json index 10d90e9..bc9b325 100644 --- a/默认模块.openapi.json +++ b/默认模块.openapi.json @@ -24029,7 +24029,7 @@ }, "/api/v1/admin/good/user_vm/migrate": { "post": { - "summary": "迁移虚拟机(更换宿主机)", + "summary": "迁移虚拟机(更换宿主机,不保留数据)", "deprecated": false, "description": "", "tags": [