This is an automated email from the ASF dual-hosted git repository.

wuzhiguo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/bigtop-manager.git


The following commit(s) were added to refs/heads/main by this push:
     new a90addb6 BIGTOP-4500: Support start/stop/restart operations for agent 
(#270)
a90addb6 is described below

commit a90addb63535bcdc37d8c79b0b5e88ee08a4d635
Author: Fdefined <[email protected]>
AuthorDate: Fri Sep 26 15:00:51 2025 +0800

    BIGTOP-4500: Support start/stop/restart operations for agent (#270)
---
 bigtop-manager-ui/src/api/host/index.ts            | 70 +++++++++-------------
 bigtop-manager-ui/src/composables/use-modal.ts     | 57 ++++++++++++++++++
 bigtop-manager-ui/src/composables/use-tab-state.ts | 44 ++++++++++++++
 .../src/features/ai-assistant/chat-history.vue     | 15 ++---
 .../src/features/create-cluster/index.vue          | 11 +---
 .../src/features/create-service/index.vue          | 11 ++--
 bigtop-manager-ui/src/features/job/index.vue       | 15 ++---
 .../src/features/service-management/components.vue | 16 ++---
 .../components/snapshot-management.vue             | 15 ++---
 bigtop-manager-ui/src/locales/en_US/common.ts      | 33 +++++-----
 bigtop-manager-ui/src/locales/en_US/host.ts        |  2 +
 bigtop-manager-ui/src/locales/zh_CN/common.ts      |  1 +
 bigtop-manager-ui/src/locales/zh_CN/host.ts        |  2 +
 .../src/pages/cluster-manage/cluster/host.vue      | 15 ++---
 .../src/pages/cluster-manage/cluster/index.vue     | 28 ++++-----
 .../src/pages/cluster-manage/cluster/overview.vue  |  4 +-
 .../src/pages/cluster-manage/hosts/detail.vue      | 59 ++++++++++++------
 .../src/pages/cluster-manage/hosts/index.vue       | 33 ++++------
 .../src/pages/cluster-manage/hosts/overview.vue    |  4 +-
 .../pages/cluster-manage/infrastructures/index.vue | 27 +++------
 .../src/pages/system-manage/llm-config/index.vue   | 15 ++---
 .../src/store/create-service/validation.ts         | 13 ++--
 bigtop-manager-ui/src/store/job-progress/index.ts  | 20 +++----
 bigtop-manager-ui/src/store/service/index.ts       | 44 ++++----------
 bigtop-manager-ui/src/store/tab-state/index.ts     | 32 ++++++++++
 25 files changed, 322 insertions(+), 264 deletions(-)

diff --git a/bigtop-manager-ui/src/api/host/index.ts 
b/bigtop-manager-ui/src/api/host/index.ts
index 57c1ecd3..869d4c91 100644
--- a/bigtop-manager-ui/src/api/host/index.ts
+++ b/bigtop-manager-ui/src/api/host/index.ts
@@ -17,66 +17,50 @@
  * under the License.
  */
 
-import request from '@/api/request.ts'
 import type { HostListParams, HostParams, HostVO, HostVOList, 
InstalledStatusVO } from '@/api/host/types'
 import type { ComponentVO } from '@/api/component/types.ts'
+import { get, post, del, put } from '@/api/request-util'
 
-export const getHosts = (params?: HostListParams): Promise<HostVOList> => {
-  return request({
-    method: 'get',
-    url: '/hosts',
-    params
-  })
+export const getHosts = (params?: HostListParams) => {
+  return get<HostVOList>('/hosts', params)
 }
-export const getHost = (pathParams: { id: number }): Promise<HostVO> => {
-  return request({
-    method: 'get',
-    url: `/hosts/${pathParams.id}`
-  })
+
+export const getHost = (pathParams: { id: number }) => {
+  return get<HostVO>(`/hosts/${pathParams.id}`)
 }
 
 export const addHost = (data: HostParams): Promise<HostVO> => {
-  return request({
-    method: 'post',
-    url: '/hosts',
-    data
-  })
+  return post<HostVO>('/hosts', data)
 }
 
 export const installDependencies = (data: HostParams) => {
-  return request({
-    method: 'post',
-    url: '/hosts/install-dependencies',
-    data
-  })
+  return post('/hosts/install-dependencies', data)
 }
 
-export const getInstalledStatus = (): Promise<InstalledStatusVO[]> => {
-  return request({
-    method: 'get',
-    url: '/hosts/installed-status'
-  })
+export const getInstalledStatus = () => {
+  return get<InstalledStatusVO[]>('/hosts/installed-status')
 }
 
-export const updateHost = (data: HostParams): Promise<HostVO[]> => {
-  return request({
-    method: 'put',
-    url: `/hosts/${data.id}`,
-    data
-  })
+export const updateHost = (data: HostParams) => {
+  return put<HostVO[]>(`/hosts/${data.id}`, data)
 }
 
-export const removeHost = (data: { ids: number[] }): Promise<boolean> => {
-  return request({
-    method: 'delete',
-    url: '/hosts/batch',
-    data
-  })
+export const removeHost = (data: { ids: number[] }) => {
+  return del<boolean>('/hosts/batch', { data })
 }
 
 export const getComponentsByHost = (pathParams: { id: number }): 
Promise<ComponentVO[]> => {
-  return request({
-    method: 'get',
-    url: `/hosts/${pathParams.id}/components`
-  })
+  return get<ComponentVO[]>(`/hosts/${pathParams.id}/components`)
+}
+
+export const startAgent = (pathParams: { id: number }) => {
+  return post<boolean>(`/hosts/${pathParams.id}/start-agent`)
+}
+
+export const restartAgent = (pathParams: { id: number }) => {
+  return post<boolean>(`/hosts/${pathParams.id}/restart-agent`)
+}
+
+export const stopAgent = (pathParams: { id: number }) => {
+  return post<boolean>(`/hosts/${pathParams.id}/stop-agent`)
 }
diff --git a/bigtop-manager-ui/src/composables/use-modal.ts 
b/bigtop-manager-ui/src/composables/use-modal.ts
new file mode 100644
index 00000000..978e031c
--- /dev/null
+++ b/bigtop-manager-ui/src/composables/use-modal.ts
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+import SvgIcon from '@/components/base/svg-icon/index.vue'
+import { Modal, ModalFuncProps } from 'ant-design-vue'
+
+interface ConfirmModalProps extends ModalFuncProps {
+  tipText?: string
+}
+
+const DEFAULT_STYLE = { top: '30vh' }
+
+export const useModal = () => {
+  function confirmModal({ tipText, onOk, ...rest }: ConfirmModalProps) {
+    return Modal.confirm({
+      title: () =>
+        h('div', { style: { display: 'flex' } }, [
+          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
+          h('p', tipText ?? '')
+        ]),
+      style: DEFAULT_STYLE,
+      icon: null,
+      ...rest,
+      onOk: async () => {
+        try {
+          if (onOk) {
+            await onOk()
+          }
+        } catch (e) {
+          console.error('Modal onOk error:', e)
+        }
+        return Promise.resolve()
+      }
+    })
+  }
+
+  return {
+    confirmModal,
+    destroyAllModal: Modal.destroyAll
+  }
+}
diff --git a/bigtop-manager-ui/src/composables/use-tab-state.ts 
b/bigtop-manager-ui/src/composables/use-tab-state.ts
new file mode 100644
index 00000000..b005cf46
--- /dev/null
+++ b/bigtop-manager-ui/src/composables/use-tab-state.ts
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+import { useTabStore } from '@/store/tab-state'
+
+/**
+ * Combined functions that manage tab state
+ * @param pageKey Page unique identifier
+ * @param defaultIndex The default selected tab
+ */
+export function useTabState(pageKey: string, defaultIndex = '0') {
+  const route = useRoute()
+  const tabStore = useTabStore()
+
+  const activeTab = ref(defaultIndex)
+
+  // Initialize tabIndex: Preferred from URL query, followed by store
+  onMounted(() => {
+    const queryTab = route.query.tab as string
+    activeTab.value = queryTab ?? tabStore.getActiveTab(pageKey) ?? 
activeTab.value
+  })
+
+  watch(activeTab, (val) => {
+    tabStore.setActiveTab(pageKey, val)
+  })
+
+  return { activeTab }
+}
diff --git a/bigtop-manager-ui/src/features/ai-assistant/chat-history.vue 
b/bigtop-manager-ui/src/features/ai-assistant/chat-history.vue
index 77bcffd5..20ce4b2c 100644
--- a/bigtop-manager-ui/src/features/ai-assistant/chat-history.vue
+++ b/bigtop-manager-ui/src/features/ai-assistant/chat-history.vue
@@ -20,9 +20,7 @@
   import { useAiChatStore } from '@/store/ai-assistant'
   import { formatTime } from '@/utils/transform'
   import { EllipsisOutlined } from '@ant-design/icons-vue'
-  import { message, Modal, Empty } from 'ant-design-vue'
-
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
+  import { message, Empty } from 'ant-design-vue'
 
   import type { ChatThread, ThreadId } from '@/api/ai-assistant/types'
 
@@ -39,6 +37,7 @@
   type ThreadOperationHandler = Record<'delete' | 'rename', (thread: 
ChatThread, idx: number) => void>
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
   const aiChatStore = useAiChatStore()
   const { threads, currThread, threadLimit } = storeToRefs(aiChatStore)
   const props = defineProps<Props>()
@@ -100,14 +99,8 @@
   }
 
   const handleDeleteConfirm = (thread: ChatThread, idx: number) => {
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', t('common.delete_msg'))
-        ]),
-      style: { top: '30vh' },
-      icon: null,
+    confirmModal({
+      tipText: t('common.delete_msg'),
       async onOk() {
         const success = await aiChatStore.deleteChatThread(thread)
         if (success) {
diff --git a/bigtop-manager-ui/src/features/create-cluster/index.vue 
b/bigtop-manager-ui/src/features/create-cluster/index.vue
index e90875f8..9b3e2132 100644
--- a/bigtop-manager-ui/src/features/create-cluster/index.vue
+++ b/bigtop-manager-ui/src/features/create-cluster/index.vue
@@ -23,8 +23,6 @@
   import { getInstalledStatus, installDependencies } from '@/api/host'
   import { execCommand } from '@/api/command'
 
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
-
   import ClusterBase from './components/cluster-base.vue'
   import ComponentInfo from './components/component-info.vue'
   import HostManage from './components/host-manage.vue'
@@ -34,6 +32,7 @@
   import type { ClusterCommandReq, CommandRequest, CommandVO, HostReq } from 
'@/api/command/types'
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
   const menuStore = useMenuStore()
 
   const compRef = ref<any>()
@@ -201,12 +200,8 @@
 
   onBeforeRouteLeave((_to, _from, next) => {
     if (current.value === stepsLimit.value && !isDone.value) {
-      Modal.confirm({
-        title: () =>
-          h('div', { style: { display: 'flex' } }, [
-            h(SvgIcon, { name: 'unknown', style: { width: '24px', height: 
'24px' } }),
-            h('span', t('common.exit_confirm'))
-          ]),
+      confirmModal({
+        tipText: t('common.exit_confirm'),
         content: h('div', { style: { paddingLeft: '36px' } }, 
t('common.installing_exit_confirm_content')),
         cancelText: t('common.no'),
         style: { top: '30vh' },
diff --git a/bigtop-manager-ui/src/features/create-service/index.vue 
b/bigtop-manager-ui/src/features/create-service/index.vue
index 2340b7da..3b2dd63a 100644
--- a/bigtop-manager-ui/src/features/create-service/index.vue
+++ b/bigtop-manager-ui/src/features/create-service/index.vue
@@ -25,9 +25,10 @@
   import ComponentAssigner from './components/component-assigner.vue'
   import ServiceConfigurator from './components/service-configurator.vue'
   import ComponentInstaller from './components/component-installer.vue'
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const route = useRoute()
   const createStore = useCreateServiceStore()
   const { current, stepsLimit, stepContext, selectedServices, createdPayload } 
= storeToRefs(createStore)
@@ -136,12 +137,8 @@
 
   onBeforeRouteLeave((_to, _from, next) => {
     if (current.value === stepsLimit.value && !isDone.value) {
-      Modal.confirm({
-        title: () =>
-          h('div', { style: { display: 'flex' } }, [
-            h(SvgIcon, { name: 'unknown', style: { width: '24px', height: 
'24px' } }),
-            h('span', t('common.exit_confirm'))
-          ]),
+      confirmModal({
+        tipText: t('common.exit_confirm'),
         content: h('div', { style: { paddingLeft: '36px' } }, 
t('common.installing_exit_confirm_content')),
         cancelText: t('common.no'),
         style: { top: '30vh' },
diff --git a/bigtop-manager-ui/src/features/job/index.vue 
b/bigtop-manager-ui/src/features/job/index.vue
index 52c7ab64..9003f5ce 100644
--- a/bigtop-manager-ui/src/features/job/index.vue
+++ b/bigtop-manager-ui/src/features/job/index.vue
@@ -18,10 +18,9 @@
 -->
 
 <script setup lang="ts">
-  import { Modal, TableColumnType, TableProps } from 'ant-design-vue'
+  import { TableColumnType, TableProps } from 'ant-design-vue'
   import { getJobList, getStageList, getTaskList, retryJob } from '@/api/job'
 
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
   import LogsView, { type LogViewProps } from '@/features/log-view/index.vue'
 
   import type { JobVO, StageVO, StateType, TaskListParams, TaskVO } from 
'@/api/job/types'
@@ -36,6 +35,8 @@
 
   const POLLING_INTERVAL = 3000
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const clusterInfo = useAttrs() as ClusterVO
   const pollingIntervalId = ref<any>(null)
   const breadcrumbs = ref<BreadcrumbItem[]>([
@@ -227,14 +228,8 @@
   }
 
   const onRetry = (row: JobVO | StageVO | TaskVO) => {
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', t('job.retry'))
-        ]),
-      style: { top: '30vh' },
-      icon: null,
+    confirmModal({
+      tipText: t('job.retry'),
       async onOk() {
         try {
           const state = await retryJob({ jobId: row.id!, clusterId: 
clusterInfo.id! })
diff --git a/bigtop-manager-ui/src/features/service-management/components.vue 
b/bigtop-manager-ui/src/features/service-management/components.vue
index cd9b859e..e0653d35 100644
--- a/bigtop-manager-ui/src/features/service-management/components.vue
+++ b/bigtop-manager-ui/src/features/service-management/components.vue
@@ -18,11 +18,10 @@
 -->
 
 <script setup lang="ts">
-  import { message, Modal, type TableColumnType, type TableProps } from 
'ant-design-vue'
+  import { message, type TableColumnType, type TableProps } from 
'ant-design-vue'
   import { deleteComponent, getComponents } from '@/api/component'
   import { useStackStore } from '@/store/stack'
   import { useJobProgress } from '@/store/job-progress'
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
 
   import type { GroupItem } from '@/components/common/button-group/types'
   import type { ComponentVO } from '@/api/component/types'
@@ -39,7 +38,10 @@
   }
 
   const POLLING_INTERVAL = 3000
+
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const jobProgressStore = useJobProgress()
   const stackStore = useStackStore()
   const route = useRoute()
@@ -245,14 +247,8 @@
   }
 
   const handleDelete = async (row: ComponentVO) => {
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', t('common.delete_msg'))
-        ]),
-      style: { top: '30vh' },
-      icon: null,
+    confirmModal({
+      tipText: t('common.delete_msg'),
       async onOk() {
         try {
           const data = await deleteComponent({ clusterId: attrs.clusterId, id: 
row.id! })
diff --git 
a/bigtop-manager-ui/src/features/service-management/components/snapshot-management.vue
 
b/bigtop-manager-ui/src/features/service-management/components/snapshot-management.vue
index ccc8051f..8e0da7ad 100644
--- 
a/bigtop-manager-ui/src/features/service-management/components/snapshot-management.vue
+++ 
b/bigtop-manager-ui/src/features/service-management/components/snapshot-management.vue
@@ -18,13 +18,12 @@
 -->
 
 <script setup lang="ts">
-  import { message, Modal, TableColumnType } from 'ant-design-vue'
+  import { message, TableColumnType } from 'ant-design-vue'
   import {
     deleteServiceConfigSnapshot,
     getServiceConfigSnapshotsList,
     recoveryServiceConfigSnapshot
   } from '@/api/service'
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
 
   import type { GroupItem } from '@/components/common/button-group/types'
   import type { ServiceConfigSnapshot, ServiceParams, SnapshotRecovery } from 
'@/api/service/types'
@@ -32,6 +31,8 @@
   type OperationType = 'Restore' | 'Remove'
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const open = ref(false)
   const serviceInfo = shallowRef<ServiceParams>()
 
@@ -98,14 +99,8 @@
 
   const handleTableOperation = (item: GroupItem<OperationType>, payLoad: 
ServiceConfigSnapshot) => {
     const currOperation = operationMap.value[item.action!]
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', currOperation.modalTitle)
-        ]),
-      icon: null,
-      style: { top: '30vh' },
+    confirmModal({
+      tipText: currOperation.modalTitle,
       async onOk() {
         try {
           const data = await currOperation.api({ ...serviceInfo.value, 
snapshotId: payLoad.id } as SnapshotRecovery)
diff --git a/bigtop-manager-ui/src/locales/en_US/common.ts 
b/bigtop-manager-ui/src/locales/en_US/common.ts
index ca428ac4..6e5f381a 100644
--- a/bigtop-manager-ui/src/locales/en_US/common.ts
+++ b/bigtop-manager-ui/src/locales/en_US/common.ts
@@ -25,6 +25,7 @@ export default {
   exit: 'Exit',
   action: 'Action',
   operation: 'Operation',
+  operate_success: 'Operation successful !',
   status: 'Status',
   healthy: 'Healthy',
   unhealthy: 'Unhealthy',
@@ -43,7 +44,7 @@ export default {
   retry: 'Retry',
   cancel: 'Cancel',
   confirm: 'Confirm',
-  exit_confirm: 'Are you sure you want to exit?',
+  exit_confirm: 'Are you sure you want to exit ?',
   install: 'Install',
   installing: 'Installing...',
   finish: 'Finish',
@@ -69,8 +70,8 @@ export default {
   required: 'Required',
   not_required: 'Not Required',
   progress: 'Progress',
-  status_change_success: 'Status changed successfully',
-  test_success: 'Test successful!',
+  status_change_success: 'Status changed successful !',
+  test_success: 'Test successful !',
   created: 'Created!',
   update_success: 'Updated!',
   update_fail: 'Update fail',
@@ -83,11 +84,11 @@ export default {
   update_time: 'Update Time',
   notification: 'Notification',
   copy: 'Copy',
-  copied: 'Copied!',
+  copied: 'Copied !',
   download: 'Download',
-  copy_fail: 'Copy failed!',
-  delete_success: 'Deleted!',
-  delete_fail: 'Delete failed!',
+  copy_fail: 'Copy failed !',
+  delete_success: 'Deleted !',
+  delete_fail: 'Delete failed !',
   delete_confirm_title: 'Delete Confirm',
   delete_confirm_content: 'Delete will not be recoverable, are you sure you 
want to delete {0} ?',
   no_options: 'No options',
@@ -95,16 +96,16 @@ export default {
   enter_error: 'Please enter {0}',
   select_error: 'Please select {0}',
   add_error: 'Please add {0}',
-  delete_msg: 'Are you sure you want to delete this?',
-  delete_msgs: 'Are you sure you want to delete these?',
-  restore_msg: 'Are you sure you want to restore this?',
+  delete_msg: 'Are you sure you want to delete this ?',
+  delete_msgs: 'Are you sure you want to delete these ?',
+  restore_msg: 'Are you sure you want to restore this ?',
   delete_empty: 'Please select the records you want to delete.',
   total: 'Total {0} items',
   upload_file: 'Upload file',
-  file_type_error: 'Only text files are allowed!',
-  file_size_error: 'File size cannot exceed 10KB!',
-  upload_success: 'File uploaded successfully!',
-  upload_failed: 'File upload failed!',
+  file_type_error: 'Only text files are allowed !',
+  file_size_error: 'File size cannot exceed 10KB !',
+  upload_success: 'File uploaded successful !',
+  upload_failed: 'File upload failed !',
   password_not_match: 'Passwords do not match.',
   overview: 'Overview',
   user: 'User',
@@ -113,7 +114,7 @@ export default {
   restart: 'Restart {0}',
   stop: 'Stop {0}',
   add: 'Add {0}',
-  success_msg: 'Added successfully',
+  success_msg: 'Added successful !',
   configs: 'Configs',
   more_operations: 'More Operations',
   ok: 'Ok',
@@ -125,7 +126,7 @@ export default {
   selected: 'Selected',
   feature_not_supported: 'Feature not supported yet',
   installing_exit_confirm_content:
-    'Installation is in progress, exiting the page will cause the installation 
to fail. Are you sure you want to exit?',
+    'Installation is in progress, exiting the page will cause the installation 
to fail. Are you sure you want to exit ?',
   confirm_action: 'Are you sure you want to {action} {target} ?',
   confirm_comp_action: 'Are you sure you want to {action} selected components 
?'
 }
diff --git a/bigtop-manager-ui/src/locales/en_US/host.ts 
b/bigtop-manager-ui/src/locales/en_US/host.ts
index d982d872..be92e137 100644
--- a/bigtop-manager-ui/src/locales/en_US/host.ts
+++ b/bigtop-manager-ui/src/locales/en_US/host.ts
@@ -41,6 +41,8 @@ export default {
   ssh_port: 'SSH Port',
   grpc_port: 'GRPC Port',
   description: 'Description',
+  operate_agent: 'Are you sure you want to {action} agent on {target} ?',
+  agent: 'Agent',
   key_password_not_match: 'Key Passwords do not match.',
   default_agent_path: 'Agent application installed path, default to /opt',
   default_grpc_port: 'Agent application grpc port, default to 8835',
diff --git a/bigtop-manager-ui/src/locales/zh_CN/common.ts 
b/bigtop-manager-ui/src/locales/zh_CN/common.ts
index 6ebbeae6..77d523aa 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/common.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/common.ts
@@ -25,6 +25,7 @@ export default {
   exit: '退出',
   action: '操作',
   operation: '操作',
+  operate_success: '操作成功',
   status: '状态',
   healthy: '健康',
   unhealthy: '不健康',
diff --git a/bigtop-manager-ui/src/locales/zh_CN/host.ts 
b/bigtop-manager-ui/src/locales/zh_CN/host.ts
index 85faf40e..55528540 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/host.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/host.ts
@@ -41,6 +41,8 @@ export default {
   ssh_port: 'SSH端口',
   grpc_port: 'GRPC端口',
   description: '备注',
+  operate_agent: '确定要{action}主机{target}上的代理吗?',
+  agent: '代理',
   key_password_not_match: '密钥口令不一致。',
   default_agent_path: '代理应用程序安装路径,默认为/opt',
   default_grpc_port: '代理应用程序gRPC端口,默认为8835',
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
index dce6adcd..d360f525 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
@@ -18,13 +18,12 @@
 -->
 
 <script setup lang="ts">
-  import { message, Modal, TableColumnType, TableProps } from 'ant-design-vue'
+  import { message, TableColumnType, TableProps } from 'ant-design-vue'
   import { getHosts } from '@/api/host'
   import * as hostApi from '@/api/host'
 
   import HostCreate from '@/features/create-host/index.vue'
   import InstallDependencies from 
'@/features/create-host/install-dependencies.vue'
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
 
   import type { FilterConfirmProps, FilterResetProps } from 
'ant-design-vue/es/table/interface'
   import type { GroupItem } from '@/components/common/button-group/types'
@@ -41,6 +40,8 @@
   }
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const router = useRouter()
   const attrs = useAttrs() as ClusterVO
 
@@ -155,14 +156,8 @@
   }
 
   const deleteHost = (ids: number[]) => {
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', ids.length > 1 ? t('common.delete_msgs') : 
t('common.delete_msg'))
-        ]),
-      style: { top: '30vh' },
-      icon: null,
+    confirmModal({
+      tipText: ids.length > 1 ? t('common.delete_msgs') : 
t('common.delete_msg'),
       async onOk() {
         try {
           const data = await hostApi.removeHost({ ids })
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
index aab76963..5bb75204 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
@@ -31,16 +31,16 @@
   import type { Command } from '@/api/command/types'
   import type { TabItem } from '@/components/base/main-card/types'
   import type { GroupItem } from '@/components/common/button-group/types'
-  import type { ClusterStatusType, ClusterVO } from '@/api/cluster/types'
+  import type { ClusterStatusType } from '@/api/cluster/types'
 
   const { t } = useI18n()
   const router = useRouter()
+  const route = useRoute()
   const jobProgressStore = useJobProgress()
   const clusterStore = useClusterStore()
-  const { loading } = storeToRefs(clusterStore)
+  const { loading, currCluster } = storeToRefs(clusterStore)
 
-  const activeKey = ref('1')
-  const clusterInfo = ref<ClusterVO>({})
+  const { activeTab } = useTabState(route.path, '1')
 
   const statusColors = shallowRef<Record<ClusterStatusType, keyof typeof 
CommonStatusTexts>>({
     1: 'healthy',
@@ -51,7 +51,7 @@
   /**
    * Determines the component to render based on the active tab.
    */
-  const getCompName = computed(() => [Overview, Service, Host, User, 
Job][parseInt(activeKey.value) - 1])
+  const getCompName = computed(() => [Overview, Service, Host, User, 
Job][parseInt(activeTab.value) - 1])
 
   const tabs = computed((): TabItem[] => [
     { key: '1', title: t('common.overview') },
@@ -89,14 +89,14 @@
       jobProgressStore.processCommand(
         {
           command: key as keyof typeof Command,
-          clusterId: clusterInfo.value.id,
+          clusterId: currCluster.value.id,
           commandLevel: 'cluster'
         },
         async () => {
           await clusterStore.loadClusters()
-          await clusterStore.getClusterDetail(clusterInfo.value.id!)
+          await clusterStore.getClusterDetail(currCluster.value.id!)
         },
-        { displayName: clusterInfo.value.displayName }
+        { displayName: currCluster.value.displayName }
       )
     } catch (error) {
       console.error('Error processing command:', error)
@@ -104,23 +104,23 @@
   }
 
   const addService: GroupItem['clickEvent'] = () => {
-    router.push({ name: 'CreateService', params: { id: clusterInfo.value.id, 
creationMode: 'internal' } })
+    router.push({ name: 'CreateService', params: { id: currCluster.value.id, 
creationMode: 'internal' } })
   }
 </script>
 
 <template>
   <a-spin :spinning="loading">
     <header-card
-      :title="clusterInfo.displayName"
+      :title="currCluster.displayName"
       avatar="cluster"
-      :status="CommonStatus[statusColors[clusterInfo.status as 
ClusterStatusType]]"
-      :desc="clusterInfo.desc"
+      :status="CommonStatus[statusColors[currCluster.status as 
ClusterStatusType]]"
+      :desc="currCluster.desc"
       :action-groups="actionGroup"
     />
-    <main-card v-model:active-key="activeKey" :tabs="tabs">
+    <main-card v-model:active-key="activeTab" :tabs="tabs">
       <template #tab-item>
         <keep-alive>
-          <component :is="getCompName" v-bind="clusterInfo" 
v-model:payload="clusterInfo"></component>
+          <component :is="getCompName" :key="activeTab" v-bind="currCluster" 
v-model:payload="currCluster"></component>
         </keep-alive>
       </template>
     </main-card>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
index a555330f..e7c368ee 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
@@ -142,9 +142,7 @@
     resume()
   })
 
-  onDeactivated(() => pause())
-
-  onUnmounted(() => pause())
+  onDeactivated(pause)
 
   watchEffect(() => {
     locateStackWithService.value = stackStore.stacks.filter((item) =>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
index 0b8b29ba..a155ba45 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
@@ -18,9 +18,8 @@
 -->
 
 <script setup lang="ts">
+  import { getHost, restartAgent, startAgent, stopAgent } from '@/api/host'
   import { message } from 'ant-design-vue'
-
-  import { getHost } from '@/api/host'
   import { CommonStatus, CommonStatusTexts } from '@/enums/state'
 
   import Overview from './overview.vue'
@@ -29,14 +28,23 @@
   import type { HostStatusType, HostVO } from '@/api/host/types'
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const route = useRoute()
+  const spinning = ref(false)
   const hostInfo = shallowRef<HostVO>({})
-  const loading = ref(false)
+  const apiMap = shallowRef({
+    start: startAgent,
+    restart: restartAgent,
+    stop: stopAgent
+  })
+
   const statusColors = shallowRef<Record<HostStatusType, keyof typeof 
CommonStatusTexts>>({
     1: 'healthy',
     2: 'unhealthy',
     3: 'unknown'
   })
+
   const actionGroup = computed<GroupItem[]>(() => [
     {
       shape: 'default',
@@ -44,40 +52,53 @@
       text: t('common.more_operations'),
       dropdownMenu: [
         {
-          action: 'Start',
-          text: t('common.start', [t('common.all')])
+          action: 'start',
+          text: t('common.start', [t('host.agent')])
         },
         {
-          action: 'Restart',
-          text: t('common.restart', [t('common.all')])
+          action: 'restart',
+          text: t('common.restart', [t('host.agent')])
         },
         {
-          action: 'Stop',
-          text: t('common.stop', [t('common.all')])
+          action: 'stop',
+          text: t('common.stop', [t('host.agent')])
         }
       ],
-      dropdownMenuClickEvent: (info) => dropdownMenuClick && 
dropdownMenuClick(info)
+      dropdownMenuClickEvent: ({ key }) => {
+        const action = t(`common.${key.toString()}`).toLowerCase()
+        const tipText = t('host.operate_agent', { action, target: 
hostInfo.value.hostname })
+        confirmModal({
+          tipText,
+          async onOk() {
+            await handleOperateAgent(key as string)
+          }
+        })
+      }
     }
   ])
 
-  const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async () => {
-    message.error(t('common.feature_not_supported'))
+  const handleOperateAgent = async (action: string) => {
+    try {
+      const res = await apiMap.value[`${action}`]({ id: hostInfo.value.id })
+      if (res) {
+        message.success(t('common.operate_success'))
+      }
+    } finally {
+      setupHostInfo()
+    }
   }
 
   const setupHostInfo = async () => {
     try {
-      loading.value = true
-      const hostId = route.query.hostId
-      const clusterId = route.query.clusterId
+      const { hostId, clusterId } = route.query
+      spinning.value = true
       const data = await getHost({ id: Number(hostId) })
       hostInfo.value = {
         ...data,
         clusterId
       }
-    } catch (error) {
-      console.log(error)
     } finally {
-      loading.value = false
+      spinning.value = false
     }
   }
 
@@ -87,7 +108,7 @@
 </script>
 
 <template>
-  <a-spin :spinning="loading">
+  <a-spin :spinning="spinning">
     <header-card
       :title="hostInfo.hostname"
       avatar="host"
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
index bfe2de65..2ece464c 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
@@ -18,7 +18,7 @@
 -->
 
 <script setup lang="ts">
-  import { message, Modal, TableColumnType, TableProps } from 'ant-design-vue'
+  import { message, TableColumnType, TableProps } from 'ant-design-vue'
 
   import { useClusterStore } from '@/store/cluster'
   import * as hostApi from '@/api/host'
@@ -26,7 +26,6 @@
   import useBaseTable from '@/composables/use-base-table'
   import HostCreate from '@/features/create-host/index.vue'
   import InstallDependencies from 
'@/features/create-host/install-dependencies.vue'
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
 
   import type { FilterConfirmProps, FilterResetProps, TableRowSelection } from 
'ant-design-vue/es/table/interface'
   import type { HostReq } from '@/api/command/types'
@@ -43,6 +42,8 @@
   }
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const router = useRouter()
   const clusterStore = useClusterStore()
   const searchInputRef = ref()
@@ -135,15 +136,8 @@
   ])
 
   const operations = computed((): GroupItem[] => [
-    {
-      text: 'edit',
-      clickEvent: (_item, args) => editHost(args)
-    },
-    {
-      text: 'remove',
-      danger: true,
-      clickEvent: (_item, args) => deleteHost([args.id])
-    }
+    { text: 'edit', clickEvent: (_item, args) => editHost(args) },
+    { text: 'remove', danger: true, clickEvent: (_item, args) => 
deleteHost([args.id]) }
   ])
 
   const { loading, dataSource, filtersParams, paginationProps, onChange } = 
useBaseTable<HostVO>({
@@ -214,19 +208,16 @@
 
   const editHost = (row: HostVO) => {
     const cluster = Object.values(clusterStore.clusterMap).find((v) => v.name 
=== row.clusterName)
-    const formatHost = { ...row, displayName: row.clusterDisplayName, 
clusterId: cluster?.id }
-    hostCreateRef.value?.handleOpen('EDIT', formatHost)
+    hostCreateRef.value?.handleOpen('EDIT', {
+      ...row,
+      displayName: row.clusterDisplayName,
+      clusterId: cluster?.id
+    })
   }
 
   const deleteHost = (ids: number[]) => {
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', ids.length > 1 ? t('common.delete_msgs') : 
t('common.delete_msg'))
-        ]),
-      style: { top: '30vh' },
-      icon: null,
+    confirmModal({
+      tipText: ids.length > 1 ? t('common.delete_msgs') : 
t('common.delete_msg'),
       async onOk() {
         try {
           const data = await hostApi.removeHost({ ids })
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
index 9946cb31..ed8ef9e7 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
@@ -93,7 +93,7 @@
   const detailKeys = computed((): (keyof HostVO)[] => 
Object.keys(baseConfig.value))
   const noChartData = computed(() => Object.values(chartData.value).length === 
0)
 
-  const handleHostOperate = (item: any, component: ComponentVO) => {
+  const handleComponentOperate = (item: any, component: ComponentVO) => {
     const { serviceName } = component
     const installedServiceMap = Object.values(serviceStore.serviceMap)
       .flat()
@@ -252,7 +252,7 @@
                   <svg-icon name="more" style="margin: 0" />
                 </a-button>
                 <template #overlay>
-                  <a-menu @click="handleHostOperate($event, comp)">
+                  <a-menu @click="handleComponentOperate($event, comp)">
                     <a-menu-item v-for="[operate, text] of 
Object.entries(componentOperates)" :key="operate">
                       <span>{{ text }}</span>
                     </a-menu-item>
diff --git 
a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue 
b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
index 2d5d3d77..6150d92a 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
@@ -27,20 +27,14 @@
 
   const { t } = useI18n()
   const router = useRouter()
-  const activeKey = ref('1')
-  const currCluster = shallowRef<ClusterVO>({
-    id: 0
-  })
+  const route = useRoute()
+  const { activeTab } = useTabState(route.path, '1')
+
+  const currCluster = shallowRef<ClusterVO>({ id: 0 })
 
   const tabs = computed((): TabItem[] => [
-    {
-      key: '1',
-      title: t('common.service')
-    },
-    {
-      key: '2',
-      title: t('common.job')
-    }
+    { key: '1', title: t('common.service') },
+    { key: '2', title: t('common.job') }
   ])
 
   const actionGroup = computed<GroupItem[]>(() => [
@@ -52,10 +46,7 @@
     }
   ])
 
-  const getCompName = computed(() => {
-    const components = [Service, Job]
-    return components[parseInt(activeKey.value) - 1]
-  })
+  const getCompName = computed(() => [Service, Job][parseInt(activeTab.value) 
- 1])
 
   const addService: GroupItem['clickEvent'] = () => {
     router.push({ name: 'CreateInfraService', params: { id: 0, creationMode: 
'public' } })
@@ -65,10 +56,10 @@
 <template>
   <div>
     <header-card :title="t('menu.infra')" :show-avatar="false" 
:desc="t('infra.info')" :action-groups="actionGroup" />
-    <main-card v-model:active-key="activeKey" :tabs="tabs">
+    <main-card v-model:active-key="activeTab" :tabs="tabs">
       <template #tab-item>
         <keep-alive>
-          <component :is="getCompName" v-bind="currCluster"></component>
+          <component :is="getCompName" :key="activeTab" 
v-bind="currCluster"></component>
         </keep-alive>
       </template>
     </main-card>
diff --git a/bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue 
b/bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue
index 3e9c07c8..79b934d8 100644
--- a/bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue
+++ b/bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue
@@ -20,16 +20,17 @@
 <script setup lang="ts">
   import { storeToRefs } from 'pinia'
   import { useLlmConfigStore } from '@/store/llm-config/index'
-  import { message, Modal } from 'ant-design-vue'
+  import { message } from 'ant-design-vue'
   import { useI18n } from 'vue-i18n'
   import LlmItem, { type ActionKeys, type ExtraItem } from 
'./components/llm-item.vue'
 
   import addLlmItem from './components/add-llm-item.vue'
-  import SvgIcon from '@/components/base/svg-icon/index.vue'
 
   import type { AuthorizedPlatform } from '@/api/llm-config/types'
 
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const llmConfigStore = useLlmConfigStore()
   const addLlmItemRef = ref<InstanceType<typeof addLlmItem> | null>(null)
   const { loading, authorizedPlatforms } = storeToRefs(llmConfigStore)
@@ -71,14 +72,8 @@
   }
 
   const handleDeleteLlmConfig = (authId: string | number) => {
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', t('common.delete_msg'))
-        ]),
-      icon: null,
-      style: { top: '30vh' },
+    confirmModal({
+      tipText: t('common.delete_msg'),
       async onOk() {
         const success = await llmConfigStore.deleteAuthPlatform(authId)
         if (success) {
diff --git a/bigtop-manager-ui/src/store/create-service/validation.ts 
b/bigtop-manager-ui/src/store/create-service/validation.ts
index 591463c1..5db870f9 100644
--- a/bigtop-manager-ui/src/store/create-service/validation.ts
+++ b/bigtop-manager-ui/src/store/create-service/validation.ts
@@ -19,11 +19,12 @@
 
 import { message, Modal } from 'ant-design-vue'
 import { useServiceStore } from '@/store/service'
-import SvgIcon from '@/components/base/svg-icon/index.vue'
 import { useStackStore, type ExpandServiceVO } from '@/store/stack'
 
 export function useValidations() {
   const { t } = useI18n()
+  const { confirmModal } = useModal()
+
   const serviceStore = useServiceStore()
   const stackStore = useStackStore()
 
@@ -72,16 +73,10 @@ export function useValidations() {
     const content = type === 'add' ? 'dependencies_add_msg' : 
'dependencies_remove_msg'
 
     return new Promise((resolve) => {
-      Modal.confirm({
-        icon: null,
-        title: () =>
-          h('div', { style: { display: 'flex' } }, [
-            h(SvgIcon, { name: 'unknown', style: { width: '24px', height: 
'24px' } }),
-            h('p', t(`service.${content}`, [targetService.displayName, 
requireds.displayName]))
-          ]),
+      confirmModal({
+        tipText: t(`service.${content}`, [targetService.displayName, 
requireds.displayName]),
         cancelText: t('common.no'),
         okText: t('common.yes'),
-        style: { top: '30vh' },
         onOk: () => resolve(true),
         onCancel: () => {
           Modal.destroyAll()
diff --git a/bigtop-manager-ui/src/store/job-progress/index.ts 
b/bigtop-manager-ui/src/store/job-progress/index.ts
index 564ae05b..59c6342d 100644
--- a/bigtop-manager-ui/src/store/job-progress/index.ts
+++ b/bigtop-manager-ui/src/store/job-progress/index.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { notification, Progress, Avatar, Button, Modal } from 'ant-design-vue'
+import { notification, Progress, Avatar, Button } from 'ant-design-vue'
 import { useClusterStore } from '@/store/cluster'
 import { execCommand } from '@/api/command'
 import { getJobDetails } from '@/api/job'
@@ -45,6 +45,8 @@ type JobStageProgress = Record<StatusType, () => 
JobStageProgressItem>
 
 export const useJobProgress = defineStore('job-progress', () => {
   const { t } = useI18n()
+  const { confirmModal, destroyAllModal } = useModal()
+
   const instance = getCurrentInstance()
   const clusterStore = useClusterStore()
   const progressMap = reactive<Map<number, JobStageProgressItem>>(new Map())
@@ -264,14 +266,8 @@ export const useJobProgress = defineStore('job-progress', 
() => {
       }
     }
 
-    Modal.confirm({
-      title: () =>
-        h('div', { style: { display: 'flex' } }, [
-          h(SvgIcon, { name: 'unknown', style: { width: '24px', height: '24px' 
} }),
-          h('p', t(`${title}`, target === '' ? { action } : { action, target 
}))
-        ]),
-      style: { top: '30vh' },
-      icon: null,
+    confirmModal({
+      tipText: t(`${title}`, target === '' ? { action } : { action, target }),
       async onOk() {
         try {
           const { id: jobId, name } = await execCommand(params)
@@ -287,9 +283,9 @@ export const useJobProgress = defineStore('job-progress', 
() => {
   }
 
   const onClick = (execRes: CommandRes) => {
-    Modal.destroyAll()
-    Modal.confirm({
-      icon: null,
+    destroyAllModal()
+    confirmModal({
+      title: undefined,
       width: '980px',
       zIndex: 9999,
       mask: false,
diff --git a/bigtop-manager-ui/src/store/service/index.ts 
b/bigtop-manager-ui/src/store/service/index.ts
index 92948950..42d98b96 100644
--- a/bigtop-manager-ui/src/store/service/index.ts
+++ b/bigtop-manager-ui/src/store/service/index.ts
@@ -18,9 +18,7 @@
  */
 
 import { getService, getServiceList, removeService as remove } from 
'@/api/service'
-import { Modal, message } from 'ant-design-vue'
-
-import SvgIcon from '@/components/base/svg-icon/index.vue'
+import { message } from 'ant-design-vue'
 
 import type { ServiceListParams, ServiceVO } from '@/api/service/types'
 
@@ -28,6 +26,8 @@ export const useServiceStore = defineStore(
   'service',
   () => {
     const { t } = useI18n()
+    const { confirmModal } = useModal()
+
     const services = ref<ServiceVO[]>([])
     const serviceMap = ref<Record<string, (ServiceVO & { clusterId: number 
})[]>>({})
     const total = ref(0)
@@ -60,11 +60,7 @@ export const useServiceStore = defineStore(
     }
 
     const getServiceDetail = async (clusterId: number, serviceId: number) => {
-      try {
-        return await getService({ clusterId, id: serviceId })
-      } catch (error) {
-        console.log(error)
-      }
+      return await getService({ clusterId, id: serviceId })
     }
 
     const getServicesOfInfra = async () => {
@@ -73,11 +69,7 @@ export const useServiceStore = defineStore(
 
     const getInstalledNamesOrIdsOfServiceByKey = (key: string, flag: 'names' | 
'ids' = 'names') => {
       return Object.values(serviceMap.value[key] || {}).map((service: 
ServiceVO) => {
-        if (flag === 'ids') {
-          return service.id
-        } else {
-          return service.name
-        }
+        return flag === 'ids' ? service.id : service.name
       })
     }
 
@@ -91,26 +83,16 @@ export const useServiceStore = defineStore(
       }
     }
 
-    async function removeService(service: ServiceVO, clusterId: number, 
callBack: (...args: any) => void) {
-      const actionI18n = t('common.remove').toLowerCase()
+    function removeService(service: ServiceVO, clusterId: number, callBack: 
(...args: any) => void) {
       const target = service.displayName ?? service.name
-      return Modal.confirm({
-        title: () =>
-          h('div', { style: { display: 'flex' } }, [
-            h(SvgIcon, { name: 'unknown', style: { width: '24px', height: 
'24px' } }),
-            h('p', t('common.confirm_action', { action: actionI18n, target }))
-          ]),
-        style: { top: '30vh' },
-        icon: null,
+      const tipText = t('common.confirm_action', { action: 
t('common.remove').toLowerCase(), target })
+      return confirmModal({
+        tipText,
         async onOk() {
-          try {
-            const res = await remove({ clusterId, id: service.id! })
-            if (res) {
-              message.success(t('common.delete_success'))
-              return Promise.resolve(callBack())
-            }
-          } catch (error) {
-            console.log('error :>> ', error)
+          const res = await remove({ clusterId, id: service.id! })
+          if (res) {
+            message.success(t('common.delete_success'))
+            return Promise.resolve(callBack())
           }
         }
       })
diff --git a/bigtop-manager-ui/src/store/tab-state/index.ts 
b/bigtop-manager-ui/src/store/tab-state/index.ts
new file mode 100644
index 00000000..6b940e6a
--- /dev/null
+++ b/bigtop-manager-ui/src/store/tab-state/index.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+export const useTabStore = defineStore('tab', () => {
+  const activeTabs = ref<Record<string, string>>({})
+
+  function setActiveTab(pageKey: string, tabIndex: string) {
+    activeTabs.value[pageKey] = tabIndex
+  }
+
+  function getActiveTab(pageKey: string) {
+    return activeTabs.value[pageKey]
+  }
+
+  return { activeTabs, setActiveTab, getActiveTab }
+})

Reply via email to