@cat-factory/app 0.28.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/board/AgentFailureCard.vue +7 -1
- package/app/components/environments/EnvironmentStatusPanel.vue +76 -0
- package/app/components/panels/AgentStepDetail.vue +37 -0
- package/app/components/provisioning/ProvisioningLogsDrawer.vue +98 -0
- package/app/components/settings/ProviderConnectionPanel.vue +24 -1
- package/app/composables/api/provisioningLogs.ts +21 -0
- package/app/composables/useApi.ts +2 -0
- package/app/stores/provisioningLogs.ts +70 -0
- package/app/types/execution.ts +21 -0
- package/app/types/provisioningLogs.ts +32 -0
- package/package.json +1 -1
|
@@ -15,7 +15,13 @@ const agentRuns = useAgentRunsStore()
|
|
|
15
15
|
|
|
16
16
|
const compact = computed(() => props.variant === 'compact')
|
|
17
17
|
const failure = computed(() => props.run.failure)
|
|
18
|
-
const title = computed(() =>
|
|
18
|
+
const title = computed(() => {
|
|
19
|
+
// A `dispatch` failure means the container/runner never accepted the job — say so
|
|
20
|
+
// explicitly rather than the generic "Run failed", and show the verbatim provider
|
|
21
|
+
// error in the collapsible detail below.
|
|
22
|
+
if (failure.value?.kind === 'dispatch') return 'Container failed to start'
|
|
23
|
+
return props.run.kind === 'bootstrap' ? 'Bootstrap failed' : 'Run failed'
|
|
24
|
+
})
|
|
19
25
|
const retryLabel = computed(() =>
|
|
20
26
|
props.run.kind === 'bootstrap' ? 'Retry bootstrap' : 'Retry run',
|
|
21
27
|
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Reusable read-only view of an ephemeral environment's lifecycle: a status badge,
|
|
3
|
+
// the live URL, the TTL, and — when it failed/expired — the verbatim provider error.
|
|
4
|
+
// Used in a run's details (AgentStepDetail) so the Tester (and any env-consuming step)
|
|
5
|
+
// shows whether the env is spinning up / running / shut down / errored, with the error.
|
|
6
|
+
import type { RunEnvironment, HumanTestEnvironmentStatus } from '~/types/execution'
|
|
7
|
+
|
|
8
|
+
defineProps<{ environment: RunEnvironment | null; degradedReason?: string | null }>()
|
|
9
|
+
|
|
10
|
+
const ENV_STATUS_META: Record<
|
|
11
|
+
HumanTestEnvironmentStatus,
|
|
12
|
+
{ label: string; color: string; icon: string }
|
|
13
|
+
> = {
|
|
14
|
+
provisioning: { label: 'Spinning up…', color: 'text-amber-300', icon: 'i-lucide-loader-circle' },
|
|
15
|
+
ready: { label: 'Running', color: 'text-emerald-300', icon: 'i-lucide-circle-dot' },
|
|
16
|
+
failed: { label: 'Errored', color: 'text-rose-300', icon: 'i-lucide-circle-alert' },
|
|
17
|
+
expired: { label: 'Expired', color: 'text-slate-400', icon: 'i-lucide-circle-off' },
|
|
18
|
+
tearing_down: {
|
|
19
|
+
label: 'Shutting down…',
|
|
20
|
+
color: 'text-slate-400',
|
|
21
|
+
icon: 'i-lucide-loader-circle',
|
|
22
|
+
},
|
|
23
|
+
torn_down: { label: 'Shut down', color: 'text-slate-400', icon: 'i-lucide-circle-off' },
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<section class="rounded-lg border border-slate-800 bg-slate-900/60 p-3">
|
|
29
|
+
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
30
|
+
Ephemeral environment
|
|
31
|
+
</h3>
|
|
32
|
+
<div v-if="environment" class="space-y-2">
|
|
33
|
+
<div class="flex items-center gap-2 text-[13px]">
|
|
34
|
+
<UIcon
|
|
35
|
+
:name="ENV_STATUS_META[environment.status].icon"
|
|
36
|
+
class="h-3.5 w-3.5"
|
|
37
|
+
:class="[
|
|
38
|
+
ENV_STATUS_META[environment.status].color,
|
|
39
|
+
{
|
|
40
|
+
'animate-spin':
|
|
41
|
+
environment.status === 'provisioning' || environment.status === 'tearing_down',
|
|
42
|
+
},
|
|
43
|
+
]"
|
|
44
|
+
/>
|
|
45
|
+
<span :class="ENV_STATUS_META[environment.status].color">{{
|
|
46
|
+
ENV_STATUS_META[environment.status].label
|
|
47
|
+
}}</span>
|
|
48
|
+
</div>
|
|
49
|
+
<a
|
|
50
|
+
v-if="environment.url"
|
|
51
|
+
:href="environment.url"
|
|
52
|
+
target="_blank"
|
|
53
|
+
rel="noopener"
|
|
54
|
+
class="inline-flex items-center gap-1.5 break-all text-[13px] text-sky-300 hover:underline"
|
|
55
|
+
>
|
|
56
|
+
<UIcon name="i-lucide-external-link" class="h-3.5 w-3.5 shrink-0" />
|
|
57
|
+
{{ environment.url }}
|
|
58
|
+
</a>
|
|
59
|
+
<p v-if="environment.expiresAt" class="text-[11px] text-slate-500">
|
|
60
|
+
Expires {{ new Date(environment.expiresAt).toLocaleString() }}
|
|
61
|
+
</p>
|
|
62
|
+
<!-- The verbatim provider error when the environment failed/expired. -->
|
|
63
|
+
<pre
|
|
64
|
+
v-if="
|
|
65
|
+
environment.lastError &&
|
|
66
|
+
(environment.status === 'failed' || environment.status === 'expired')
|
|
67
|
+
"
|
|
68
|
+
class="mt-1 max-h-32 overflow-auto whitespace-pre-wrap rounded border border-rose-900/60 bg-rose-950/40 p-1.5 text-[11px] text-rose-200/90"
|
|
69
|
+
>{{ environment.lastError }}</pre
|
|
70
|
+
>
|
|
71
|
+
</div>
|
|
72
|
+
<p v-else class="text-[12px] text-slate-500">
|
|
73
|
+
{{ degradedReason ?? 'No ephemeral environment for this run.' }}
|
|
74
|
+
</p>
|
|
75
|
+
</section>
|
|
76
|
+
</template>
|
|
@@ -6,6 +6,8 @@ import { agentKindMeta } from '~/utils/catalog'
|
|
|
6
6
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
7
7
|
import StepMetadataCard from '~/components/panels/StepMetadataCard.vue'
|
|
8
8
|
import StepTestReport from '~/components/panels/StepTestReport.vue'
|
|
9
|
+
import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
|
|
10
|
+
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
9
11
|
import { useStepTimer } from '~/composables/useStepTimer'
|
|
10
12
|
import { useStepProse } from '~/composables/useStepProse'
|
|
11
13
|
import { useStepApproval } from '~/composables/useStepApproval'
|
|
@@ -50,6 +52,16 @@ const pctOf = (n: number) => `${Math.round(n * 100)}%`
|
|
|
50
52
|
const testReport = computed(() => step.value?.test?.lastReport ?? null)
|
|
51
53
|
const testPhase = computed(() => step.value?.test ?? null)
|
|
52
54
|
|
|
55
|
+
// The ephemeral environment this step runs against (deployer provisions it; tester/
|
|
56
|
+
// coder consume it), so the panel shows its spinning-up/running/shutdown/errored state.
|
|
57
|
+
const stepEnvironment = computed(() => step.value?.environment ?? null)
|
|
58
|
+
|
|
59
|
+
// The run's infrastructure attempts (container/runner/env spin-up + tear-down), behind
|
|
60
|
+
// a toggle. This is the surface that makes the per-run `container` log rows + the
|
|
61
|
+
// executionId filter visible — most useful when the run failed to start a container.
|
|
62
|
+
const showProvisioning = ref(false)
|
|
63
|
+
const executionId = computed(() => instance.value?.id ?? null)
|
|
64
|
+
|
|
53
65
|
// A failed run is no longer executing: a step left mid-flight (state still
|
|
54
66
|
// `working`, no `finishedAt`) must stop looking live — no ticking clock, no
|
|
55
67
|
// "spinning up" phase, no spinner.
|
|
@@ -310,6 +322,31 @@ async function copyOutput() {
|
|
|
310
322
|
@resolve="resolveCompanionCap"
|
|
311
323
|
/>
|
|
312
324
|
|
|
325
|
+
<!-- ephemeral environment lifecycle (spinning up / running / shut down /
|
|
326
|
+
errored + the exact error), when this step runs against one -->
|
|
327
|
+
<EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
|
|
328
|
+
|
|
329
|
+
<!-- this run's infrastructure attempts (container/runner/env spin-up +
|
|
330
|
+
tear-down): the surface for the per-run container log rows + the exact
|
|
331
|
+
provider error, behind a toggle (most useful on a failed-to-start run) -->
|
|
332
|
+
<div v-if="executionId">
|
|
333
|
+
<UButton
|
|
334
|
+
:icon="showProvisioning ? 'i-lucide-chevron-up' : 'i-lucide-scroll-text'"
|
|
335
|
+
variant="ghost"
|
|
336
|
+
size="xs"
|
|
337
|
+
@click="showProvisioning = !showProvisioning"
|
|
338
|
+
>
|
|
339
|
+
{{
|
|
340
|
+
showProvisioning ? 'Hide infrastructure attempts' : 'Infrastructure attempts'
|
|
341
|
+
}}
|
|
342
|
+
</UButton>
|
|
343
|
+
<ProvisioningLogsDrawer
|
|
344
|
+
v-if="showProvisioning"
|
|
345
|
+
class="mt-2"
|
|
346
|
+
:execution-id="executionId"
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
313
350
|
<!-- tester report: what was tested, the per-area outcomes, the concerns
|
|
314
351
|
it raised and the greenlight verdict; plus the fixer-loop phase -->
|
|
315
352
|
<StepTestReport v-if="testReport" :report="testReport" :phase="testPhase" />
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The "View logs" surface for the unified provisioning event log: every attempt to
|
|
3
|
+
// spin up / tear down infrastructure (environment provider, runner-pool, or per-run
|
|
4
|
+
// container), with its outcome and — for failures — the verbatim provider/runtime
|
|
5
|
+
// error. Two modes, mutually exclusive: pass `subsystem` for the provider config
|
|
6
|
+
// panels' drawer, or `executionId` for a run's "Infrastructure attempts" drawer (which
|
|
7
|
+
// surfaces that run's container/runner/env attempts). Loaded on mount + re-loadable.
|
|
8
|
+
import { onMounted } from 'vue'
|
|
9
|
+
import type { ProvisioningOperation, ProvisioningSubsystem } from '~/types/provisioningLogs'
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{ subsystem?: ProvisioningSubsystem; executionId?: string }>()
|
|
12
|
+
|
|
13
|
+
const store = useProvisioningLogsStore()
|
|
14
|
+
const state = computed(() =>
|
|
15
|
+
props.executionId
|
|
16
|
+
? (store.byExecution[props.executionId] ?? { entries: [], loading: false, error: null })
|
|
17
|
+
: store.bySubsystem[props.subsystem ?? 'environment'],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
function reload() {
|
|
21
|
+
if (props.executionId) void store.loadForExecution(props.executionId)
|
|
22
|
+
else if (props.subsystem) void store.load(props.subsystem)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onMounted(reload)
|
|
26
|
+
|
|
27
|
+
const OPERATION_LABEL: Record<ProvisioningOperation, string> = {
|
|
28
|
+
provision: 'Spin up',
|
|
29
|
+
teardown: 'Tear down',
|
|
30
|
+
status: 'Status check',
|
|
31
|
+
dispatch: 'Spin up',
|
|
32
|
+
release: 'Tear down',
|
|
33
|
+
'poll-failure': 'Health check',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function when(epochMs: number): string {
|
|
37
|
+
return new Date(epochMs).toLocaleString()
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="rounded-lg border border-slate-700 bg-slate-900/50">
|
|
43
|
+
<div class="flex items-center justify-between border-b border-slate-800 px-3 py-2">
|
|
44
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
45
|
+
Provisioning logs
|
|
46
|
+
</p>
|
|
47
|
+
<UButton
|
|
48
|
+
icon="i-lucide-rotate-ccw"
|
|
49
|
+
variant="ghost"
|
|
50
|
+
size="xs"
|
|
51
|
+
:loading="state.loading"
|
|
52
|
+
@click="reload"
|
|
53
|
+
>
|
|
54
|
+
Refresh
|
|
55
|
+
</UButton>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<p v-if="state.error" class="px-3 py-2 text-[12px] text-rose-300">{{ state.error }}</p>
|
|
59
|
+
<p
|
|
60
|
+
v-else-if="!state.loading && state.entries.length === 0"
|
|
61
|
+
class="px-3 py-3 text-[12px] text-slate-500"
|
|
62
|
+
>
|
|
63
|
+
No provisioning attempts recorded yet.
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<ul v-else class="max-h-80 divide-y divide-slate-800 overflow-auto">
|
|
67
|
+
<li v-for="entry in state.entries" :key="entry.id" class="px-3 py-2">
|
|
68
|
+
<div class="flex items-center gap-2 text-[12px]">
|
|
69
|
+
<UIcon
|
|
70
|
+
:name="entry.outcome === 'success' ? 'i-lucide-check-circle' : 'i-lucide-x-circle'"
|
|
71
|
+
class="h-3.5 w-3.5 shrink-0"
|
|
72
|
+
:class="entry.outcome === 'success' ? 'text-emerald-400' : 'text-rose-400'"
|
|
73
|
+
/>
|
|
74
|
+
<span class="font-medium text-slate-200">{{ OPERATION_LABEL[entry.operation] }}</span>
|
|
75
|
+
<span
|
|
76
|
+
class="rounded px-1.5 py-0.5 text-[10px] uppercase tracking-wide"
|
|
77
|
+
:class="
|
|
78
|
+
entry.outcome === 'success'
|
|
79
|
+
? 'bg-emerald-950/60 text-emerald-300'
|
|
80
|
+
: 'bg-rose-950/60 text-rose-300'
|
|
81
|
+
"
|
|
82
|
+
>{{ entry.outcome }}</span
|
|
83
|
+
>
|
|
84
|
+
<span class="ml-auto text-[11px] text-slate-500">{{ when(entry.createdAt) }}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div v-if="entry.targetId" class="mt-0.5 text-[11px] text-slate-500">
|
|
87
|
+
{{ entry.providerId ? `${entry.providerId} · ` : '' }}{{ entry.targetId }}
|
|
88
|
+
</div>
|
|
89
|
+
<!-- The verbatim provider/runtime error on a failed attempt. -->
|
|
90
|
+
<pre
|
|
91
|
+
v-if="entry.error"
|
|
92
|
+
class="mt-1 max-h-28 overflow-auto whitespace-pre-wrap rounded border border-rose-900/50 bg-rose-950/30 p-1.5 text-[11px] text-rose-200/90"
|
|
93
|
+
>{{ entry.error }}</pre
|
|
94
|
+
>
|
|
95
|
+
</li>
|
|
96
|
+
</ul>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { computed, ref, watch } from 'vue'
|
|
11
11
|
import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
12
12
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
13
|
+
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
13
14
|
|
|
14
15
|
const ui = useUiStore()
|
|
15
16
|
const store = useProviderConnectionsStore()
|
|
@@ -42,6 +43,14 @@ const meta = computed(() => (kind.value ? META[kind.value] : null))
|
|
|
42
43
|
const descriptor = computed(() => (kind.value ? store.descriptorFor(kind.value) : null))
|
|
43
44
|
const connection = computed(() => (kind.value ? store.connectionFor(kind.value) : null))
|
|
44
45
|
|
|
46
|
+
// "View logs": the provisioning event history for this provider's subsystem — every
|
|
47
|
+
// spin-up / tear-down attempt with its outcome and the exact error. The panel kind
|
|
48
|
+
// maps 1:1 to the log subsystem ('environment' / 'runner-pool').
|
|
49
|
+
const showLogs = ref(false)
|
|
50
|
+
watch(kind, () => {
|
|
51
|
+
showLogs.value = false
|
|
52
|
+
})
|
|
53
|
+
|
|
45
54
|
// Per-field draft values, keyed by field key (blank ⇒ fall back to default/stored value).
|
|
46
55
|
const values = ref<Record<string, string>>({})
|
|
47
56
|
const testResult = ref<{ ok: boolean; message?: string } | null>(null)
|
|
@@ -225,7 +234,21 @@ function fieldHelp(key: string): string | undefined {
|
|
|
225
234
|
</template>
|
|
226
235
|
<template #body>
|
|
227
236
|
<div v-if="descriptor" class="space-y-4">
|
|
228
|
-
<
|
|
237
|
+
<div class="flex items-start justify-between gap-3">
|
|
238
|
+
<p class="text-xs text-slate-400">{{ meta?.blurb }}</p>
|
|
239
|
+
<UButton
|
|
240
|
+
:icon="showLogs ? 'i-lucide-chevron-up' : 'i-lucide-scroll-text'"
|
|
241
|
+
variant="ghost"
|
|
242
|
+
size="xs"
|
|
243
|
+
class="shrink-0"
|
|
244
|
+
@click="showLogs = !showLogs"
|
|
245
|
+
>
|
|
246
|
+
{{ showLogs ? 'Hide logs' : 'View logs' }}
|
|
247
|
+
</UButton>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Provisioning attempt history for this provider's subsystem. -->
|
|
251
|
+
<ProvisioningLogsDrawer v-if="showLogs && kind" :subsystem="kind" />
|
|
229
252
|
|
|
230
253
|
<!-- Saved connection summary -->
|
|
231
254
|
<div
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProvisioningLogEntry, ProvisioningSubsystem } from '~/types/provisioningLogs'
|
|
2
|
+
import type { ApiContext } from './context'
|
|
3
|
+
|
|
4
|
+
/** Read access to the unified provisioning event log (the "View logs" drawers + run details). */
|
|
5
|
+
export function provisioningLogsApi({ http, ws }: ApiContext) {
|
|
6
|
+
return {
|
|
7
|
+
listProvisioningLogs: (
|
|
8
|
+
workspaceId: string,
|
|
9
|
+
params: { subsystem?: ProvisioningSubsystem; executionId?: string; limit?: number } = {},
|
|
10
|
+
) => {
|
|
11
|
+
const q = new URLSearchParams()
|
|
12
|
+
if (params.subsystem) q.set('subsystem', params.subsystem)
|
|
13
|
+
if (params.executionId) q.set('executionId', params.executionId)
|
|
14
|
+
if (params.limit != null) q.set('limit', String(params.limit))
|
|
15
|
+
const qs = q.toString()
|
|
16
|
+
return http<{ entries: ProvisioningLogEntry[] }>(
|
|
17
|
+
`${ws(workspaceId)}/provisioning-logs${qs ? `?${qs}` : ''}`,
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -13,6 +13,7 @@ import { modelsApi } from './api/models'
|
|
|
13
13
|
import { notificationsApi } from './api/notifications'
|
|
14
14
|
import { presetsApi } from './api/presets'
|
|
15
15
|
import { providerConnectionsApi } from './api/providerConnections'
|
|
16
|
+
import { provisioningLogsApi } from './api/provisioningLogs'
|
|
16
17
|
import { recurringApi } from './api/recurring'
|
|
17
18
|
import { releaseHealthApi } from './api/releaseHealth'
|
|
18
19
|
import { sandboxApi } from './api/sandbox'
|
|
@@ -88,6 +89,7 @@ export function useApi() {
|
|
|
88
89
|
...notificationsApi(ctx),
|
|
89
90
|
...presetsApi(ctx),
|
|
90
91
|
...providerConnectionsApi(ctx),
|
|
92
|
+
...provisioningLogsApi(ctx),
|
|
91
93
|
...releaseHealthApi(ctx),
|
|
92
94
|
...recurringApi(ctx),
|
|
93
95
|
...sandboxApi(ctx),
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
|
+
import type { ProvisioningLogEntry, ProvisioningSubsystem } from '~/types/provisioningLogs'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
|
|
6
|
+
interface LogState {
|
|
7
|
+
entries: ProvisioningLogEntry[]
|
|
8
|
+
loading: boolean
|
|
9
|
+
error: string | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function emptyState(): LogState {
|
|
13
|
+
return { entries: [], loading: false, error: null }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The unified provisioning event log, loaded on demand for two surfaces:
|
|
18
|
+
* - per SUBSYSTEM, for the "View logs" drawers in the environment-provider and
|
|
19
|
+
* self-hosted runner-pool config panels;
|
|
20
|
+
* - per EXECUTION (run), for the "Infrastructure attempts" drawer in a run's step
|
|
21
|
+
* details — this is the surface that makes the `container` rows (per-run container
|
|
22
|
+
* dispatch/release/poll-failure) and the `executionId` filter visible.
|
|
23
|
+
* Each shows every spin-up/tear-down attempt with its outcome + the exact error.
|
|
24
|
+
*/
|
|
25
|
+
export const useProvisioningLogsStore = defineStore('provisioningLogs', () => {
|
|
26
|
+
const api = useApi()
|
|
27
|
+
const bySubsystem = reactive<Record<ProvisioningSubsystem, LogState>>({
|
|
28
|
+
environment: emptyState(),
|
|
29
|
+
'runner-pool': emptyState(),
|
|
30
|
+
container: emptyState(),
|
|
31
|
+
})
|
|
32
|
+
const byExecution = reactive<Record<string, LogState>>({})
|
|
33
|
+
|
|
34
|
+
async function load(subsystem: ProvisioningSubsystem) {
|
|
35
|
+
const ws = useWorkspaceStore()
|
|
36
|
+
const s = bySubsystem[subsystem]
|
|
37
|
+
s.loading = true
|
|
38
|
+
s.error = null
|
|
39
|
+
try {
|
|
40
|
+
const { entries } = await api.listProvisioningLogs(ws.requireId(), { subsystem, limit: 200 })
|
|
41
|
+
s.entries = entries
|
|
42
|
+
} catch (err) {
|
|
43
|
+
s.error = err instanceof Error ? err.message : 'Failed to load logs'
|
|
44
|
+
s.entries = []
|
|
45
|
+
} finally {
|
|
46
|
+
s.loading = false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function loadForExecution(executionId: string) {
|
|
51
|
+
const ws = useWorkspaceStore()
|
|
52
|
+
const s = (byExecution[executionId] ??= emptyState())
|
|
53
|
+
s.loading = true
|
|
54
|
+
s.error = null
|
|
55
|
+
try {
|
|
56
|
+
const { entries } = await api.listProvisioningLogs(ws.requireId(), {
|
|
57
|
+
executionId,
|
|
58
|
+
limit: 200,
|
|
59
|
+
})
|
|
60
|
+
s.entries = entries
|
|
61
|
+
} catch (err) {
|
|
62
|
+
s.error = err instanceof Error ? err.message : 'Failed to load logs'
|
|
63
|
+
s.entries = []
|
|
64
|
+
} finally {
|
|
65
|
+
s.loading = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { bySubsystem, byExecution, load, loadForExecution }
|
|
70
|
+
})
|
package/app/types/execution.ts
CHANGED
|
@@ -339,6 +339,13 @@ export interface PipelineStep {
|
|
|
339
339
|
* Absent on non-human-test steps. Mirrors `humanTestStepStateSchema`.
|
|
340
340
|
*/
|
|
341
341
|
humanTest?: HumanTestStepState | null
|
|
342
|
+
/**
|
|
343
|
+
* The ephemeral environment this step runs against (when its block has one), so a
|
|
344
|
+
* run's details show its lifecycle state + the exact error. Populated by the engine
|
|
345
|
+
* for container/deployer steps; the `human-test` gate uses `humanTest.environment`.
|
|
346
|
+
* Mirrors `runEnvironmentSchema`.
|
|
347
|
+
*/
|
|
348
|
+
environment?: RunEnvironment | null
|
|
342
349
|
}
|
|
343
350
|
|
|
344
351
|
/** One failing CI check the gate's precheck saw (mirrors `gateFailingCheckSchema`). */
|
|
@@ -410,6 +417,20 @@ export interface HumanTestEnvironment {
|
|
|
410
417
|
expiresAt?: number | null
|
|
411
418
|
}
|
|
412
419
|
|
|
420
|
+
/**
|
|
421
|
+
* The ephemeral environment a run's step is associated with — surfaced in run details
|
|
422
|
+
* so its spinning-up / running / shut-down / errored state + the exact error show next
|
|
423
|
+
* to the consuming step (tester/coder). Mirrors `runEnvironmentSchema`.
|
|
424
|
+
*/
|
|
425
|
+
export interface RunEnvironment {
|
|
426
|
+
id: string
|
|
427
|
+
url: string | null
|
|
428
|
+
status: HumanTestEnvironmentStatus
|
|
429
|
+
expiresAt?: number | null
|
|
430
|
+
/** The verbatim provider error when the environment failed/expired, else null. */
|
|
431
|
+
lastError?: string | null
|
|
432
|
+
}
|
|
433
|
+
|
|
413
434
|
/** One fix / pull-main round on a `human-test` gate (mirrors `humanTestRoundSchema`). */
|
|
414
435
|
export interface HumanTestRound {
|
|
415
436
|
kind: 'fix' | 'pull-main'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Frontend mirror of the unified provisioning event-log wire shapes
|
|
2
|
+
// (`@cat-factory/contracts` provisioning-logs.ts). Drives the "View logs" drawers in
|
|
3
|
+
// the environment-provider + runner-pool config panels and the run-details env surface.
|
|
4
|
+
|
|
5
|
+
export type ProvisioningSubsystem = 'environment' | 'runner-pool' | 'container'
|
|
6
|
+
|
|
7
|
+
export type ProvisioningOperation =
|
|
8
|
+
| 'provision'
|
|
9
|
+
| 'teardown'
|
|
10
|
+
| 'status'
|
|
11
|
+
| 'dispatch'
|
|
12
|
+
| 'release'
|
|
13
|
+
| 'poll-failure'
|
|
14
|
+
|
|
15
|
+
export type ProvisioningOutcome = 'success' | 'failure'
|
|
16
|
+
|
|
17
|
+
/** One provisioning attempt (spin-up / tear-down), as returned by the logs endpoint. */
|
|
18
|
+
export interface ProvisioningLogEntry {
|
|
19
|
+
id: string
|
|
20
|
+
workspaceId: string
|
|
21
|
+
subsystem: ProvisioningSubsystem
|
|
22
|
+
operation: ProvisioningOperation
|
|
23
|
+
targetId: string | null
|
|
24
|
+
providerId: string | null
|
|
25
|
+
blockId: string | null
|
|
26
|
+
executionId: string | null
|
|
27
|
+
outcome: ProvisioningOutcome
|
|
28
|
+
/** The verbatim provider/runtime error on a failure, else null. */
|
|
29
|
+
error: string | null
|
|
30
|
+
detail: string | null
|
|
31
|
+
createdAt: number
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|