fix: repo sync error
This commit is contained in:
@@ -1,119 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onUnmounted } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { X, Loader2 } from 'lucide-vue-next'
|
||||
import LogTerminal from '@/components/LogTerminal.vue'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { api } from '@/api'
|
||||
// import { toast } from 'vue-sonner' // redundant here
|
||||
import { TASK_STATUS } from '@/constants'
|
||||
|
||||
const { resolvedTheme } = useTheme()
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { X, Search } from 'lucide-vue-next'
|
||||
import Ansi from 'ansi-to-vue3'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
logId?: string
|
||||
taskName?: string
|
||||
initialStatus?: string
|
||||
title: string
|
||||
content: string
|
||||
status?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
}>()
|
||||
|
||||
const logContent = ref('')
|
||||
const logStatus = ref(props.initialStatus || '')
|
||||
const isWsLoading = ref(false)
|
||||
let logSocket: WebSocket | null = null
|
||||
let durationTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const lightLogBackgroundClass = 'bg-zinc-100'
|
||||
const darkLogBackgroundClass = 'bg-zinc-950'
|
||||
const searchKeyword = ref('')
|
||||
|
||||
function close() {
|
||||
emit('update:open', false)
|
||||
}
|
||||
|
||||
function connectLogSocket(id: string) {
|
||||
if (logSocket) {
|
||||
logSocket.close()
|
||||
}
|
||||
isWsLoading.value = true
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const host = window.location.host
|
||||
const baseUrl = (window as any).__BASE_URL__ || ''
|
||||
const apiVersion = (window as any).__API_VERSION__ || '/api/v1'
|
||||
const wsUrl = `${protocol}//${host}${baseUrl}${apiVersion}/logs/ws?log_id=${id}`
|
||||
|
||||
logSocket = new WebSocket(wsUrl)
|
||||
|
||||
logSocket.onopen = () => {
|
||||
isWsLoading.value = false
|
||||
logContent.value = ''
|
||||
}
|
||||
|
||||
logSocket.onmessage = (event) => {
|
||||
if (logStatus.value !== TASK_STATUS.RUNNING) {
|
||||
logContent.value = event.data
|
||||
} else {
|
||||
logContent.value += event.data
|
||||
}
|
||||
}
|
||||
|
||||
logSocket.onerror = () => {
|
||||
isWsLoading.value = false
|
||||
logContent.value = '日志连接异常'
|
||||
}
|
||||
|
||||
logSocket.onclose = () => {
|
||||
isWsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function startPolling(id: string) {
|
||||
if (durationTimer) clearInterval(durationTimer)
|
||||
durationTimer = setInterval(async () => {
|
||||
try {
|
||||
if (!props.open) {
|
||||
if (durationTimer) clearInterval(durationTimer)
|
||||
return
|
||||
}
|
||||
const logRes = await api.logs.get(id)
|
||||
if (logRes) {
|
||||
logStatus.value = logRes.status
|
||||
if (logRes.status !== TASK_STATUS.RUNNING) {
|
||||
if (durationTimer) clearInterval(durationTimer)
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
watch(() => props.open, (val) => {
|
||||
if (val && props.logId) {
|
||||
logStatus.value = props.initialStatus || ''
|
||||
connectLogSocket(props.logId)
|
||||
if (logStatus.value === TASK_STATUS.RUNNING) {
|
||||
startPolling(props.logId)
|
||||
}
|
||||
// 统一控制 Body 滚动
|
||||
function toggleBodyScroll(lock: boolean) {
|
||||
if (lock) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
} else {
|
||||
if (logSocket) {
|
||||
logSocket.close()
|
||||
logSocket = null
|
||||
}
|
||||
if (durationTimer) {
|
||||
clearInterval(durationTimer)
|
||||
durationTimer = null
|
||||
}
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 监听打开状态
|
||||
watch(() => props.open, (val) => {
|
||||
if (val) {
|
||||
searchKeyword.value = ''
|
||||
toggleBodyScroll(true)
|
||||
} else {
|
||||
toggleBodyScroll(false)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 确保组件卸载时恢复滚动
|
||||
onUnmounted(() => {
|
||||
if (logSocket) logSocket.close()
|
||||
if (durationTimer) clearInterval(durationTimer)
|
||||
document.body.style.overflow = ''
|
||||
toggleBodyScroll(false)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -123,36 +53,36 @@ onUnmounted(() => {
|
||||
@click.self="close">
|
||||
<div
|
||||
class="bg-background rounded-lg shadow-lg flex flex-col w-full sm:w-[90vw] md:w-[80vw] max-w-5xl h-[90vh] sm:h-[85vh]">
|
||||
<div class="flex items-center justify-between px-3 sm:px-4 py-2 sm:py-3 border-b shrink-0 gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||
<span class="text-sm font-medium truncate">最新日志 - {{ taskName }}</span>
|
||||
<div v-if="logStatus"
|
||||
<div
|
||||
class="flex flex-col sm:flex-row sm:items-center justify-between px-3 sm:px-4 py-2 sm:py-3 border-b shrink-0 gap-2">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span class="text-sm font-medium truncate">{{ title }}</span>
|
||||
<div v-if="status"
|
||||
class="flex items-center gap-1.5 px-2 py-0.5 rounded text-[10px] font-bold uppercase transition-colors shrink-0"
|
||||
:class="logStatus === TASK_STATUS.SUCCESS ? 'bg-green-500/10 text-green-500 border border-green-500/20' :
|
||||
logStatus === TASK_STATUS.FAILED ? 'bg-red-500/10 text-red-500 border border-red-500/20' :
|
||||
:class="status === 'success' ? 'bg-green-500/10 text-green-500 border border-green-500/20' :
|
||||
status === 'failed' ? 'bg-red-500/10 text-red-500 border border-red-500/20' :
|
||||
'bg-yellow-500/10 text-yellow-500 border border-yellow-500/20'">
|
||||
<span v-if="logStatus === TASK_STATUS.RUNNING" class="relative flex h-1.5 w-1.5 mr-0.5">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
|
||||
<span v-if="status === 'running'" class="relative flex h-1.5 w-1.5 mr-0.5">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-1.5 w-1.5 bg-yellow-500"></span>
|
||||
</span>
|
||||
{{ logStatus === TASK_STATUS.SUCCESS ? '成功' : logStatus === TASK_STATUS.FAILED ? '失败' : '执行中' }}
|
||||
{{ status === 'success' ? '成功' : status === 'failed' ? '失败' : '执行中' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 sm:h-7 sm:w-7" @click="close">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative flex-1 sm:flex-none">
|
||||
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input v-model="searchKeyword" placeholder="搜索内容..." class="h-8 pl-9 w-full sm:w-56 text-sm" />
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 shrink-0" @click="close">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden relative"
|
||||
:class="resolvedTheme === 'dark' ? darkLogBackgroundClass : lightLogBackgroundClass">
|
||||
<LogTerminal v-if="logContent" :content="logContent" :theme="resolvedTheme" />
|
||||
<div v-else-if="isWsLoading" class="absolute inset-0 flex items-center justify-center gap-2 text-zinc-500 font-mono text-sm">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
连接中...
|
||||
</div>
|
||||
<div v-else class="absolute inset-0 flex items-center justify-center text-zinc-500 font-mono text-sm italic">
|
||||
无日志输出
|
||||
<div class="flex-1 overflow-auto bg-black/5 dark:bg-white/5">
|
||||
<div class="p-3 sm:p-4 text-xs font-mono whitespace-pre-wrap break-all leading-relaxed">
|
||||
<Ansi>{{ content }}</Ansi>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user