kin: KIN-084 Live console в Pipeline view: скрытая по умолчанию панель (раскрывается по клику), показывающая полный лог выполнения в реальном времени: запуск PM → параметры вызова claude → получен ответ JSON → парсинг pipeline → запуск backend_dev → параметры вызова → ответ → запуск tester... Как терминал, моноширинный шрифт, автоскролл. Runner пишет каждый шаг в отдельную таблицу pipeline_log или в agent_logs с детализацией. GUI поллит и дописывает.

This commit is contained in:
Gros Frumos 2026-03-17 17:40:21 +02:00
parent 939a30a3de
commit 4144c521be

View file

@ -773,13 +773,75 @@ async function addDecision() {
<span class="text-gray-700">|</span>
<router-link :to="`/chat/${project.id}`" class="text-indigo-500 hover:text-indigo-400 text-sm no-underline">Чат</router-link>
</div>
<div class="flex items-center gap-3 mb-2">
<div class="flex items-center gap-3 mb-2 flex-wrap">
<h1 class="text-xl font-bold text-gray-100">{{ project.id }}</h1>
<span class="text-gray-400">{{ project.name }}</span>
<Badge :text="project.status" :color="project.status === 'active' ? 'green' : 'gray'" />
<Badge v-if="project.project_type && project.project_type !== 'development'"
:text="project.project_type"
:color="project.project_type === 'operations' ? 'orange' : 'green'" />
<button
@click="runDeploy"
:disabled="deploying || !hasDeployConfig"
:title="hasDeployConfig ? 'Deploy project' : 'Настройте deploy-параметры в Settings'"
class="px-3 py-1 text-xs bg-teal-900/50 text-teal-400 border border-teal-800 rounded hover:bg-teal-900 disabled:opacity-50 ml-auto"
>
<span v-if="deploying" class="inline-block w-3 h-3 border-2 border-teal-400 border-t-transparent rounded-full animate-spin mr-1"></span>
{{ deploying ? 'Deploying...' : 'Deploy' }}
</button>
</div>
<!-- Deploy result -->
<div v-if="deployResult" class="mb-3 p-3 rounded border text-xs font-mono"
:class="deployResult.overall_success !== false && deployResult.success ? 'border-teal-800 bg-teal-950/30 text-teal-300' : 'border-red-800 bg-red-950/30 text-red-300'">
<div class="flex items-center gap-2 mb-1">
<span :class="deployResult.overall_success !== false && deployResult.success ? 'text-teal-400' : 'text-red-400'" class="font-semibold">
{{ deployResult.overall_success !== false && deployResult.success ? 'Deploy succeeded' : 'Deploy failed' }}
</span>
<span class="text-gray-500">{{ deployResult.duration_seconds }}s</span>
<button @click.stop="deployResult = null" class="ml-auto text-gray-600 hover:text-gray-400 bg-transparent border-none cursor-pointer text-xs">x</button>
</div>
<!-- Structured steps -->
<div v-if="deployResult.results?.length" class="space-y-1 mt-1">
<details v-for="step in deployResult.results" :key="step.step" class="border border-gray-700 rounded">
<summary class="flex items-center gap-2 px-2 py-1 cursor-pointer list-none">
<span :class="step.exit_code === 0 ? 'text-teal-400' : 'text-red-400'" class="font-semibold text-[10px]">{{ step.exit_code === 0 ? 'ok' : 'fail' }}</span>
<span class="text-gray-300 text-[11px]">{{ step.step }}</span>
<span class="text-gray-600 text-[10px] ml-auto">exit {{ step.exit_code }}</span>
</summary>
<div class="px-2 pb-2">
<pre v-if="step.stdout" class="whitespace-pre-wrap text-gray-300 max-h-32 overflow-y-auto text-[10px]">{{ step.stdout }}</pre>
<pre v-if="step.stderr" class="whitespace-pre-wrap text-red-400/80 max-h-32 overflow-y-auto text-[10px] mt-1">{{ step.stderr }}</pre>
</div>
</details>
</div>
<!-- Legacy output -->
<template v-else>
<pre v-if="deployResult.stdout" class="whitespace-pre-wrap text-gray-300 max-h-40 overflow-y-auto">{{ deployResult.stdout }}</pre>
<pre v-if="deployResult.stderr" class="whitespace-pre-wrap text-red-400/80 max-h-40 overflow-y-auto mt-1">{{ deployResult.stderr }}</pre>
</template>
<!-- Dependents -->
<div v-if="deployResult.dependents_deployed?.length" class="mt-2 border-t border-gray-700 pt-2">
<p class="text-xs text-gray-400 font-semibold mb-1">Зависимые проекты:</p>
<details v-for="dep in deployResult.dependents_deployed" :key="dep.project_id" class="border border-gray-700 rounded mb-1">
<summary class="flex items-center gap-2 px-2 py-1 cursor-pointer list-none">
<span :class="dep.success ? 'text-teal-400' : 'text-red-400'" class="font-semibold text-[10px]">{{ dep.success ? 'ok' : 'fail' }}</span>
<span class="text-gray-300 text-[11px]">{{ dep.project_name }}</span>
</summary>
<div class="px-2 pb-2 space-y-1">
<details v-for="step in dep.results" :key="step.step" class="border border-gray-800 rounded">
<summary class="flex items-center gap-2 px-2 py-0.5 cursor-pointer list-none">
<span :class="step.exit_code === 0 ? 'text-teal-400' : 'text-red-400'" class="text-[10px]">{{ step.exit_code === 0 ? 'ok' : 'fail' }}</span>
<span class="text-gray-400 text-[10px]">{{ step.step }}</span>
</summary>
<div class="px-2 pb-1">
<pre v-if="step.stdout" class="whitespace-pre-wrap text-gray-300 max-h-24 overflow-y-auto text-[10px]">{{ step.stdout }}</pre>
<pre v-if="step.stderr" class="whitespace-pre-wrap text-red-400/80 max-h-24 overflow-y-auto text-[10px]">{{ step.stderr }}</pre>
</div>
</details>
</div>
</details>
</div>
</div>
<div class="flex gap-2 flex-wrap mb-2" v-if="project.tech_stack?.length">
<Badge v-for="t in project.tech_stack" :key="t" :text="t" color="purple" />
@ -804,20 +866,21 @@ async function addDecision() {
</div>
<!-- Tabs -->
<div class="flex gap-1 mb-4 border-b border-gray-800">
<button v-for="tab in (['tasks', 'phases', 'decisions', 'modules', 'kanban', 'environments'] as const)" :key="tab"
<div class="flex gap-1 mb-4 border-b border-gray-800 flex-wrap">
<button v-for="tab in (['tasks', 'phases', 'decisions', 'modules', 'kanban', 'environments', 'links'] as const)" :key="tab"
@click="activeTab = tab"
class="px-4 py-2 text-sm border-b-2 transition-colors"
:class="activeTab === tab
? 'text-gray-200 border-blue-500'
: 'text-gray-500 border-transparent hover:text-gray-300'">
{{ tab === 'kanban' ? 'Kanban' : tab === 'environments' ? 'Среды' : tab.charAt(0).toUpperCase() + tab.slice(1) }}
{{ tab === 'kanban' ? 'Kanban' : tab === 'environments' ? 'Среды' : tab === 'links' ? 'Links' : tab.charAt(0).toUpperCase() + tab.slice(1) }}
<span class="text-xs text-gray-600 ml-1">
{{ tab === 'tasks' ? project.tasks.length
: tab === 'phases' ? phases.length
: tab === 'decisions' ? project.decisions.length
: tab === 'modules' ? project.modules.length
: tab === 'environments' ? environments.length
: tab === 'links' ? links.length
: project.tasks.length }}
</span>
</button>