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 ccf9373 BIGTOP-4164: Close sse connection when task log window closed
(#21)
ccf9373 is described below
commit ccf9373203bc9472693d1e7f0c89dd7a05828cf4
Author: Fdefined <[email protected]>
AuthorDate: Thu Jul 18 23:30:44 2024 +0800
BIGTOP-4164: Close sse connection when task log window closed (#21)
---
bigtop-manager-ui/src/api/request.ts | 2 +
bigtop-manager-ui/src/api/sse/index.ts | 11 +-
bigtop-manager-ui/src/components/job-info/job.vue | 138 +++-----------------
.../src/components/job-info/task-log.vue | 141 +++++++++++++++++++++
bigtop-manager-ui/src/utils/tools.ts | 8 ++
5 files changed, 177 insertions(+), 123 deletions(-)
diff --git a/bigtop-manager-ui/src/api/request.ts
b/bigtop-manager-ui/src/api/request.ts
index 3a471b5..6b43130 100644
--- a/bigtop-manager-ui/src/api/request.ts
+++ b/bigtop-manager-ui/src/api/request.ts
@@ -79,6 +79,8 @@ request.interceptors.response.use(
message.error(i18n.global.t('common.error_network'))
} else if (error.code === AxiosError.ETIMEDOUT) {
message.error(i18n.global.t('common.error_timeout'))
+ } else if (error.code === AxiosError.ERR_CANCELED) {
+ return
} else {
console.log(error)
message.error(i18n.global.t('common.error_unknown'))
diff --git a/bigtop-manager-ui/src/api/sse/index.ts
b/bigtop-manager-ui/src/api/sse/index.ts
index c07ad27..52ee663 100644
--- a/bigtop-manager-ui/src/api/sse/index.ts
+++ b/bigtop-manager-ui/src/api/sse/index.ts
@@ -17,20 +17,25 @@
* under the License.
*/
+import axios, { type AxiosProgressEvent, type CancelTokenSource } from 'axios'
import request from '@/api/request.ts'
-import { AxiosProgressEvent } from 'axios'
export const getLogs = (
clusterId: number,
id: number,
func: Function
-): Promise<any> => {
- return request({
+): { promise: Promise<any>; cancel: () => void } => {
+ const source: CancelTokenSource = axios.CancelToken.source()
+
+ const promise = request({
method: 'get',
url: `/sse/clusters/${clusterId}/tasks/${id}/log`,
responseType: 'stream',
timeout: 0,
+ cancelToken: source.token,
onDownloadProgress: (progressEvent: AxiosProgressEvent) =>
func(progressEvent)
})
+
+ return { promise, cancel: source.cancel }
}
diff --git a/bigtop-manager-ui/src/components/job-info/job.vue
b/bigtop-manager-ui/src/components/job-info/job.vue
index 9ca8568..d2a2b34 100644
--- a/bigtop-manager-ui/src/components/job-info/job.vue
+++ b/bigtop-manager-ui/src/components/job-info/job.vue
@@ -18,12 +18,10 @@
-->
<script setup lang="ts">
- import { ref, watch, computed, reactive, toRaw, toRefs } from 'vue'
+ import { ref, watch, computed, reactive, toRaw, toRefs, nextTick } from 'vue'
import { useClusterStore } from '@/store/cluster'
import { PaginationConfig } from 'ant-design-vue/es/pagination/Pagination'
- import { CopyOutlined } from '@ant-design/icons-vue'
import { storeToRefs } from 'pinia'
- import { message } from 'ant-design-vue'
import {
JobVO,
StageVO,
@@ -31,17 +29,13 @@
OuterData,
Pagination
} from '@/api/job/types.ts'
- import { getLogs } from '@/api/sse'
import { getJobs } from '@/api/job'
import { Pausable, useIntervalFn } from '@vueuse/core'
- import { AxiosProgressEvent } from 'axios'
import { MONITOR_SCHEDULE_INTERVAL } from '@/utils/constant.ts'
import CustomProgress from './custom-progress.vue'
import Stage from './stage.vue'
import Task from './task.vue'
- import { copyText } from '@/utils/tools'
- import { useI18n } from 'vue-i18n'
- const { t } = useI18n()
+ import TaskLog from './task-log.vue'
const columns = [
{
@@ -69,16 +63,16 @@
const clusterStore = useClusterStore()
const { clusterId } = storeToRefs(clusterStore)
- const props = withDefaults(
- defineProps<{
- visible: boolean
- outerData?: OuterData
- }>(),
- {
- visible: false,
- outerData: undefined
- }
- )
+ interface Props {
+ visible: boolean
+ outerData?: OuterData
+ }
+
+ const props = withDefaults(defineProps<Props>(), {
+ visible: false,
+ outerData: undefined
+ })
+
const { visible, outerData } = toRefs(props)
const emits = defineEmits(['update:visible', 'closed'])
@@ -90,8 +84,7 @@
const currTaskInfo = ref<TaskVO>()
const jobs = ref<JobVO[]>([])
const intervalId = ref<Pausable | undefined>()
- const logTextOrigin = ref<string>('')
- const logsInfoRef = ref<HTMLElement | null>(null)
+ const logRef = ref<InstanceType<typeof TaskLog> | null>()
const currPage = ref<string[]>([
'isJobTable',
'isStageTable',
@@ -110,24 +103,11 @@
return currPage.value[breadcrumbs.value.length - 1]
})
- const logText = computed(() => {
- return logTextOrigin.value
- .split('\n\n')
- .map((s) => {
- return s.substring(5)
- })
- .join('\n')
- })
-
watch(visible, (val) => {
if (val) {
loading.value = true
Object.assign(paginationProps, initPagedProps())
- if (outerData.value) {
- checkMetaOrigin(true)
- } else {
- checkMetaOrigin(false)
- }
+ checkMetaOrigin(outerData.value ? true : false)
loading.value = false
}
})
@@ -189,42 +169,11 @@
}
}
- const getLogsInfo = (id: number) => {
- getLogs(clusterId.value, id, ({ event }: AxiosProgressEvent) => {
- logTextOrigin.value = event.target.responseText
- logsInfoRef.value = document.querySelector('.logsInfoRef')
- if (!logsInfoRef.value) {
- return
- }
- ;(function smoothscroll() {
- const { scrollTop, offsetHeight, scrollHeight } = logsInfoRef.value
- if (scrollHeight - 10 > scrollTop + offsetHeight) {
- window.requestAnimationFrame(smoothscroll)
- logsInfoRef.value.scrollTo(
- 0,
- scrollTop + (scrollHeight - scrollTop - offsetHeight) / 2
- )
- }
- })()
- })
- }
-
- const copyLogTextContent = (text: string) => {
- copyText(text)
- .then(() => {
- message.success(`${t('common.copy_success')}`)
- })
- .catch((err: Error) => {
- message.error(`${t('common.copy_fail')}`)
- console.log('err :>> ', err)
- })
- }
-
- const clickTask = (record: TaskVO) => {
+ const clickTask = async (record: TaskVO) => {
breadcrumbs.value.push(record)
currTaskInfo.value = record
- logTextOrigin.value = ''
- getLogsInfo(record.id)
+ await nextTick()
+ logRef.value?.getLogsInfo(record.id)
}
const clickJob = (record: JobVO) => {
@@ -336,26 +285,7 @@
<task :columns="columns" :tasks="tasks" @click-task="clickTask" />
</template>
<template v-if="getCurrPage == 'isTaskLogs'">
- <div class="logs">
- <div class="logs_header">
- <span>Task Logs</span>
- <div class="logs_header-ops">
- <a-button
- type="link"
- size="small"
- @click="copyLogTextContent(logText)"
- >
- <template #icon>
- <copy-outlined />
- </template>
- <span class="copy-button">{{ $t('common.copy') }}</span>
- </a-button>
- </div>
- </div>
- <div ref="logsInfoRef" class="logs_info">
- <pre id="logs">{{ logText }}</pre>
- </div>
- </div>
+ <task-log ref="logRef" />
</template>
</a-modal>
</template>
@@ -366,36 +296,4 @@
margin-bottom: 16px !important;
}
}
- .logs {
- height: 50vh;
- display: flex;
- flex-direction: column;
- &_header {
- font-size: 16px;
- font-weight: 600;
- margin: 0 0 10px 0;
- display: flex;
- justify-content: space-between;
-
- .copy-button {
- margin-left: 3px;
- }
- }
- &_info {
- height: 100%;
- overflow: auto;
- background-color: #f5f5f5;
- border-radius: 4px;
- position: relative;
- pre {
- height: 100%;
- margin: 0;
- padding: 16px 14px;
- box-sizing: border-box;
- color: #444;
- border-color: #eee;
- line-height: 16px;
- }
- }
- }
</style>
diff --git a/bigtop-manager-ui/src/components/job-info/task-log.vue
b/bigtop-manager-ui/src/components/job-info/task-log.vue
new file mode 100644
index 0000000..7665639
--- /dev/null
+++ b/bigtop-manager-ui/src/components/job-info/task-log.vue
@@ -0,0 +1,141 @@
+<!--
+ ~ 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
+ ~
+ ~ http://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.
+-->
+
+<script setup lang="ts">
+ import { useI18n } from 'vue-i18n'
+ import { copyText, scrollToBottom } from '@/utils/tools'
+ import { message } from 'ant-design-vue'
+ import { ref, watch, onBeforeUnmount } from 'vue'
+ import { CopyOutlined } from '@ant-design/icons-vue'
+ import { AxiosProgressEvent, Canceler } from 'axios'
+ import { getLogs } from '@/api/sse'
+ import { storeToRefs } from 'pinia'
+ import { useClusterStore } from '@/store/cluster'
+
+ const { t } = useI18n()
+ const clusterStore = useClusterStore()
+ const { clusterId } = storeToRefs(clusterStore)
+ const logsInfoRef = ref<HTMLElement | null>(null)
+ const logText = ref<string | null>(null)
+ const logMeta = ref<string>('')
+ const canceler = ref<Canceler>()
+
+ watch(logMeta, (val) => {
+ logText.value = val
+ .split('\n\n')
+ .map((s) => {
+ return s.substring(5)
+ })
+ .join('\n')
+ })
+
+ const getLogsInfo = async (id: number) => {
+ const { cancel } = getLogs(clusterId.value, id, getLogProgress)
+ canceler.value = cancel
+ }
+
+ const getLogProgress = ({ event }: AxiosProgressEvent) => {
+ logMeta.value = event.target.responseText
+ scrollToBottom(logsInfoRef.value)
+ }
+
+ const cancelSseConnect = () => {
+ if (!canceler.value) {
+ return
+ }
+ canceler.value()
+ }
+
+ const copyLogTextContent = (text: string | null) => {
+ if (!text) {
+ return
+ }
+ copyText(text)
+ .then(() => {
+ message.success(`${t('common.copy_success')}`)
+ })
+ .catch((err: Error) => {
+ message.error(`${t('common.copy_fail')}`)
+ console.log('err :>> ', err)
+ })
+ }
+
+ onBeforeUnmount(() => {
+ cancelSseConnect()
+ })
+
+ defineExpose({
+ getLogsInfo
+ })
+</script>
+
+<template>
+ <div class="logs">
+ <div class="logs_header">
+ <span>Task Logs</span>
+ <div class="logs_header-ops">
+ <a-button type="link" size="small"
@click="copyLogTextContent(logText)">
+ <template #icon>
+ <copy-outlined />
+ </template>
+ <span class="copy-button">{{ $t('common.copy') }}</span>
+ </a-button>
+ </div>
+ </div>
+ <div class="logs_info">
+ <pre id="logs" ref="logsInfoRef">{{ logText }}</pre>
+ </div>
+ </div>
+</template>
+
+<style scoped lang="scss">
+ .logs {
+ height: 50vh;
+ display: flex;
+ flex-direction: column;
+ &_header {
+ font-size: 16px;
+ font-weight: 600;
+ margin: 0 0 10px 0;
+ display: flex;
+ justify-content: space-between;
+
+ .copy-button {
+ margin-left: 3px;
+ }
+ }
+ &_info {
+ height: 100%;
+ overflow: auto;
+ scroll-behavior: smooth;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ position: relative;
+ pre {
+ height: 100%;
+ margin: 0;
+ padding: 16px 14px;
+ box-sizing: border-box;
+ color: #444;
+ border-color: #eee;
+ line-height: 16px;
+ }
+ }
+ }
+</style>
diff --git a/bigtop-manager-ui/src/utils/tools.ts
b/bigtop-manager-ui/src/utils/tools.ts
index 2a884bf..033fc37 100644
--- a/bigtop-manager-ui/src/utils/tools.ts
+++ b/bigtop-manager-ui/src/utils/tools.ts
@@ -47,3 +47,11 @@ export const copyText = (text: string): Promise<any> => {
}
})
}
+
+export const scrollToBottom = (container: HTMLElement | null) => {
+ if (!container) {
+ return
+ }
+ const { clientHeight, scrollHeight } = container
+ container.scrollTop = scrollHeight - clientHeight
+}