Skip to content

Commit

Permalink
BIGTOP-4164: Close sse connection when task log window closed (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
FU-design authored Jul 18, 2024
1 parent a9aa638 commit ccf9373
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 123 deletions.
2 changes: 2 additions & 0 deletions bigtop-manager-ui/src/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down
11 changes: 8 additions & 3 deletions bigtop-manager-ui/src/api/sse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
138 changes: 18 additions & 120 deletions bigtop-manager-ui/src/components/job-info/job.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,24 @@
-->

<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,
TaskVO,
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 = [
{
Expand Down Expand Up @@ -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'])
Expand All @@ -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',
Expand All @@ -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
}
})
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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>
Expand All @@ -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>
141 changes: 141 additions & 0 deletions bigtop-manager-ui/src/components/job-info/task-log.vue
Original file line number Diff line number Diff line change
@@ -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>
8 changes: 8 additions & 0 deletions bigtop-manager-ui/src/utils/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit ccf9373

Please sign in to comment.