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 }
+})