From dcdbccac14d00187d1c45fe11d4eabb573944075 Mon Sep 17 00:00:00 2001 From: jojo <royalpioneer@foxmail.com> Date: Tue, 8 Oct 2024 15:03:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E6=94=AF=E6=8C=81=E6=95=85?= =?UTF-8?q?=E9=9A=9C=E6=B1=A0=E3=80=81=E5=BE=85=E5=9B=9E=E6=94=B6=E6=B1=A0?= =?UTF-8?q?=20#7881=20#=20Reviewed,=20transaction=20id:=2026509?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/layout/components/ResourceManage.vue | 26 +- dbm-ui/frontend/src/locales/zh-cn.json | 18 +- .../frontend/src/services/source/dbdirty.ts | 8 +- .../src/services/source/dbresourceResource.ts | 13 +- dbm-ui/frontend/src/services/source/tag.ts | 1 + .../resource-manage/pool/business/Index.vue | 26 +- .../fault-or-recycle-list/Index.vue | 3 +- .../pool/components/host-list/Index.vue | 318 +++++++++++------- .../host-list/components/HostOperationTip.vue | 9 +- .../host-list/components/ImportHostBtn.vue | 5 +- .../components/batch-add-tags/Index.vue | 144 ++++++++ .../batch-add-tags/components/FormPanel.vue | 144 ++++++++ .../batch-add-tags/components/ListPanel.vue | 249 ++++++++++++++ .../batch-assign/components/FormPanel.vue | 24 +- .../batch-assign/components/ListPanel.vue | 27 +- .../batch-convert-to-business/Index.vue | 2 +- .../batch-covert-to-public/Index.vue | 2 +- .../components/batch-setting/Index.vue | 154 +++++---- .../import-host/components/FormPanel.vue | 9 +- .../components/select-host-panel/Index.vue | 63 +++- .../components/HostEmpty.vue | 13 +- .../com-factory/components/Label.vue | 36 +- .../components/field-input/Index.vue | 14 +- .../tag-research/selector/Index.vue | 3 +- .../components/tag-selector/Index.vue | 273 +++++++++++++++ .../components/update-assign/Index.vue | 11 +- .../components/DimensionSelect.vue | 21 ++ .../summary-view/components/List.vue | 13 +- .../pool/components/tag-selector/Index.vue | 18 +- .../resource-manage/pool/global/Index.vue | 23 +- .../spec/components/SpecList.vue | 17 + .../components/BusinessSelector.vue | 28 +- 32 files changed, 1409 insertions(+), 306 deletions(-) create mode 100644 dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/Index.vue create mode 100644 dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/FormPanel.vue create mode 100644 dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/ListPanel.vue create mode 100644 dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-selector/Index.vue diff --git a/dbm-ui/frontend/src/layout/components/ResourceManage.vue b/dbm-ui/frontend/src/layout/components/ResourceManage.vue index e5fbc94aa3..589d528d2e 100644 --- a/dbm-ui/frontend/src/layout/components/ResourceManage.vue +++ b/dbm-ui/frontend/src/layout/components/ResourceManage.vue @@ -5,18 +5,6 @@ :opened-keys="[parentKey]" @click="handleMenuChange"> <BkMenuGroup :name="t('资源管理')"> - <BkMenuItem - key="resourceSpec" - v-db-console="'resourceManage.resourceSpec'"> - <template #icon> - <DbIcon type="spec" /> - </template> - <span - v-overflow-tips.right - class="text-overflow"> - {{ t('资源规格管理') }} - </span> - </BkMenuItem> <BkMenuItem key="resourcePool" v-db-console="'resourceManage.resourcePool'"> @@ -26,7 +14,7 @@ <span v-overflow-tips.right class="text-overflow"> - {{ t('DB 资源池管理') }} + {{ t('资源池') }} </span> </BkMenuItem> <BkMenuItem @@ -54,15 +42,15 @@ </span> </BkMenuItem> <BkMenuItem - key="resourcePoolDirtyMachines" - v-db-console="'resourceManage.dirtyHostManage'"> + key="resourceSpec" + v-db-console="'resourceManage.resourceSpec'"> <template #icon> - <DbIcon type="dirty-host" /> + <DbIcon type="spec" /> </template> <span v-overflow-tips.right class="text-overflow"> - {{ t('污点主机处理') }} + {{ t('资源规格管理') }} </span> </BkMenuItem> <BkMenuItem @@ -77,6 +65,8 @@ {{ t('资源标签管理') }} </span> </BkMenuItem> + </BkMenuGroup> + <BkMenuGroup :name="t('其他')"> <BkMenuItem key="resourcePoolOperationRecord" v-db-console="'resourceManage.resourceOperationRecord'"> @@ -106,5 +96,5 @@ parentKey, key: currentActiveKey, routeLocation: handleMenuChange, - } = useActiveKey(menuRef as Ref<InstanceType<typeof Menu>>, 'resourceSpec'); + } = useActiveKey(menuRef as Ref<InstanceType<typeof Menu>>, 'resourcePool'); </script> diff --git a/dbm-ui/frontend/src/locales/zh-cn.json b/dbm-ui/frontend/src/locales/zh-cn.json index 576eab8562..92882b0e32 100644 --- a/dbm-ui/frontend/src/locales/zh-cn.json +++ b/dbm-ui/frontend/src/locales/zh-cn.json @@ -3685,8 +3685,8 @@ "撤销导入": "撤销导入", "确认后,主机将从资源池移回原有模块": "确认后,主机将从资源池移回原有模块", "所属DB": "所属DB", - "移入故障池": "移入故障池", - "移入待回收池": "移入待回收池", + "转入故障池": "转入故障池", + "转入待回收池": "转入待回收池", "资源归属": "资源归属", "确认批量将 {n} 台主机转入回收池?": "确认批量将 {n} 台主机转入回收池?", "确认批量将 {n} 台主机转入故障池?": "确认批量将 {n} 台主机转入故障池?", @@ -3695,7 +3695,7 @@ "资源标签": "资源标签", "标签": "标签", "仅支持同业务的主机": "仅支持同业务的主机", - "设置属性": "设置属性", + "设置主机属性": "设置主机属性", "添加资源归属": "添加资源归属", "转为公共资源": "转为公共资源", "共n台": "共{0}台", @@ -3739,5 +3739,17 @@ "已选 IP": "已选 IP", "业务资源池": "业务资源池", "冷/热节点": "冷/热节点", + "退回公共资源池": "退回公共资源池", + "确认转入业务资源池?": "确认转入业务资源池?", + "转入业务资源池": "转入业务资源池", + "从 CMDB 的 n 业务空闲机导入": "从 CMDB 的 {0} 业务空闲机导入", + "重新设置资源归属": "重新设置资源归属", + "暂无主机,你通过以下方法获取主机至n->空闲机池->空闲机模块": "暂无主机,你通过以下方法获取主机至 “ {0} -> 空闲机池 -> 空闲机模块 “", + "确认退回公共资源池?": "确认退回公共资源池?", + "确认后,主机不再归属当前业务": "确认后,主机不再归属当前业务", + "从n业务CMDB空闲机模块导入": "从「{0}」业务 CMDB 空闲机模块导入", + "清空主机现有的所属 DB 和标签,重新进行设置": "清空主机现有的所属 DB 和标签,重新进行设置", + "清空主机现有的所属业务、所属 DB 、标签,重新进行设置": "清空主机现有的所属业务、所属 DB 、标签,重新进行设置", + "添加属性": "添加属性", "这行勿动!新增翻译请在上一行添加!": "" } diff --git a/dbm-ui/frontend/src/services/source/dbdirty.ts b/dbm-ui/frontend/src/services/source/dbdirty.ts index 1d8fdf07f0..366cc13391 100644 --- a/dbm-ui/frontend/src/services/source/dbdirty.ts +++ b/dbm-ui/frontend/src/services/source/dbdirty.ts @@ -57,7 +57,13 @@ export function deleteDirtyRecords(params: { bk_host_ids: number[] }) { /** * 故障池、待回收池列表 */ -export function getMachinePool(params: { limit?: number; offset?: number; ips?: string; pool: 'fault' | 'recycle' }) { +export function getMachinePool(params: { + limit?: number; + offset?: number; + ips?: string; + pool: 'fault' | 'recycle'; + bk_biz_id?: number; +}) { return http.get<ListBase<FaultOrRecycleMachineModel[]>>(`${path}/query_machine_pool/`, params).then((res) => ({ ...res, results: res.results.map((item: FaultOrRecycleMachineModel) => new FaultOrRecycleMachineModel(item)), diff --git a/dbm-ui/frontend/src/services/source/dbresourceResource.ts b/dbm-ui/frontend/src/services/source/dbresourceResource.ts index 11b87b633c..c4f03f0836 100644 --- a/dbm-ui/frontend/src/services/source/dbresourceResource.ts +++ b/dbm-ui/frontend/src/services/source/dbresourceResource.ts @@ -96,7 +96,7 @@ export function fetchList(params: Record<string, any>, payload = {} as IRequestP /** * 获取DBA业务下的主机信息 */ -export function fetchListDbaHost(params: { limit: number; offset: number; search_content: string }) { +export function fetchListDbaHost(params: { limit: number; offset: number; search_content: string; bk_biz_id: number }) { return http .get<{ total: number; @@ -105,6 +105,7 @@ export function fetchListDbaHost(params: { limit: number; offset: number; search search_content: params.search_content, start: params.offset, page_size: params.limit, + bk_biz_id: params.bk_biz_id, }) .then((data) => ({ count: data.total, @@ -177,7 +178,7 @@ export function getSpecResourceCount(params: { export function updateResource(params: { bk_host_ids: number[]; labels?: number[]; - for_biz: number; + for_biz?: number; rack_id: string; resource_type?: string; storage_device?: Record<string, { size: number; disk_type: string }>; @@ -212,6 +213,7 @@ export function getSummaryList(params: { machine_type?: string; cluster_type?: string; spec_id_list?: number[]; + enable_spec?: boolean; }; }) { return http.get<SummaryModel[]>(`${path}/resource_summary/`, params).then((data) => ({ @@ -219,3 +221,10 @@ export function getSummaryList(params: { results: data.map((item) => new SummaryModel(item)), })); } + +/** + * 追加主机标签 + */ +export function appendHostLabel(params: { bk_host_ids: number[]; labels: number[] }) { + return http.post(`${path}/append_labels/`, params); +} diff --git a/dbm-ui/frontend/src/services/source/tag.ts b/dbm-ui/frontend/src/services/source/tag.ts index 649ace1376..3517e6ccd9 100644 --- a/dbm-ui/frontend/src/services/source/tag.ts +++ b/dbm-ui/frontend/src/services/source/tag.ts @@ -16,6 +16,7 @@ export function listTag(params: { limit?: number; offset?: number; ordering?: string; + ids?: string; }) { return http.get<ListBase<ResourceTagModel[]>>(`${path}`, params).then((res) => ({ ...res, diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/business/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/business/Index.vue index 8ad233dfd5..3695ccb586 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/business/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/business/Index.vue @@ -14,18 +14,13 @@ <div class="pool-container"> <Teleport to="#dbContentTitleAppend"> <BkTag - class="ml-8" + class="ml-8 mr-8" theme="info"> {{ t('业务') }} </BkTag> - <BkButton - v-show="activeTab === ResourcePool.public" - class="ml-8" - theme="primary" - @click="linkToBusiness"> - <DbIcon type="add" /> - {{ t('导入主机') }} - </BkButton> + <ImportHostBtn + class="w-88" + @export-host="handleImportHost" /> </Teleport> <BkTab v-model:active="activeTab" @@ -44,6 +39,7 @@ ref="listRef" :type="activeTab" /> </div> + <ImportHost v-model:is-show="isShowImportHost" /> </div> </template> @@ -52,6 +48,8 @@ import { useDebouncedRef } from '@hooks'; + import ImportHost from '../components/host-list/components/import-host/Index.vue'; + import ImportHostBtn from '../components/host-list/components/ImportHostBtn.vue'; import HostList from '../components/host-list/Index.vue'; import { ResourcePool } from '../type'; @@ -61,6 +59,8 @@ const activeTab = useDebouncedRef(route.params.page as ResourcePool); const listRef = useTemplateRef('listRef'); + const isShowImportHost = ref(false); + const panels = [ { name: 'business', @@ -80,11 +80,9 @@ }); }; - const linkToBusiness = () => { - activeTab.value = ResourcePool.business; - setTimeout(() => { - listRef.value?.handleImportHost(); - }, 1000); + // 导入主机 + const handleImportHost = () => { + isShowImportHost.value = true; }; </script> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/fault-or-recycle-list/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/fault-or-recycle-list/Index.vue index 61a43cc329..db72c2cf0e 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/fault-or-recycle-list/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/fault-or-recycle-list/Index.vue @@ -114,7 +114,8 @@ const dataSource = (params: FaultOrRecycleMachineModel) => getMachinePool({ ...params, - pool: isFaultPool.value ? 'fault' : 'recycle' + pool: isFaultPool.value ? 'fault' : 'recycle', + bk_biz_id: undefined, }); const tableColumn = [ { diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/Index.vue index 7bc58fbd30..97e5924320 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/Index.vue @@ -27,10 +27,7 @@ </BkButton> </template> <template v-else> - <ImportHostBtn - class="w-88" - @export-host="handleImportHost" /> - <BkDropdown> + <BkDropdown :disabled="selectionHostIdList.length < 1"> <BkButton class="ml-8" :disabled="selectionHostIdList.length < 1"> @@ -39,19 +36,30 @@ </BkButton> <template #content> <BkDropdownMenu> - <BkDropdownItem @click="handleShowBatchSetting"> {{ t('设置属性') }} </BkDropdownItem> + <BkDropdownItem @click="handleShowBatchAssign"> + {{ t('重新设置资源归属') }} + </BkDropdownItem> <BkDropdownItem v-bk-tooltips="{ content: t('仅支持同业务的主机'), disabled: isSelectedSameBiz, }" :class="isSelectedSameBiz ? undefined : 'disabled-cls'" - @click="handleShowBatchAssign"> - {{ t('添加资源归属') }} + @click="handleShowBatchAddTags"> + {{ t('添加资源标签') }} + </BkDropdownItem> + <BkDropdownItem + v-if="type === 'business'" + @click="handleShowBatchCovertToPublic"> + {{ t('退回公共资源池') }} + </BkDropdownItem> + <BkDropdownItem @click="handleShowBatchSetting"> {{ t('设置主机属性') }} </BkDropdownItem> + <BkDropdownItem @click="handleShowBatchMoveToFaultPool"> {{ t('转入故障池') }} </BkDropdownItem> + <BkDropdownItem + v-if="type !== 'business'" + @click="handleShowBatchMoveToRecyclePool"> + {{ t('转入待回收池') }} </BkDropdownItem> - <BkDropdownItem @click="handleShowBatchCovertToPublic"> {{ t('转为公共资源') }} </BkDropdownItem> - <BkDropdownItem @click="handleShowBatchMoveToRecyclePool"> {{ t('移入待回收池') }} </BkDropdownItem> - <BkDropdownItem @click="handleShowBatchMoveToFaultPool"> {{ t('移入故障池') }} </BkDropdownItem> <BkDropdownItem @click="handleShowBatchUndoImport"> {{ t('撤销导入') }} </BkDropdownItem> </BkDropdownMenu> </template> @@ -97,9 +105,6 @@ @clear-search="handleClearSearch" @selection="handleSelection" @setting-change="handleSettingChange" /> - <ImportHost - v-model:is-show="isShowImportHost" - @change="handleImportHostChange" /> <BatchSetting v-model:is-show="isShowBatchSetting" :data="selectionHostIdList" @@ -108,6 +113,10 @@ v-model:is-show="isShowBatchCovertToPublic" :selected="selectionListWholeDataMemo" @refresh="handleRefresh" /> + <BatchAddTags + v-model:is-show="isShowBatchAddTags" + :selected="selectionListWholeDataMemo" + @refresh="handleRefresh" /> <BatchMoveToRecyclePool v-model:is-show="isShowBatchMoveToRecyclePool" :selected="selectionListWholeDataMemo" @@ -136,8 +145,6 @@ </div> </template> <script setup lang="tsx"> - import { Tag } from 'bkui-vue'; - import BkButton from 'bkui-vue/lib/button'; import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; @@ -155,6 +162,7 @@ import { ResourcePool } from '../../type'; + import BatchAddTags from './components/batch-add-tags/Index.vue'; import BatchAssign from './components/batch-assign/Index.vue'; import BatchConvertToBusiness from './components/batch-convert-to-business/Index.vue'; import BatchCovertToPublic from './components/batch-covert-to-public/Index.vue'; @@ -163,8 +171,6 @@ import BatchSetting from './components/batch-setting/Index.vue'; import BatchUndoImport from './components/batch-undo-import/Index.vue'; import HostOperationTip from './components/HostOperationTip.vue'; - import ImportHost from './components/import-host/Index.vue'; - import ImportHostBtn from './components/ImportHostBtn.vue'; import RenderTable from './components/RenderTable.vue'; import SearchBox from './components/search-box/Index.vue'; import UpdateAssign from './components/update-assign/Index.vue'; @@ -191,7 +197,6 @@ const tableRef = ref(); const selectionHostIdList = ref<number[]>([]); const isShowBatchSetting = ref(false); - const isShowImportHost = ref(false); const isShowBatchCovertToPublic = ref(false); const isShowBatchMoveToRecyclePool = ref(false); const isShowBatchMoveToFaultPool = ref(false); @@ -199,6 +204,7 @@ const isShowBatchConvertToBusiness = ref(false); const isShowBatchAssign = ref(false); const isShowUpdateAssign = ref(false); + const isShowBatchAddTags = ref(false); const curEditData = ref<DbResourceModel>({} as DbResourceModel); const isSelectedSameBiz = ref(false); @@ -245,29 +251,92 @@ field: 'resourceOwner', width: 320, render: ({ data }: { data: DbResourceModel }) => ( - <div class='resource-owner-wrapper'> - <div class='resource-owner'> - <Tag - theme={(data.for_biz.bk_biz_id === 0 || !data.for_biz.bk_biz_name) ? 'success' : undefined}>{t('所属业务')} : {data.forBizDisplay} - </Tag> - <Tag - theme={(!data.resource_type || data.resource_type === 'PUBLIC') ? 'success' : undefined}>{t('所属DB')} : {data.resourceTypeDisplay} - </Tag> - { - data.labels && Array.isArray(data.labels) && ( - data.labels.map(item => ( - <Tag> - {item.name} - </Tag> - )) - )} - </div> - <DbIcon - type="edit" - class='operation-icon' - onClick={() => handleEdit(data)} - /> - </div> + <bk-popover + theme="light" + placement="top" + popover-delay={[300, 0]} + disable-outside-click> + {{ + default: () => ( + <div class='resource-owner-wrapper'> + <div class='resource-owner'> + <bk-tag + theme={ + (data.for_biz.bk_biz_id === 0 || !data.for_biz.bk_biz_name) + ? 'success' + : '' + } + > + {t('所属业务')} : {data.forBizDisplay} + </bk-tag> + <bk-tag + theme={ + (!data.resource_type || data.resource_type === 'PUBLIC') + ? 'success' + : '' + } + > + {t('所属DB')} : {data.resourceTypeDisplay} + </bk-tag> + { + data.labels && Array.isArray(data.labels) && ( + data.labels.map(item => (<bk-tag>{item.name}</bk-tag>)) + )} + </div> + { + props.type !== ResourcePool.public && ( + <DbIcon + type="edit" + class='operation-icon' + onClick={() => handleEdit(data)} + /> + ) + } + </div> + ), + content: () => ( + <div class='resource-owner-tips'> + <strong>{t('所属业务')}:</strong> + <div class='resource-owner-tips-values mb-10'> + <bk-tag + theme={ + (data.for_biz.bk_biz_id === 0 || !data.for_biz.bk_biz_name) + ? 'success' + : '' + } + > + {data.forBizDisplay} + </bk-tag> + </div> + <strong>{t('所属DB')}</strong> + <div class='resource-owner-tips-values mb-10'> + <bk-tag + theme={ + (!data.resource_type || data.resource_type === 'PUBLIC') + ? 'success' + : '' + } + > + {data.resourceTypeDisplay} + </bk-tag> + </div> + { + !!data.labels.length && ( + <> + <strong>{t('资源标签')}</strong> + <div class='resource-owner-tips-values mb-10'> + { + data.labels.map(item => (<bk-tag>{item.name}</bk-tag>)) + } + </div> + </> + ) + } + + </div> + ) + }} + </bk-popover> ), }, { @@ -323,63 +392,82 @@ width: 300, fixed: 'right', render: ({ data }: { data: DbResourceModel }) => ( - props.type === ResourcePool.public ? ( - <HostOperationTip - data={data} - type="public" - onRefresh={fetchData} - tip={t('确认后,主机将标记为业务专属')} - title={t('确认转入业务资源池?')} - > - <BkButton - text - theme="primary"> - {t('转入业务资源池')} - </BkButton> - </HostOperationTip> - ) : ( - <> + <> + {props.type === ResourcePool.public && ( <HostOperationTip data={data} - title={t('确认转入待回收池?')} - tip={t('确认后,主机将标记为待回收,等待处理')} - type='to_recycle' - onRefresh={fetchData} > - <BkButton + type="to_biz" + onRefresh={fetchData} + tip={t('确认后,主机将标记为业务专属')} + title={t('确认转入业务资源池?')} + > + <bk-button text theme="primary"> - {t('移入待回收池')} - </BkButton> + {t('转入业务资源池')} + </bk-button> </HostOperationTip> - <HostOperationTip - data={data} - title={t('确认转入待故障池?')} - tip={t('确认后,主机将标记为故障,等待处理')} - type='to_fault' - onRefresh={fetchData} > - <BkButton - text - class='ml-16' - theme="primary"> - {t('移入故障池')} - </BkButton> - </HostOperationTip> - <HostOperationTip - data={data} - title={t('确认撤销导入?')} - tip={t('确认后,主机将从资源池移回原有模块')} - type='undo_import' - onRefresh={fetchData}> - <BkButton - text - class='ml-16' - theme="primary"> - {t('撤销导入')} - </BkButton> - </HostOperationTip> - </> - ) - + )} + { + [ResourcePool.business, ResourcePool.global].includes(props.type) && <> + { + props.type === ResourcePool.business ? ( + <HostOperationTip + data={data} + title={t('确认退回公共资源池?')} + tip={t('确认后,主机不再归属当前业务')} + type='to_public' + onRefresh={fetchData} > + <bk-button + text + theme="primary"> + {t('退回公共资源池')} + </bk-button> + </HostOperationTip> + ) : ( + <HostOperationTip + data={data} + title={t('确认转入待回收池?')} + tip={t('确认后,主机将标记为待回收,等待处理')} + type='to_recycle' + onRefresh={fetchData} > + <bk-button + text + theme="primary"> + {t('转入待回收池')} + </bk-button> + </HostOperationTip> + ) + } + <HostOperationTip + data={data} + title={t('确认转入待故障池?')} + tip={t('确认后,主机将标记为故障,等待处理')} + type='to_fault' + onRefresh={fetchData} > + <bk-button + text + class='ml-16' + theme="primary"> + {t('转入故障池')} + </bk-button> + </HostOperationTip> + <HostOperationTip + data={data} + title={t('确认撤销导入?')} + tip={t('确认后,主机将从资源池移回原有模块')} + type='undo_import' + onRefresh={fetchData}> + <bk-button + text + class='ml-16' + theme="primary"> + {t('撤销导入')} + </bk-button> + </HostOperationTip> + </> + } + </> ), }, ]; @@ -389,18 +477,8 @@ }; const handleSearch = (params: Record<string, any>) => { - searchParams = {...searchParams, ...params}; - tableRef.value.fetchData(searchParams); - }; - - // 导入主机 - const handleImportHost = () => { - isShowImportHost.value = true; - }; - - // 导入主机成功需要刷新列表 - const handleImportHostChange = () => { - fetchData(); + searchParams = params; + tableRef.value.fetchData(params); }; // 批量设置 @@ -487,10 +565,12 @@ isShowBatchConvertToBusiness.value = true; } + const handleShowBatchAddTags = () => { + isShowBatchAddTags.value = true; + } + const handleShowBatchAssign = () => { - if (isSelectedSameBiz.value) { - isShowBatchAssign.value = true; - } + isShowBatchAssign.value = true; } const handleEdit = (data: DbResourceModel) => { @@ -510,10 +590,6 @@ onMounted(() => { fetchData(); }); - - defineExpose({ - handleImportHost, - }); </script> <style lang="less"> .resource-pool-list-page { @@ -546,25 +622,35 @@ .operation-icon { margin-left: 7.5px; + font-size: 12px; color: #3a84ff; cursor: pointer; - font-size: 12px; visibility: hidden; } } &:hover { .operation-icon { - visibility: visible; display: block; + visibility: visible; } } } } .disabled-cls { - background-color: #f9fafd !important; - cursor: not-allowed !important; color: #dcdee5 !important; + cursor: not-allowed !important; + background-color: #f9fafd !important; + } + + .resource-owner-tips { + min-width: 280px; + padding: 9px 0 0; + color: #63656e; + + .resource-owner-tips-values { + margin: 6px 0; + } } </style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/HostOperationTip.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/HostOperationTip.vue index 0684e08802..8a7ad54cf1 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/HostOperationTip.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/HostOperationTip.vue @@ -37,13 +37,15 @@ import DbResourceModel from '@services/model/db-resource/DbResource'; import { removeResource, updateResource } from '@services/source/dbresourceResource'; + import { useGlobalBizs } from '@stores'; + import { messageSuccess } from '@utils'; interface Props { title: string; tip: string; data: DbResourceModel; - type: ServiceParameters<typeof removeResource>['event'] | 'public'; + type: ServiceParameters<typeof removeResource>['event'] | 'to_biz' | 'to_public'; } interface Emits { @@ -55,6 +57,7 @@ const emits = defineEmits<Emits>(); const { t } = useI18n(); + const globalBizsStore = useGlobalBizs(); const { run: runRemove } = useRequest(removeResource, { manual: true, @@ -73,10 +76,10 @@ }); const handleConfirm = () => { - if (props.type === 'public') { + if (['to_public', 'to_biz'].includes(props.type)) { convertToPublic({ bk_host_ids: [props.data.bk_host_id], - for_biz: props.data.bk_biz_id, + for_biz: props.type === 'to_biz' ? globalBizsStore.currentBizId : 0, rack_id: '', resource_type: props.data.resource_type, storage_device: {}, diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/ImportHostBtn.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/ImportHostBtn.vue index 78ec5c9771..6239746776 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/ImportHostBtn.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/ImportHostBtn.vue @@ -11,7 +11,10 @@ class="w-88" theme="primary" @click="handleExportHost"> - {{ t('导入') }} + <DbIcon + class="mr-6" + type="add" /> + {{ t('导入主机') }} </BkButton> </BkBadge> <template #content> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/Index.vue new file mode 100644 index 0000000000..d60c386569 --- /dev/null +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/Index.vue @@ -0,0 +1,144 @@ +<template> + <BkDialog + class="batch-assign-dialog" + :esc-close="false" + :is-show="isShow" + :quick-close="false" + render-directive="if" + :width="width" + @closed="handleCancel"> + <BkResizeLayout + :border="false" + collapsible + :initial-divide="400" + placement="right" + :style="layoutStyle"> + <template #main> + <FormPanel + ref="formPanelRef" + :biz-id="curBizId" /> + </template> + <template #aside> + <ListPanel + ref="formRef" + v-model="hostList" + :content-height="contentHeight" + @update:host-list="handleUpdate" /> + </template> + </BkResizeLayout> + <template #footer> + <div> + <span + v-bk-tooltips="{ + disabled: !!hostList.length, + content: t('请选择主机'), + }"> + <BkButton + :disabled="!hostList.length" + :loading="isUpdating" + theme="primary" + @click="handleSubmit"> + {{ t('确定') }} + </BkButton> + </span> + <BkButton + class="ml-8" + @click="handleCancel"> + {{ t('取消') }} + </BkButton> + </div> + </template> + </BkDialog> +</template> + +<script setup lang="tsx"> + import { useI18n } from 'vue-i18n'; + import { useRequest } from 'vue-request'; + + import DbResourceModel from '@services/model/db-resource/DbResource'; + import { appendHostLabel } from '@services/source/dbresourceResource'; + + import { messageSuccess } from '@utils'; + + import FormPanel from './components/FormPanel.vue'; + import ListPanel from './components/ListPanel.vue'; + + interface Props { + selected: DbResourceModel[]; + } + + interface Emits { + (e: 'refresh'): void; + } + + const props = defineProps<Props>(); + + const emits = defineEmits<Emits>(); + + const isShow = defineModel<boolean>('isShow', { + default: false, + }); + const hostList = defineModel<DbResourceModel[]>('hostList', { + default: () => [], + }); + + const { t } = useI18n(); + const formPanelRef = useTemplateRef('formPanelRef'); + + const width = Math.ceil(window.innerWidth * 0.8); + const contentHeight = Math.ceil(window.innerHeight * 0.8 - 48); + const layoutStyle = { + height: `${contentHeight}px`, + }; + + const curBizId = computed(() => hostList.value[0]?.for_biz.bk_biz_id || 0); + + const { loading: isUpdating, run: runAppend } = useRequest(appendHostLabel, { + manual: true, + onSuccess() { + emits('refresh'); + isShow.value = false; + messageSuccess('设置成功'); + }, + }); + + watch( + () => props.selected, + () => { + hostList.value = props.selected; + }, + ); + + const handleUpdate = (data: DbResourceModel[]) => { + hostList.value = data; + }; + + const handleSubmit = async () => { + const data = await formPanelRef.value!.getValue(); + runAppend({ + bk_host_ids: hostList.value.map((item) => item.bk_host_id), + labels: data.labels, + }); + }; + + const handleCancel = () => { + isShow.value = false; + }; +</script> + +<style lang="less"> + .batch-assign-dialog { + .bk-modal-header { + display: none; + } + + .bk-dialog-content { + padding: 0; + margin: 0; + } + + .bk-modal-close { + display: none !important; + } + } +</style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/FormPanel.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/FormPanel.vue new file mode 100644 index 0000000000..7c19d9fe69 --- /dev/null +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/FormPanel.vue @@ -0,0 +1,144 @@ +<!-- + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License athttps://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for + * the specific language governing permissions and limitations under the License. +--> + +<template> + <div class="batch-assign-form-panel"> + <div class="title"> + {{ t('添加资源标签') }} + </div> + <BkAlert + class="mt-12" + closable + theme="warning"> + {{ t('为主机添加资源标签,若标签不存在则添加,已存在则忽略') }} + </BkAlert> + <BkForm + ref="formRef" + class="mt-16" + form-type="vertical" + :model="formData"> + <BkFormItem + :label="t('所属业务')" + property="for_biz" + required> + <BkSelect + v-model="formData.for_biz" + :allow-empty-values="[0]" + disabled> + <BkOption + v-for="bizItem in bizList" + :key="bizItem.bk_biz_id" + :label="bizItem.display_name" + :value="bizItem.bk_biz_id" /> + </BkSelect> + </BkFormItem> + <BkFormItem + :label="t('资源标签')" + property="labels"> + <TagSelector + v-model="formData.labels" + :bk-biz-id="formData.for_biz" /> + </BkFormItem> + </BkForm> + </div> +</template> + +<script setup lang="tsx"> + import { useI18n } from 'vue-i18n'; + import { useRequest } from 'vue-request'; + + import { listTag } from '@services/source/tag'; + import type { BizItem } from '@services/types'; + + import { useGlobalBizs } from '@stores'; + + import TagSelector from '@views/resource-manage/pool/components/tag-selector/Index.vue'; + + interface Props { + bizId: number; + } + + interface Expose { + getValue: () => Promise<{ + labels: number[]; + for_biz: number; + }>; + } + + const props = defineProps<Props>(); + + const { t } = useI18n(); + const globalBizsStore = useGlobalBizs(); + const formRef = useTemplateRef('formRef'); + + const formData = reactive({ + for_biz: 0, + labels: [] as number[], + }); + + const tagList = shallowRef<ServiceReturnType<typeof listTag>['results']>([]); + + const bizList = computed(() => [ + { + bk_biz_id: 0, + display_name: t('公共资源池'), + } as BizItem, + ...globalBizsStore.bizs, + ]); + + useRequest(listTag, { + defaultParams: [ + { + bk_biz_id: props.bizId, + }, + ], + onSuccess(data) { + tagList.value = data.results; + }, + }); + + watch( + () => props.bizId, + () => { + formData.for_biz = props.bizId; + }, + { + immediate: true, + }, + ); + + defineExpose<Expose>({ + getValue() { + return formRef.value!.validate().then(() => ({ + for_biz: Number(formData.for_biz), + labels: formData.labels, + })); + }, + }); +</script> + +<style lang="less"> + .batch-assign-form-panel { + padding: 16px 24px; + + .title { + font-size: 20px; + line-height: 28px; + color: #313238; + } + + .search-input { + margin: 14px 0 12px; + } + } +</style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/ListPanel.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/ListPanel.vue new file mode 100644 index 0000000000..ff0aa526fa --- /dev/null +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-add-tags/components/ListPanel.vue @@ -0,0 +1,249 @@ +<!-- + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License athttps://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for + * the specific language governing permissions and limitations under the License. +--> + +<template> + <div class="batch-assign-list-panel"> + <div class="title"> + <span> + {{ t('已选主机') }} + </span> + <BkPopover + :arrow="false" + :is-show="isShowHostActionPop" + placement="bottom" + theme="light export-host-action-extends" + trigger="manual"> + <div + class="host-action" + :class="{ + active: isShowHostActionPop, + }" + @blur="handleHideHostAction" + @click="handleShowHostAction"> + <DbIcon type="more" /> + </div> + <template #content> + <div + class="item" + @click="handleCopyAll"> + {{ t('复制所有 IP') }} + </div> + </template> + </BkPopover> + </div> + <div class="host-header"> + <DbIcon + class="mr-6" + type="down-big" /> + <I18nT keypath="共n台"> + <span + class="number" + style="color: #3a84ff"> + {{ hostList.length }} + </span> + </I18nT> + </div> + <div class="host-list"> + <div + v-for="hostItem in hostList" + :key="hostItem.bk_host_id" + class="host-item"> + <div>{{ hostItem.ip }}</div> + <div class="action-box"> + <DbIcon + v-bk-tooltips="t('复制')" + type="copy" + @click="handleCopy(hostItem)" /> + </div> + </div> + <BkException + v-if="hostList.length < 1" + :description="t('暂无数据')" + scene="part" + type="empty" /> + </div> + </div> +</template> +<script setup lang="ts"> + import { reactive } from 'vue'; + import { useI18n } from 'vue-i18n'; + + import DbResourceModel from '@services/model/db-resource/DbResource'; + + import { useCopy } from '@hooks'; + + import { messageWarn } from '@utils'; + + interface Expose { + getValue: () => Promise<any>; + } + + const hostList = defineModel<DbResourceModel[]>({ + default: () => [], + }); + + const copy = useCopy(); + const { t } = useI18n(); + + const formRef = ref(); + const isShowHostActionPop = ref(false); + const formData = reactive({ + for_biz: '', + resource_type: '', + labels: '', + }); + + const handleShowHostAction = () => { + isShowHostActionPop.value = true; + }; + + const handleHideHostAction = () => { + isShowHostActionPop.value = false; + }; + + // 复制所有主机 IP + const handleCopyAll = () => { + const ipList = hostList.value.map((item) => item.ip); + isShowHostActionPop.value = false; + if (!ipList.length) { + messageWarn(t('暂无可复制 IP')); + return; + } + + copy(ipList.join('\n')); + }; + + // 复制单个指定主机 IP + const handleCopy = (hostItem: DbResourceModel) => { + copy(hostItem.ip); + }; + + defineExpose<Expose>({ + getValue() { + return formRef.value.validate().then(() => ({ + for_biz: Number(formData.for_biz), + resource_type: formData.resource_type, + labels: formData.labels, + })); + }, + }); +</script> +<style lang="less"> + .batch-assign-list-panel { + display: flex; + height: 100%; + background: #f5f6fa; + flex-direction: column; + + .title { + display: flex; + padding: 8px 12px 10px 24px; + font-size: 12px; + font-weight: 700; + color: #313238; + background: #fff; + border: 1px solid #dcdee5; + border-radius: 0 2px 2px 0; + + .host-action { + display: flex; + width: 20px; + height: 20px; + margin-left: auto; + cursor: pointer; + border-radius: 2px; + transition: all 0.15s; + align-items: center; + justify-content: center; + + &.active, + &:hover { + background: #e1ecff; + } + } + } + + .host-header { + display: flex; + padding: 0 24px; + margin-top: 14px; + margin-bottom: 4px; + font-size: 12px; + line-height: 24px; + color: #63656e; + align-items: center; + } + + .host-list { + padding: 0 24px; + overflow-y: auto; + font-size: 12px !important; + + .host-item { + display: flex; + height: 32px; + padding: 0 12px; + line-height: 1; + color: #63656e; + background-color: #fff; + border-radius: 2px; + transition: all 0.15s; + align-items: center; + + & ~ .host-item { + margin-top: 2px; + } + + &:hover { + background-color: #e1ecff; + + .action-box { + display: flex; + } + } + + .action-box { + display: none; + margin-left: auto; + color: #3a84ff; + align-items: center; + + i { + padding: 0 2px; + cursor: pointer; + } + } + } + } + } + + [data-theme~='export-host-action-extends'] { + padding: 8px 0 !important; + + .item { + display: flex; + height: 32px; + padding: 0 12px; + font-size: 12px; + color: #63656e; + cursor: pointer; + transition: all 0.15s; + align-items: center; + + &:hover { + color: #3a84ff; + background-color: #e1ecff; + } + } + } +</style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/FormPanel.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/FormPanel.vue index 8632b86401..d8bcba16cd 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/FormPanel.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/FormPanel.vue @@ -14,13 +14,17 @@ <template> <div class="batch-assign-form-panel"> <div class="title"> - {{ t('批量添加资源归属') }} + {{ t('重新设置资源归属') }} </div> <BkAlert class="mt-12" closable theme="warning"> - {{ t('为主机添加或更新所属 DB、标签设置,若设置不存在则添加,已存在则覆盖更新') }} + {{ + isBusiness + ? t('清空主机现有的所属 DB 和标签,重新进行设置') + : t('清空主机现有的所属业务、所属 DB 、标签,重新进行设置') + }} </BkAlert> <BkForm ref="formRef" @@ -34,7 +38,7 @@ <BkSelect v-model="formData.for_biz" :allow-empty-values="[0]" - disabled> + :disabled="isBusiness"> <BkOption v-for="bizItem in bizList" :key="bizItem.bk_biz_id" @@ -91,6 +95,8 @@ const props = defineProps<Props>(); const { t } = useI18n(); + const route = useRoute(); + const formRef = useTemplateRef('formRef'); const formData = reactive({ @@ -103,6 +109,8 @@ const dbTypeList = shallowRef<ServiceReturnType<typeof fetchDbTypeList>>([]); const tagList = shallowRef<ServiceReturnType<typeof listTag>['results']>([]); + const isBusiness = route.name === 'BizResourcePool'; + useRequest(getBizs, { onSuccess(data) { bizList.value = [ @@ -138,16 +146,6 @@ }, }); - watch( - () => props.bizId, - () => { - formData.for_biz = props.bizId; - }, - { - immediate: true, - }, - ); - defineExpose<Expose>({ getValue() { return formRef.value!.validate().then(() => ({ diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/ListPanel.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/ListPanel.vue index 9915a0362b..ff0aa526fa 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/ListPanel.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-assign/components/ListPanel.vue @@ -64,11 +64,6 @@ v-bk-tooltips="t('复制')" type="copy" @click="handleCopy(hostItem)" /> - <DbIcon - v-bk-tooltips="t('删除')" - style="font-size: 16px" - type="close" - @click="handleRemove(hostItem)" /> </div> </div> <BkException @@ -133,18 +128,6 @@ copy(hostItem.ip); }; - // 删除单个主机 - const handleRemove = (hostItem: DbResourceModel) => { - const hostListResult = hostList.value.reduce<DbResourceModel[]>((result, item) => { - if (item.bk_host_id !== hostItem.bk_host_id) { - result.push(item); - } - return result; - }, []); - - hostList.value = hostListResult; - }; - defineExpose<Expose>({ getValue() { return formRef.value.validate().then(() => ({ @@ -163,14 +146,14 @@ flex-direction: column; .title { + display: flex; padding: 8px 12px 10px 24px; - font-weight: 700; font-size: 12px; + font-weight: 700; color: #313238; - background: #ffffff; + background: #fff; border: 1px solid #dcdee5; border-radius: 0 2px 2px 0; - display: flex; .host-action { display: flex; @@ -192,13 +175,13 @@ .host-header { display: flex; - align-items: center; padding: 0 24px; margin-top: 14px; margin-bottom: 4px; + font-size: 12px; line-height: 24px; color: #63656e; - font-size: 12px; + align-items: center; } .host-list { diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-convert-to-business/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-convert-to-business/Index.vue index 69284083cb..9a9277d201 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-convert-to-business/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-convert-to-business/Index.vue @@ -65,8 +65,8 @@ bk_host_ids: props.selected.map((item) => item.bk_host_id), for_biz: props.bizId, rack_id: props.selected[0].rack_id, - resource_type: '', storage_device: props.selected[0].storage_device, + labels: [], }); }; diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-covert-to-public/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-covert-to-public/Index.vue index 0fd8bf3eab..4b7001e0f5 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-covert-to-public/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-covert-to-public/Index.vue @@ -66,8 +66,8 @@ bk_host_ids: props.selected.map((item) => item.bk_host_id), for_biz: 0, rack_id: '', - resource_type: 'PUBLIC', storage_device: {}, + labels: [], }); }; diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-setting/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-setting/Index.vue index 7416d5387f..f44d483c74 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-setting/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/batch-setting/Index.vue @@ -4,7 +4,7 @@ :width="800" @update:is-show="handleCancel"> <template #header> - <span>{{ t('设置业务专用') }}</span> + <span>{{ t('设置主机属性') }}</span> <span style="margin-left: 12px; font-size: 12px; color: #63656e"> <I18nT keypath="已选:n台主机"> <span class="number">{{ data.length }}</span> @@ -13,47 +13,49 @@ </template> <div class="resource-pool-batch-setting"> <div class="mb-36"> + <BkSelect + v-model="selectedOptions" + class="mb-16 setting-item-selector" + multiple> + <template #trigger> + <BkButton + text + theme="primary"> + <DbIcon type="plus-circle" /> {{ t('添加属性') }} + </BkButton> + </template> + <BkOption + v-for="item in SETTING_OPTIONS" + :key="item.value" + :label="item.label" + :value="item.value" /> + </BkSelect> <DbForm ref="formRef" form-type="vertical" :model="formData"> - <DbFormItem - :label="t('所属业务')" - property="for_biz"> - <div class="com-input"> - <BkSelect - v-model="formData.for_biz" - :allow-empty-values="[0]" - filterable> - <BkOption - v-for="bizItem in bizList" - :key="bizItem.bk_biz_id" - :label="bizItem.display_name" - :value="bizItem.bk_biz_id" /> - </BkSelect> - </div> - </DbFormItem> - <DbFormItem - :label="t('所属DB类型')" - property="resource_type"> - <div class="com-input"> - <BkSelect - v-model="formData.resource_type" - filterable> - <BkOption - v-for="item in dbTypeList" - :key="item.id" - :label="item.name" - :value="item.id" /> - </BkSelect> - </div> - </DbFormItem> - <DbFormItem :label="t('磁盘')"> - <ResourceSpecStorage v-model="formData.storage_spec" /> - </DbFormItem> - <DbFormItem :label="t('机架')"> - <BkInput v-model="formData.rack_id" /> - </DbFormItem> + <div + v-if="selectedOptions.includes('storage_spec')" + class="mb-16 setting-item"> + <DbIcon + class="close-icon" + type="close" + @click.stop="() => handleDelete('storage_spec')" /> + <DbFormItem :label="t('磁盘')"> + <ResourceSpecStorage v-model="formData.storage_spec" /> + </DbFormItem> + </div> + <div + v-if="selectedOptions.includes('rack_id')" + class="mb-16 setting-item"> + <DbIcon + class="close-icon" + type="close" + @click.stop="() => handleDelete('rack_id')" /> + <DbFormItem :label="t('机架')"> + <BkInput v-model="formData.rack_id" /> + </DbFormItem> + </div> </DbForm> </div> </div> @@ -74,7 +76,7 @@ </DbSideslider> </template> <script setup lang="ts"> - import { computed, reactive, ref } from 'vue'; + import { reactive, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRequest } from 'vue-request'; @@ -99,14 +101,13 @@ } const props = defineProps<Props>(); + const emits = defineEmits<Emits>(); const { t } = useI18n(); const genDefaultData = () => ({ - for_biz: undefined, rack_id: '', - resource_type: '', storage_spec: [] as IStorageSpecItem[], }); @@ -115,11 +116,22 @@ const bizList = shallowRef<ServiceReturnType<typeof getBizs>>([]); const dbTypeList = shallowRef<ServiceReturnType<typeof fetchDbTypeList>>([]); + const selectedOptions = ref<string[]>([]); + const formData = reactive(genDefaultData()); - const isSubmitDisabled = computed( - () => !(formData.for_biz !== '' || formData.resource_type || formData.storage_spec.length > 0), - ); + const isSubmitDisabled = computed(() => !(formData.storage_spec.length > 0 || formData.rack_id)); + + const SETTING_OPTIONS = [ + { + label: t('磁盘'), + value: 'storage_spec', + }, + { + label: t('机架'), + value: 'rack_id', + }, + ]; useRequest(getBizs, { onSuccess(data) { @@ -144,6 +156,15 @@ }, }); + watch( + () => props.isShow, + () => { + if (props.isShow) { + selectedOptions.value = []; + } + }, + ); + const handleSubmit = () => { isSubmiting.value = true; formRef.value @@ -161,22 +182,10 @@ ); const params = { bk_host_ids: props.data.map((item) => ~~item), - for_biz: formData.for_biz, rack_id: formData.rack_id, storage_device: storageDevice, }; - if (formData.rack_id !== '') { - Object.assign(params, { rack_id: formData.rack_id }); - } - if (Object.values(storageDevice).length > 0) { - Object.assign(params, { storage_device: storageDevice }); - } - if (formData.for_biz !== '') { - Object.assign(params, { for_biz: Number(formData.for_biz) }); - } - if (formData.resource_type !== '') { - Object.assign(params, { resource_type: formData.resource_type }); - } + return updateResource(params).then(() => { window.changeConfirm = false; emits('change'); @@ -188,6 +197,11 @@ }); }; + const handleDelete = (value: 'storage_spec' | 'rack_id') => { + selectedOptions.value = selectedOptions.value.filter((item) => item !== value); + formData[value] = undefined; + }; + const handleCancel = () => { leaveConfirm().then(() => { emits('update:isShow', false); @@ -210,5 +224,31 @@ flex: 1; } } + + .setting-item-selector { + width: 352px; + } + + .setting-item { + position: relative; + + .close-icon { + position: absolute; + top: 10px; + right: 10px; + visibility: hidden; + } + + &:hover { + padding: 6px; + background-color: #f0f1f5; + + .close-icon { + z-index: 99; + cursor: pointer; + visibility: visible; + } + } + } } </style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/FormPanel.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/FormPanel.vue index 00787722ca..d7440c4769 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/FormPanel.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/FormPanel.vue @@ -101,6 +101,7 @@ <BkSelect v-model="formData.for_biz" :allow-empty-values="[0]" + :disabled="isBusiness" filterable> <BkOption v-for="bizItem in bizList" @@ -151,6 +152,8 @@ import { useCopy } from '@hooks'; + import { useGlobalBizs } from '@stores'; + import TagSelector from '@views/resource-manage/pool/components/tag-selector/Index.vue'; import { messageWarn } from '@utils'; @@ -170,11 +173,15 @@ const copy = useCopy(); const { t } = useI18n(); + const route = useRoute(); + const globalBizsStore = useGlobalBizs(); + + const isBusiness = route.name === 'BizResourcePool'; const formRef = ref(); const isShowHostActionPop = ref(false); const formData = reactive({ - for_biz: 0, + for_biz: isBusiness ? globalBizsStore.currentBizId : 0, resource_type: '', labels: [], }); diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/Index.vue index 907f21f3c2..5908803270 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/Index.vue @@ -15,8 +15,32 @@ <div class="export-host-select-panel"> <div class="title"> {{ t('导入主机') }} - <span style="font-size: 12px; color: #979ba5"> - {{ t('(从 CMDB 的 DBA 业务空闲机导入)') }} + <BusinessSelector + v-if="!isBusiness" + v-model="bizId"> + <template #trigger> + <span style="font-size: 12px; color: #979ba5; cursor: pointer"> + ( + <I18nT + keypath="从n业务CMDB空闲机模块导入" + tag="span"> + {{ globalBizsStore.bizIdMap.get(bizId)?.name }} + </I18nT> + ) + <DbIcon type="down-big" /> + </span> + </template> + </BusinessSelector> + <span + v-else + style="font-size: 12px; color: #979ba5"> + ( + <I18nT + keypath="从n业务CMDB空闲机模块导入" + tag="span"> + {{ globalBizsStore.bizIdMap.get(bizId)?.name }} + </I18nT> + ) </span> </div> <BkInput @@ -43,7 +67,7 @@ <template v-if="!searchContent" #empty> - <HostEmpty /> + <HostEmpty :bk-biz-id="bizId" /> </template> </DbTable> </div> @@ -59,8 +83,12 @@ import { fetchListDbaHost } from '@services/source/dbresourceResource'; import type { HostInfo } from '@services/types'; + import { useGlobalBizs, useSystemEnviron } from '@stores'; + import DbStatus from '@components/db-status/index.vue'; + import BusinessSelector from '@views/tag-manage/components/BusinessSelector.vue'; + import HostEmpty from './components/HostEmpty.vue'; interface Props { @@ -73,11 +101,17 @@ const props = defineProps<Props>(); const emits = defineEmits<Emits>(); + const route = useRoute(); + const globalBizsStore = useGlobalBizs(); + const systemEnvironStore = useSystemEnviron(); + + const isBusiness = route.name === 'BizResourcePool'; const { t } = useI18n(); const tableRef = ref(); const searchContent = ref(''); + const bizId = ref(isBusiness ? globalBizsStore.currentBizId : systemEnvironStore.urls.DBA_APP_BK_BIZ_ID); const tableColumn = [ { @@ -89,7 +123,7 @@ { label: 'IPV6', field: 'ipv6', - render: ({ data }: { data: HostInfo}) => data.ipv6 || '--', + render: ({ data }: { data: HostInfo }) => data.ipv6 || '--', }, { label: t('管控区域'), @@ -98,7 +132,7 @@ { label: t('Agent 状态'), field: 'agent', - render: ({ data }: { data: HostInfo}) => { + render: ({ data }: { data: HostInfo }) => { const info = data.alive === 1 ? { theme: 'success', text: t('正常') } : { theme: 'danger', text: t('异常') }; return <DbStatus theme={info.theme}>{info.text}</DbStatus>; }, @@ -129,6 +163,18 @@ }); }); + watch(bizId, () => { + fetchData(); + } + ); + + const fetchData = () => { + tableRef.value.fetchData({ + search_content: searchContent.value, + bk_biz_id: bizId.value, + }); + }; + const disableSelectMethod = (data: HostInfo) => { if (data.alive !== 1) { return t('异常主机不可用'); @@ -138,11 +184,6 @@ } return false; }; - const fetchData = () => { - tableRef.value.fetchData({ - search_content: searchContent.value, - }); - }; const handleSearch = () => { fetchData(); @@ -166,9 +207,11 @@ padding: 16px 24px; .title { + display: flex; font-size: 20px; line-height: 28px; color: #313238; + align-items: center; } .search-input { diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/components/HostEmpty.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/components/HostEmpty.vue index 42e71f0c9e..95c059f5aa 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/components/HostEmpty.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/import-host/components/select-host-panel/components/HostEmpty.vue @@ -18,7 +18,9 @@ type="empty" /> <div style="display: inline-block; text-align: left"> <div class="mb-12"> - {{ t('暂无主机,你通过以下方法获取主机至“DBA 业务 -> 空闲机池 -> 空闲机模块”') }} + <I18nT keypath="暂无主机,你通过以下方法获取主机至n->空闲机池->空闲机模块"> + {{ globalBizsStore.bizIdMap.get(bkBizId)?.name || bkBizId }} + </I18nT> </div> <div> <I18nT keypath="方法一:从CMDB 资源池分配;"> @@ -56,7 +58,16 @@ import { fetchResourceImportUrls } from '@services/source/dbresourceResource'; + import { useGlobalBizs } from '@stores'; + + interface Props { + bkBizId: number; + } + + defineProps<Props>(); + const { t } = useI18n(); + const globalBizsStore = useGlobalBizs(); const { data } = useRequest(fetchResourceImportUrls); </script> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/com-factory/components/Label.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/com-factory/components/Label.vue index 2a1ca49b99..5c23a08572 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/com-factory/components/Label.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/com-factory/components/Label.vue @@ -5,6 +5,7 @@ :model-value="selected" multiple multiple-mode="tag" + :remote-method="handleSearch" :scroll-loading="isLoading" selected-style="checkbox" @change="handleChange" @@ -18,6 +19,7 @@ </template> <script setup lang="tsx"> + import { uniqBy } from 'lodash'; import { useRequest } from 'vue-request'; import type DbResource from '@services/model/db-resource/DbResource'; @@ -37,6 +39,7 @@ const emits = defineEmits<Emits>(); + const searchVal = ref(''); const tagList = ref<ServiceReturnType<typeof listTag>['results']>([]); const selected = ref<DbResource['labels'][number]['id'][]>([]); const pagination = reactive({ @@ -49,15 +52,21 @@ manual: true, onSuccess(data) { pagination.count = data.count; - tagList.value.push(...data.results); + tagList.value = uniqBy([...tagList.value, ...data.results], 'value'); }, }); + const { runAsync: runAsyncList } = useRequest(listTag); + watch( () => props.defaultValue, - () => { + async () => { if (props.defaultValue) { selected.value = props.defaultValue.split(',').map((v) => +v); + const { results } = await runAsyncList({ + ids: props.defaultValue, + }); + tagList.value = uniqBy([...tagList.value, ...results], 'value'); } }, { @@ -66,18 +75,35 @@ }, ); + watch(searchVal, () => { + pagination.offset = 0; + pagination.count = 0; + tagList.value = []; + runList({ + ...pagination, + value: searchVal.value, + }); + }); + const loadMore = () => { - if (tagList.value.length >= pagination.count || isLoading.value) { + if (pagination.offset >= pagination.count || isLoading.value) { return; } - pagination.offset += pagination.limit; - runList(pagination); + pagination.offset = Math.min(pagination.count, pagination.offset + pagination.limit); + runList({ + ...pagination, + value: searchVal.value, + }); }; const handleChange = (value: string[]) => { emits('change', value.join(',')); }; + const handleSearch = (val: string) => { + searchVal.value = val; + }; + onMounted(() => { runList(pagination); }); diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/field-input/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/field-input/Index.vue index e9bf3bba78..92d7a8ff81 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/field-input/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/search-box/components/field-input/Index.vue @@ -15,6 +15,7 @@ <div class="search-field-input"> <div class="row"> <ComFactory + v-if="!isBusiness" :ref="(el: any) => initInputRefCallback(el, 'for_biz')" :model="localValueMemo" name="for_biz" @@ -25,9 +26,9 @@ name="resource_type" @change="handleChange" /> <ComFactory - :ref="(el: any) => initInputRefCallback(el, 'bk_cloud_ids')" + :ref="(el: any) => initInputRefCallback(el, 'label')" :model="localValueMemo" - name="bk_cloud_ids" + name="labels" @change="handleChange" /> <ComFactory :ref="(el: any) => initInputRefCallback(el, 'agent_status')" @@ -71,11 +72,10 @@ name="mem" @change="handleChange" /> <ComFactory - :ref="(el: any) => initInputRefCallback(el, 'label')" + :ref="(el: any) => initInputRefCallback(el, 'bk_cloud_ids')" :model="localValueMemo" - name="labels" + name="bk_cloud_ids" @change="handleChange" /> - <div style="flex: 1" /> </div> <div class="row"> <ComFactory @@ -156,6 +156,7 @@ const emits = defineEmits<Emits>(); const { t } = useI18n(); + const route = useRoute(); const isShowMore = ref(false); const inputRef = shallowRef<Record<string, typeof ComFactory>>({}); @@ -164,11 +165,12 @@ os_type: true, cpu: true, mem: true, - labels: true, + bk_cloud_ids: true, mount_point: true, disk: true, disk_type: true, }; + const isBusiness = route.name === 'BizResourcePool'; const initInputRefCallback = (com: typeof ComFactory, name: string) => { inputRef.value[name] = com; diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-research/selector/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-research/selector/Index.vue index 3840ec85b4..8fe8ff4fe0 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-research/selector/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-research/selector/Index.vue @@ -84,9 +84,10 @@ <style scoped lang="less"> .tag-research-selector { width: 150; + .trigger-btn { - margin-left: 8px; width: 150px; + margin-left: 8px; } } </style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-selector/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-selector/Index.vue new file mode 100644 index 0000000000..1fa30ebc9c --- /dev/null +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/tag-selector/Index.vue @@ -0,0 +1,273 @@ +<template> + <BkSelect + v-model="modelValue" + :disabled="disabled" + multiple + multiple-mode="tag" + :remote-method="handleSearch" + :scroll-loading="listTagLoading" + @scroll-end="loadMore"> + <BkOption + v-for="item in tagList" + :key="item.id" + :label="item.value" + :value="item.id" /> + <template #extension> + <div + v-if="isEdit" + class="editor-wrapper"> + <BkInput + v-model="tagValue" + class="editor" + @blur="handleClose" /> + <div + class="operator-wrapper" + @click="handleCreate"> + <DbIcon + class="check-line" + type="check-line" /> + </div> + <div + class="operator-wrapper" + @click="handleClose"> + <DbIcon + class="close" + type="close" /> + </div> + </div> + <div + v-else + class="operation-wrapper"> + <div + class="create-tag" + @click.stop="handleEdit"> + <DbIcon + class="icon" + type="plus-circle" /> + <span class="ml-2">{{ t('新建标签') }}</span> + </div> + <BkDivider + direction="vertical" + type="solid" /> + <div + class="link-to-manage" + @click.stop="handleLink"> + <DbIcon + class="icon" + type="link" /> + <span class="ml-2">{{ t('跳转管理页') }}</span> + </div> + </div> + </template> + </BkSelect> +</template> + +<script setup lang="tsx"> + import { uniqBy } from 'lodash'; + import { useI18n } from 'vue-i18n'; + import { useRequest } from 'vue-request'; + + import type DbResourceModel from '@services/model/db-resource/DbResource'; + import { createTag, listTag } from '@services/source/tag'; + + import { messageSuccess } from '@utils'; + + interface Props { + bkBizId: number; + disabled?: boolean; + defaultList?: DbResourceModel['labels']; + } + + const props = defineProps<Props>(); + const modelValue = defineModel<number[]>({ + default: () => [], + }); + + const { t } = useI18n(); + const router = useRouter(); + const route = useRoute(); + + const isEdit = ref(false); + const tagValue = ref(''); + const searchVal = ref(''); + const tagList = ref<ServiceReturnType<typeof listTag>['results']>([]); + const pagination = reactive({ + offset: 0, + limit: 10, + count: 0, + }); + + const isBusiness = route.name === 'BizResourcePool'; + + const { run: runListTag, loading: listTagLoading } = useRequest(listTag, { + manual: true, + onSuccess(data) { + pagination.count = data.count; + tagList.value = uniqBy([...tagList.value, ...data.results], 'value'); + }, + }); + + const { run: runCreate } = useRequest(createTag, { + manual: true, + onSuccess() { + pagination.count += 1; + loadMore(); + isEdit.value = false; + messageSuccess(t('新建成功')); + }, + }); + + const loadMore = () => { + if (listTagLoading.value) { + return; + } + if (tagList.value.length >= pagination.count) { + return; + } + pagination.offset = Math.min(pagination.count, pagination.offset + pagination.limit); + runListTag({ + bk_biz_id: props.bkBizId, + offset: pagination.offset, + limit: pagination.limit, + value: searchVal.value, + }); + }; + + watch( + () => props.bkBizId, + () => { + modelValue.value = []; + tagList.value = []; + runListTag({ + bk_biz_id: props.bkBizId, + }); + }, + ); + + watch(searchVal, () => { + pagination.offset = 0; + pagination.count = 0; + initTagList(); + runListTag({ + bk_biz_id: props.bkBizId, + offset: pagination.offset, + limit: pagination.limit, + value: searchVal.value, + }); + }); + + const handleEdit = () => { + isEdit.value = true; + }; + + const handleClose = () => { + isEdit.value = false; + }; + + const handleCreate = () => { + runCreate({ + bk_biz_id: props.bkBizId, + tags: [ + { + key: 'dbresource', + value: tagValue.value, + }, + ], + }); + }; + + const handleLink = () => { + const route = router.resolve({ + name: isBusiness ? 'BizResourceTag' : 'resourceTagsManagement', + }); + window.open(route.href); + }; + + const handleSearch = (val: string) => { + searchVal.value = val; + }; + + const initTagList = () => { + if (props.defaultList?.length) { + tagList.value = props.defaultList.map((item) => ({ + id: item.id, + value: item.name, + })) as ServiceReturnType<typeof listTag>['results']; + } else { + tagList.value = []; + } + }; + + onMounted(() => { + initTagList(); + runListTag({ + bk_biz_id: props.bkBizId, + offset: 0, + limit: pagination.limit, + }); + }); +</script> + +<style scoped lang="less"> + .operation-wrapper { + display: flex; + align-items: center; + justify-content: space-around; + width: 100%; + + .icon { + width: 14px; + height: 14px; + color: #979ba5; + } + + .create-tag { + cursor: pointer; + } + + .link-to-manage { + cursor: pointer; + } + } + + .editor-wrapper { + display: flex; + align-items: center; + width: 100%; + padding: 8px; + + .editor { + flex: 1; + } + + .operator-wrapper { + display: flex; + width: 32px; + height: 32px; + align-items: center; + border-radius: 16px; + justify-content: center; + + &:hover { + cursor: pointer; + background-color: #e1ecff; + } + + .check-line { + width: 13px; + height: 9.31px; + margin-right: 12.5px; + margin-left: 12.5px; + color: #2dcb56; + cursor: pointer; + } + + .close { + width: 10px; + height: 10px; + color: #979ba5; + cursor: pointer; + } + } + } +</style> diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/update-assign/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/update-assign/Index.vue index 26365e0b31..0c307053af 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/update-assign/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/host-list/components/update-assign/Index.vue @@ -16,7 +16,8 @@ required> <BkSelect v-model="formData.for_biz" - :allow-empty-values="[0]"> + :allow-empty-values="[0]" + :disabled="isBusiness"> <BkOption v-for="bizItem in bizList" :key="bizItem.bk_biz_id" @@ -72,6 +73,8 @@ import { fetchDbTypeList } from '@services/source/infras'; import type { BizItem } from '@services/types'; + import { useGlobalBizs } from '@stores'; + import TagSelector from '@views/resource-manage/pool/components/tag-selector/Index.vue'; interface Props { @@ -92,15 +95,19 @@ const { t } = useI18n(); const formRef = useTemplateRef('formRef'); + const globalBizsStore = useGlobalBizs(); + const route = useRoute(); const formData = reactive({ - for_biz: 0, + for_biz: globalBizsStore.currentBizId, resource_type: '', labels: [] as DbResourceModel['labels'][number]['id'][], }); const bizList = shallowRef<ServiceReturnType<typeof getBizs>>([]); const dbTypeList = shallowRef<ServiceReturnType<typeof fetchDbTypeList>>([]); + const isBusiness = route.name === 'BizResourcePool'; + watch( () => props.editData, () => { diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/DimensionSelect.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/DimensionSelect.vue index b5e7243190..f1729cd2f8 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/DimensionSelect.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/DimensionSelect.vue @@ -24,6 +24,13 @@ </BkRadio> </BkRadioGroup> </div> + <div class="check-box-wrapper"> + <BkCheckbox + v-model="isSpecEnable" + class="mr-6" + @change="changeSpecEnable" /> + {{ t('仅统计已启用的规格') }} + </div> </div> </template> @@ -32,6 +39,7 @@ interface Emits { (e: 'change', value: string): void; + (e: 'changeSpecEnable', value: boolean): void; } const emits = defineEmits<Emits>(); @@ -56,6 +64,7 @@ const selectTriggerRef = ref(); const selectWrapperRef = ref(); const showSelectWrapper = ref(false); + const isSpecEnable = ref(true); const renderLabel = computed(() => dimensions.find((item) => item.value === modelValue.value)?.label as string); @@ -63,6 +72,10 @@ showSelectWrapper.value = false; emits('change', value); }; + + const changeSpecEnable = (value: boolean) => { + emits('changeSpecEnable', value); + }; </script> <style lang="less" scoped> @@ -71,6 +84,14 @@ margin: 16px 0; align-items: center; + .check-box-wrapper { + display: flex; + margin-right: 21px; + font-size: 12px; + color: #4d4f56; + align-items: center; + } + .select-main { padding: 0 6px; diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/List.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/List.vue index 8525ee3814..77fda15023 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/List.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/summary-view/components/List.vue @@ -6,7 +6,8 @@ <div class="opearte-row"> <DimensionSelect v-model="dimension" - @change="handleChangeDimension" /> + @change="handleChangeDimension" + @change-spec-enable="handleChangeSpecEnable" /> <Export :data="allTableData" :dimension="dimension" /> @@ -108,6 +109,7 @@ const loadingRef = ref(); const dimension = ref('spec'); + const isSpecEnable = ref(true); const tableRef = ref(); const pagination = ref(useDefaultPagination()); const isAnomalies = ref(false); @@ -138,6 +140,9 @@ const fetchListData = () => { fetchData({ group_by: dimension.value, + spec_param: { + enable_spec: isSpecEnable.value, + }, ...getSearchParams(), } as ServiceParameters<typeof getSummaryList>); }; @@ -148,6 +153,12 @@ fetchListData(); }; + const handleChangeSpecEnable = (value: boolean) => { + isSpecEnable.value = value; + handleChangePage(1); + fetchListData(); + }; + const handleChangePage = (value: number) => { pagination.value.current = value; tableRef.value.scrollTo(0, 0); diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/components/tag-selector/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/components/tag-selector/Index.vue index 87e67fa1a1..1a672c5469 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/components/tag-selector/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/components/tag-selector/Index.vue @@ -108,14 +108,16 @@ const { run: runCreate } = useRequest(createTag, { manual: true, - onSuccess() { - if (pagination.count === tagList.value.length) { - runListTag({ - bk_biz_id: props.bkBizId, - offset: tagList.value.length, - ordering: 'create_at', - }); - } + async onSuccess() { + const data = await listTag({ + bk_biz_id: props.bkBizId, + offset: pagination.count, + limit: 1, + ordering: 'create_at', + }); + tagList.value = uniqBy([...tagList.value, ...data.results], 'value'); + pagination.count = data.count; + modelValue.value = [...modelValue.value, data.results[0].id]; handleClose(); messageSuccess(t('新建成功')); }, diff --git a/dbm-ui/frontend/src/views/resource-manage/pool/global/Index.vue b/dbm-ui/frontend/src/views/resource-manage/pool/global/Index.vue index 20f57ada7f..604392e134 100644 --- a/dbm-ui/frontend/src/views/resource-manage/pool/global/Index.vue +++ b/dbm-ui/frontend/src/views/resource-manage/pool/global/Index.vue @@ -15,10 +15,13 @@ <div> <Teleport to="#dbContentTitleAppend"> <BkTag - class="ml-8" + class="ml-8 mr-8" theme="info"> {{ t('全局') }} </BkTag> + <ImportHostBtn + class="w-88" + @export-host="handleImportHost" /> </Teleport> <BkTab v-model:active="activeTab" @@ -35,6 +38,7 @@ <KeepAlive> <Component :is="renderComponent" /> </KeepAlive> + <ImportHost v-model:is-show="isShowImportHost" /> </div> </div> </template> @@ -44,6 +48,8 @@ import { useDebouncedRef } from '@hooks'; + import ImportHost from '../components/host-list/components/import-host/Index.vue'; + import ImportHostBtn from '../components/host-list/components/ImportHostBtn.vue'; import HostList from '../components/host-list/Index.vue'; import SummaryView from '../components/summary-view/Index.vue'; @@ -51,15 +57,17 @@ const router = useRouter(); const route = useRoute(); + const isShowImportHost = ref(false); + const panels = [ - { - name: 'summary-view', - label: t('统计视图'), - }, { name: 'host-list', label: t('主机列表'), }, + { + name: 'summary-view', + label: t('统计视图'), + }, ]; const activeTab = useDebouncedRef(route.params.page as string); @@ -85,6 +93,11 @@ }, }); }; + + // 导入主机 + const handleImportHost = () => { + isShowImportHost.value = true; + }; </script> <style lang="less" scoped> diff --git a/dbm-ui/frontend/src/views/resource-manage/spec/components/SpecList.vue b/dbm-ui/frontend/src/views/resource-manage/spec/components/SpecList.vue index 4efb8549c5..89b43f2a7e 100644 --- a/dbm-ui/frontend/src/views/resource-manage/spec/components/SpecList.vue +++ b/dbm-ui/frontend/src/views/resource-manage/spec/components/SpecList.vue @@ -47,6 +47,13 @@ {{ t('启用') }} </BkButton> </span> + <div class="enable-checkbox"> + <BkCheckbox + v-model="isEnableSpec" + class="mr-6" + @change="fetchData" /> + {{ t('仅显示已启用的规格') }} + </div> <BkInput v-model="searchKey" clearable @@ -156,6 +163,7 @@ const setRowClass = (data: ResourceSpecModel) => (data.isRecentSeconds ? 'is-new-row' : ''); const tableRef = ref(); + const isEnableSpec = ref(true); const specOperationState = reactive({ isShow: false, @@ -440,6 +448,7 @@ }, { spec_cluster_type: props.dbType, spec_machine_type: props.machineType, + enable: isEnableSpec.value, }); }; @@ -531,6 +540,14 @@ .delete-button { margin-right: auto; } + + .enable-checkbox { + display: flex; + margin-right: 16px; + font-size: 12px; + color: #4d4f56; + align-items: center; + } } :deep(.machine-info) { diff --git a/dbm-ui/frontend/src/views/tag-manage/components/BusinessSelector.vue b/dbm-ui/frontend/src/views/tag-manage/components/BusinessSelector.vue index 118a478b7d..c92e8dbaf2 100644 --- a/dbm-ui/frontend/src/views/tag-manage/components/BusinessSelector.vue +++ b/dbm-ui/frontend/src/views/tag-manage/components/BusinessSelector.vue @@ -17,14 +17,16 @@ :min-height="389" @toggle="handleToggle"> <template #trigger> - <div - ref="businessSelectorRef" - class="business-selector"> - <div>{{ bizIdMap.get(selected as number)?.name }}</div> - <AngleDownFill - class="triangle-icon mt-2 ml-7" - :class="[{ rotate: !isExpanded }]" /> - </div> + <slot name="trigger"> + <div + ref="businessSelectorRef" + class="business-selector"> + <div>{{ bizIdMap.get(selected as number)?.name }}</div> + <AngleDownFill + class="triangle-icon mt-2 ml-7" + :class="[{ rotate: !isExpanded }]" /> + </div> + </slot> </template> <BkOption v-for="item in sortedBizList" @@ -125,12 +127,12 @@ <style lang="less" scoped> .business-selector { - cursor: pointer; - color: #3a84ff; display: flex; - align-items: center; - font-size: 14px; width: 360px; + font-size: 14px; + color: #3a84ff; + cursor: pointer; + align-items: center; .triangle-icon { transition: transform 0.2s ease; // 确保过渡效果应用到 transform @@ -143,8 +145,8 @@ .bk-select-option { .biz-info { - color: #979ba5; margin-left: 2px; + color: #979ba5; } .unfavored {