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

Reply via email to