kin: auto-commit after pipeline

This commit is contained in:
Gros Frumos 2026-03-17 17:39:40 +02:00
parent 248934d5d7
commit 939a30a3de
6 changed files with 796 additions and 3 deletions

View file

@ -0,0 +1,117 @@
<script setup lang="ts">
import { ref, watch, onUnmounted } from 'vue'
import { api, type PipelineLog } from '../api'
const props = defineProps<{
pipelineId: string
pipelineStatus: string
}>()
const visible = ref(false)
const logs = ref<PipelineLog[]>([])
const error = ref('')
const consoleEl = ref<HTMLElement | null>(null)
let sinceId = 0
let userScrolled = false
let timer: ReturnType<typeof setInterval> | null = null
const MAX_LOGS = 500
function levelClass(level: PipelineLog['level']): string {
switch (level) {
case 'INFO': return 'text-gray-300'
case 'DEBUG': return 'text-gray-500'
case 'ERROR': return 'text-red-400'
case 'WARN': return 'text-yellow-400'
}
}
function onScroll() {
if (!consoleEl.value) return
const { scrollTop, clientHeight, scrollHeight } = consoleEl.value
userScrolled = scrollTop + clientHeight < scrollHeight - 50
}
function scrollToBottom() {
if (!consoleEl.value || userScrolled) return
consoleEl.value.scrollTop = consoleEl.value.scrollHeight
}
async function fetchLogs() {
try {
const newLogs = await api.getPipelineLogs(props.pipelineId, sinceId)
if (!newLogs.length) return
sinceId = Math.max(...newLogs.map(l => l.id))
logs.value = [...logs.value, ...newLogs].slice(-MAX_LOGS)
// Scroll after DOM update
setTimeout(scrollToBottom, 0)
} catch (e: any) {
error.value = e.message
}
}
function startPolling() {
if (timer) return
timer = setInterval(async () => {
await fetchLogs()
if (props.pipelineStatus !== 'running' && props.pipelineStatus !== 'in_progress') {
stopPolling()
}
}, 2000)
}
function stopPolling() {
if (timer) { clearInterval(timer); timer = null }
}
async function toggle() {
visible.value = !visible.value
if (visible.value) {
// Reset on open
userScrolled = false
await fetchLogs()
if (props.pipelineStatus === 'running' || props.pipelineStatus === 'in_progress') {
startPolling()
}
} else {
stopPolling()
}
}
// When status changes while panel is open do final fetch and stop
watch(() => props.pipelineStatus, async (newStatus) => {
if (!visible.value) return
if (newStatus !== 'running' && newStatus !== 'in_progress') {
stopPolling()
await fetchLogs()
} else {
startPolling()
}
})
onUnmounted(() => {
stopPolling()
})
</script>
<template>
<div class="mt-4">
<button
@click="toggle"
class="text-xs text-gray-500 hover:text-gray-300 border border-gray-800 rounded px-3 py-1.5 bg-gray-900/50 hover:bg-gray-900 transition-colors"
>
{{ visible ? '▲ Скрыть лог' : '▼ Показать лог' }}
</button>
<div v-show="visible" class="mt-2 bg-gray-950 border border-gray-800 rounded-lg p-4 font-mono text-xs max-h-[400px] overflow-y-auto" ref="consoleEl" @scroll="onScroll">
<div v-if="!logs.length && !error" class="text-gray-600">Нет записей...</div>
<div v-if="error" class="text-red-400">Ошибка: {{ error }}</div>
<div v-for="log in logs" :key="log.id" class="mb-1">
<span class="text-gray-600">{{ log.ts }}</span>
<span :class="[levelClass(log.level), 'ml-2 font-semibold']">[{{ log.level }}]</span>
<span :class="[levelClass(log.level), 'ml-2']">{{ log.message }}</span>
<pre v-if="log.extra_json" class="mt-0.5 ml-4 text-gray-500 whitespace-pre-wrap">{{ JSON.stringify(log.extra_json, null, 2) }}</pre>
</div>
</div>
</div>
</template>