kin: KIN-083 Healthcheck claude CLI auth: перед запуском pipeline проверять что claude залогинен (быстрый claude -p 'ok' --output-format json, проверить is_error и 'Not logged in'). Если не залогинен — не запускать pipeline, а показать ошибку 'Claude CLI requires login' в GUI с инструкцией.
This commit is contained in:
parent
a80679ae72
commit
bfc8f1c0bb
18 changed files with 1390 additions and 57 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { api, type ProjectDetail, type AuditResult, type Phase, type Task } from '../api'
|
||||
import { api, ApiError, type ProjectDetail, type AuditResult, type Phase, type Task } from '../api'
|
||||
import Badge from '../components/Badge.vue'
|
||||
import Modal from '../components/Modal.vue'
|
||||
|
||||
|
|
@ -18,6 +18,7 @@ const activeTab = ref<'tasks' | 'phases' | 'decisions' | 'modules' | 'kanban'>('
|
|||
const phases = ref<Phase[]>([])
|
||||
const phasesLoading = ref(false)
|
||||
const phaseError = ref('')
|
||||
const claudeLoginError = ref(false)
|
||||
const showReviseModal = ref(false)
|
||||
const revisePhaseId = ref<number | null>(null)
|
||||
const reviseComment = ref('')
|
||||
|
|
@ -76,11 +77,16 @@ async function approvePhase(phaseId: number) {
|
|||
async function startPhase() {
|
||||
startPhaseSaving.value = true
|
||||
phaseError.value = ''
|
||||
claudeLoginError.value = false
|
||||
try {
|
||||
await api.startPhase(props.id)
|
||||
await loadPhases()
|
||||
} catch (e: any) {
|
||||
phaseError.value = e.message
|
||||
if (e instanceof ApiError && e.code === 'claude_auth_required') {
|
||||
claudeLoginError.value = true
|
||||
} else {
|
||||
phaseError.value = e.message
|
||||
}
|
||||
} finally {
|
||||
startPhaseSaving.value = false
|
||||
}
|
||||
|
|
@ -702,6 +708,17 @@ async function addDecision() {
|
|||
|
||||
<!-- Phases Tab -->
|
||||
<div v-if="activeTab === 'phases'">
|
||||
<div v-if="claudeLoginError" class="mb-3 px-4 py-3 border border-yellow-700 bg-yellow-950/30 rounded">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-yellow-300">⚠ Claude CLI requires login</p>
|
||||
<p class="text-xs text-yellow-200/80 mt-1">Откройте терминал и выполните:</p>
|
||||
<code class="text-xs text-yellow-400 font-mono bg-black/30 px-2 py-0.5 rounded mt-1 inline-block">claude login</code>
|
||||
<p class="text-xs text-gray-500 mt-1">После входа повторите запуск pipeline.</p>
|
||||
</div>
|
||||
<button @click="claudeLoginError = false" class="text-gray-600 hover:text-gray-400 bg-transparent border-none cursor-pointer text-xs shrink-0">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="phasesLoading" class="text-gray-500 text-sm">Loading phases...</p>
|
||||
<p v-else-if="phaseError" class="text-red-400 text-sm">{{ phaseError }}</p>
|
||||
<div v-else-if="phases.length === 0" class="text-gray-600 text-sm">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { api, type TaskFull, type PipelineStep, type PendingAction, type DeployResult } from '../api'
|
||||
import { api, ApiError, type TaskFull, type PipelineStep, type PendingAction, type DeployResult } from '../api'
|
||||
import Badge from '../components/Badge.vue'
|
||||
import Modal from '../components/Modal.vue'
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ const router = useRouter()
|
|||
const task = ref<TaskFull | null>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const claudeLoginError = ref(false)
|
||||
const selectedStep = ref<PipelineStep | null>(null)
|
||||
const polling = ref(false)
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
|
@ -206,12 +207,17 @@ async function revise() {
|
|||
}
|
||||
|
||||
async function runPipeline() {
|
||||
claudeLoginError.value = false
|
||||
try {
|
||||
await api.runTask(props.id)
|
||||
startPolling()
|
||||
await load()
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
if (e instanceof ApiError && e.code === 'claude_auth_required') {
|
||||
claudeLoginError.value = true
|
||||
} else {
|
||||
error.value = e.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -521,6 +527,19 @@ async function saveEdit() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Claude login error banner -->
|
||||
<div v-if="claudeLoginError" class="mt-3 px-4 py-3 border border-yellow-700 bg-yellow-950/30 rounded">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-yellow-300">⚠ Claude CLI requires login</p>
|
||||
<p class="text-xs text-yellow-200/80 mt-1">Откройте терминал и выполните:</p>
|
||||
<code class="text-xs text-yellow-400 font-mono bg-black/30 px-2 py-0.5 rounded mt-1 inline-block">claude login</code>
|
||||
<p class="text-xs text-gray-500 mt-1">После входа повторите запуск pipeline.</p>
|
||||
</div>
|
||||
<button @click="claudeLoginError = false" class="text-gray-600 hover:text-gray-400 bg-transparent border-none cursor-pointer text-xs shrink-0">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deploy result inline block -->
|
||||
<div v-if="deployResult" class="mx-0 mt-2 p-3 rounded border text-xs font-mono"
|
||||
:class="deployResult.success ? 'border-teal-800 bg-teal-950/30 text-teal-300' : 'border-red-800 bg-red-950/30 text-red-300'">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue