@cat-factory/app 0.46.10 → 0.46.11
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/observability/StepMetricsBar.vue +31 -11
- package/app/components/observability/StepModelActivity.vue +3 -2
- package/app/components/panels/AgentStepDetail.vue +52 -39
- package/app/components/panels/DecisionModal.vue +12 -5
- package/app/components/panels/GenericStructuredResultView.vue +16 -6
- package/app/components/panels/InspectorPanel.vue +35 -28
- package/app/components/panels/ObservabilityPanel.vue +92 -49
- package/app/components/panels/StepMetadataCard.vue +87 -30
- package/app/components/panels/StepRestartControl.vue +4 -3
- package/app/components/panels/StepRunMeta.vue +23 -10
- package/app/components/panels/StepTestReport.vue +14 -11
- package/app/components/panels/inspector/ContainerSummary.vue +25 -14
- package/app/components/panels/inspector/EpicChildren.vue +7 -4
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +60 -19
- package/app/components/panels/inspector/ServiceFragments.vue +3 -2
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +18 -13
- package/app/components/panels/inspector/ServiceTestConfig.vue +50 -39
- package/app/components/panels/inspector/TaskAgentConfig.vue +6 -5
- package/app/components/panels/inspector/TaskDependencies.vue +9 -4
- package/app/components/panels/inspector/TaskEstimateBadge.vue +14 -13
- package/app/components/panels/inspector/TaskExecution.vue +65 -31
- package/app/components/panels/inspector/TaskRunSettings.vue +93 -56
- package/app/components/panels/inspector/TaskStructure.vue +5 -4
- package/i18n/locales/en.json +402 -0
- package/i18n/locales/es.json +402 -0
- package/i18n/locales/fr.json +402 -0
- package/i18n/locales/pl.json +402 -0
- package/i18n/locales/uk.json +402 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue'
|
|
3
|
-
import type { AgentState, PipelineStep, CompanionVerdict } from '~/types/execution'
|
|
3
|
+
import type { AgentState, PipelineStep, CompanionVerdict, StepApproval } from '~/types/execution'
|
|
4
4
|
import { subtaskIconClass } from '~/utils/pipelineRender'
|
|
5
5
|
import StepModelActivity from '~/components/observability/StepModelActivity.vue'
|
|
6
6
|
|
|
@@ -21,20 +21,28 @@ const props = defineProps<{
|
|
|
21
21
|
}>()
|
|
22
22
|
|
|
23
23
|
const models = useModelsStore()
|
|
24
|
+
const { t, d } = useI18n()
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
pending:
|
|
27
|
-
working:
|
|
28
|
-
waiting_decision:
|
|
29
|
-
done:
|
|
26
|
+
const STATE_LABEL_KEYS: Record<AgentState, string> = {
|
|
27
|
+
pending: 'panels.stepMeta.state.pending',
|
|
28
|
+
working: 'panels.stepMeta.state.working',
|
|
29
|
+
waiting_decision: 'panels.stepMeta.state.waiting_decision',
|
|
30
|
+
done: 'panels.stepMeta.state.done',
|
|
31
|
+
}
|
|
32
|
+
const STATE_COLOR: Record<AgentState, string> = {
|
|
33
|
+
pending: '#64748b',
|
|
34
|
+
working: '#6366f1',
|
|
35
|
+
waiting_decision: '#f59e0b',
|
|
36
|
+
done: '#22c55e',
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
// The state badge: a step left mid-flight on a failed run keeps `state: 'working'`,
|
|
33
40
|
// so report it as "Failed" rather than the misleading "Working".
|
|
34
41
|
const stateMeta = computed(() => {
|
|
35
42
|
const s = props.step
|
|
36
|
-
if (props.runFailed && s.state === 'working')
|
|
37
|
-
|
|
43
|
+
if (props.runFailed && s.state === 'working')
|
|
44
|
+
return { label: t('panels.stepMeta.state.failed'), color: '#ef4444' }
|
|
45
|
+
return { label: t(STATE_LABEL_KEYS[s.state]), color: STATE_COLOR[s.state] }
|
|
38
46
|
})
|
|
39
47
|
|
|
40
48
|
const modelLabel = computed(() => (props.step.model ? models.labelForRef(props.step.model) : null))
|
|
@@ -47,8 +55,18 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
47
55
|
|
|
48
56
|
const pctOf = (n: number) => `${Math.round(n * 100)}%`
|
|
49
57
|
|
|
58
|
+
const APPROVAL_STATUS_KEYS: Record<StepApproval['status'], string> = {
|
|
59
|
+
pending: 'panels.stepMeta.approvalStatus.pending',
|
|
60
|
+
approved: 'panels.stepMeta.approvalStatus.approved',
|
|
61
|
+
changes_requested: 'panels.stepMeta.approvalStatus.changes_requested',
|
|
62
|
+
rejected: 'panels.stepMeta.approvalStatus.rejected',
|
|
63
|
+
}
|
|
64
|
+
const approvalStatusLabel = computed(() =>
|
|
65
|
+
props.step.approval ? t(APPROVAL_STATUS_KEYS[props.step.approval.status]) : '',
|
|
66
|
+
)
|
|
67
|
+
|
|
50
68
|
function formatClock(ms?: number | null): string | null {
|
|
51
|
-
return ms ? new Date(ms)
|
|
69
|
+
return ms ? d(new Date(ms), 'long') : null
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
async function copyRunId() {
|
|
@@ -61,7 +79,9 @@ async function copyRunId() {
|
|
|
61
79
|
<div>
|
|
62
80
|
<dl class="grid grid-cols-2 gap-x-6 gap-y-3 text-[13px] sm:grid-cols-3">
|
|
63
81
|
<div>
|
|
64
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
82
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
83
|
+
{{ t('panels.stepMeta.stateLabel') }}
|
|
84
|
+
</dt>
|
|
65
85
|
<dd class="mt-0.5 flex items-center gap-1.5 text-slate-200">
|
|
66
86
|
<UIcon
|
|
67
87
|
v-if="runFailed && step.state === 'working'"
|
|
@@ -74,7 +94,9 @@ async function copyRunId() {
|
|
|
74
94
|
</dd>
|
|
75
95
|
</div>
|
|
76
96
|
<div>
|
|
77
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
97
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
98
|
+
{{ t('panels.stepMeta.duration') }}
|
|
99
|
+
</dt>
|
|
78
100
|
<dd class="mt-0.5 flex items-center gap-1.5 tabular-nums text-slate-200">
|
|
79
101
|
<UIcon
|
|
80
102
|
v-if="isRunning"
|
|
@@ -83,33 +105,47 @@ async function copyRunId() {
|
|
|
83
105
|
/>
|
|
84
106
|
<span v-if="durationLabel">{{ durationLabel }}</span>
|
|
85
107
|
<span v-else class="text-slate-500">—</span>
|
|
86
|
-
<span v-if="isRunning" class="text-[11px] text-slate-500">
|
|
108
|
+
<span v-if="isRunning" class="text-[11px] text-slate-500">{{
|
|
109
|
+
t('panels.stepMeta.elapsed')
|
|
110
|
+
}}</span>
|
|
87
111
|
</dd>
|
|
88
112
|
</div>
|
|
89
113
|
<div>
|
|
90
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
91
|
-
|
|
114
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
115
|
+
{{ t('panels.stepMeta.step') }}
|
|
116
|
+
</dt>
|
|
117
|
+
<dd class="mt-0.5 text-slate-200">
|
|
118
|
+
{{ t('panels.stepMeta.stepOf', { number: stepNumber, total: totalSteps }) }}
|
|
119
|
+
</dd>
|
|
92
120
|
</div>
|
|
93
121
|
<div>
|
|
94
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
122
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
123
|
+
{{ t('panels.stepMeta.started') }}
|
|
124
|
+
</dt>
|
|
95
125
|
<dd class="mt-0.5 text-slate-300">{{ formatClock(step.startedAt) ?? '—' }}</dd>
|
|
96
126
|
</div>
|
|
97
127
|
<div>
|
|
98
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
128
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
129
|
+
{{ t('panels.stepMeta.finished') }}
|
|
130
|
+
</dt>
|
|
99
131
|
<dd class="mt-0.5 text-slate-300">{{ formatClock(step.finishedAt) ?? '—' }}</dd>
|
|
100
132
|
</div>
|
|
101
133
|
<div>
|
|
102
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
134
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
135
|
+
{{ t('panels.stepMeta.model') }}
|
|
136
|
+
</dt>
|
|
103
137
|
<dd class="mt-0.5 truncate text-slate-300" :title="step.model">
|
|
104
|
-
{{ modelLabel ?? '
|
|
138
|
+
{{ modelLabel ?? t('panels.stepMeta.notRecorded') }}
|
|
105
139
|
</dd>
|
|
106
140
|
</div>
|
|
107
141
|
<!-- The run id this step belongs to, surfaced for debugging (copyable). -->
|
|
108
142
|
<div class="col-span-2 sm:col-span-3">
|
|
109
|
-
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
143
|
+
<dt class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
144
|
+
{{ t('panels.stepMeta.run') }}
|
|
145
|
+
</dt>
|
|
110
146
|
<dd
|
|
111
147
|
class="mt-0.5 cursor-pointer truncate font-mono text-[12px] text-slate-400 hover:text-slate-200"
|
|
112
|
-
:title="
|
|
148
|
+
:title="t('panels.stepMeta.clickToCopy', { id: step.runId ?? instanceId ?? '' })"
|
|
113
149
|
@click="copyRunId"
|
|
114
150
|
>
|
|
115
151
|
{{ step.runId ?? instanceId ?? '—' }}
|
|
@@ -124,13 +160,18 @@ async function copyRunId() {
|
|
|
124
160
|
class="mt-4 flex items-center gap-2 rounded-lg border border-sky-900/50 bg-sky-950/30 px-3 py-2 text-[12px] text-sky-300"
|
|
125
161
|
>
|
|
126
162
|
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 shrink-0 animate-spin" />
|
|
127
|
-
<span>
|
|
163
|
+
<span>{{ t('panels.stepMeta.spinningUpContainer') }}</span>
|
|
128
164
|
</div>
|
|
129
165
|
|
|
130
166
|
<!-- live subtask breakdown -->
|
|
131
167
|
<div v-if="step.subtasks && step.subtasks.total > 0" class="mt-4">
|
|
132
168
|
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
133
|
-
|
|
169
|
+
{{
|
|
170
|
+
t('panels.stepMeta.subtasks', {
|
|
171
|
+
completed: step.subtasks.completed,
|
|
172
|
+
total: step.subtasks.total,
|
|
173
|
+
})
|
|
174
|
+
}}
|
|
134
175
|
</div>
|
|
135
176
|
<div class="mt-1 h-1 overflow-hidden rounded-full bg-slate-700/60">
|
|
136
177
|
<div
|
|
@@ -170,7 +211,9 @@ async function copyRunId() {
|
|
|
170
211
|
|
|
171
212
|
<!-- standards (prompt fragments) folded into this step -->
|
|
172
213
|
<div v-if="step.selectedFragmentIds && step.selectedFragmentIds.length" class="mt-4">
|
|
173
|
-
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
214
|
+
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
215
|
+
{{ t('panels.stepMeta.standardsApplied') }}
|
|
216
|
+
</div>
|
|
174
217
|
<div class="mt-1 flex flex-wrap gap-1">
|
|
175
218
|
<UBadge
|
|
176
219
|
v-for="id in step.selectedFragmentIds"
|
|
@@ -186,7 +229,9 @@ async function copyRunId() {
|
|
|
186
229
|
|
|
187
230
|
<!-- decision raised on this step -->
|
|
188
231
|
<div v-if="step.decision" class="mt-4">
|
|
189
|
-
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
232
|
+
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
233
|
+
{{ t('panels.stepMeta.decision') }}
|
|
234
|
+
</div>
|
|
190
235
|
<p class="mt-0.5 text-[13px] text-slate-200">{{ step.decision.question }}</p>
|
|
191
236
|
<p
|
|
192
237
|
v-if="step.decision.chosen"
|
|
@@ -195,21 +240,27 @@ async function copyRunId() {
|
|
|
195
240
|
<UIcon name="i-lucide-check" class="h-3 w-3 shrink-0" />
|
|
196
241
|
{{ step.decision.chosen }}
|
|
197
242
|
</p>
|
|
198
|
-
<p v-else class="mt-0.5 text-[12px] text-amber-400">
|
|
243
|
+
<p v-else class="mt-0.5 text-[12px] text-amber-400">
|
|
244
|
+
{{ t('panels.stepMeta.awaitingChoice') }}
|
|
245
|
+
</p>
|
|
199
246
|
</div>
|
|
200
247
|
|
|
201
248
|
<!-- approval gate state -->
|
|
202
249
|
<div v-if="step.approval" class="mt-4">
|
|
203
|
-
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
204
|
-
|
|
205
|
-
|
|
250
|
+
<div class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
251
|
+
{{ t('panels.stepMeta.approvalGate') }}
|
|
252
|
+
</div>
|
|
253
|
+
<p class="mt-0.5 text-[13px] text-slate-200">
|
|
254
|
+
{{ approvalStatusLabel }}
|
|
206
255
|
</p>
|
|
207
256
|
</div>
|
|
208
257
|
|
|
209
258
|
<!-- companion verdict + full correction sequence -->
|
|
210
259
|
<div v-if="companionVerdicts.length" class="mt-4">
|
|
211
260
|
<div class="flex items-center justify-between">
|
|
212
|
-
<span class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
261
|
+
<span class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
262
|
+
{{ t('panels.stepMeta.companionReview') }}
|
|
263
|
+
</span>
|
|
213
264
|
<UBadge :color="latestVerdict?.passed ? 'success' : 'warning'" variant="subtle" size="sm">
|
|
214
265
|
{{ pctOf(latestVerdict!.rating) }}
|
|
215
266
|
{{ latestVerdict?.passed ? '≥' : '<' }} {{ pctOf(latestVerdict!.threshold) }}
|
|
@@ -234,7 +285,13 @@ async function copyRunId() {
|
|
|
234
285
|
</li>
|
|
235
286
|
</ol>
|
|
236
287
|
<p v-if="companionVerdicts.length > 1" class="mt-1 text-[11px] text-slate-500">
|
|
237
|
-
{{
|
|
288
|
+
{{
|
|
289
|
+
t(
|
|
290
|
+
'panels.stepMeta.correctionIterations',
|
|
291
|
+
{ count: companionVerdicts.length },
|
|
292
|
+
companionVerdicts.length,
|
|
293
|
+
)
|
|
294
|
+
}}
|
|
238
295
|
</p>
|
|
239
296
|
</div>
|
|
240
297
|
</div>
|
|
@@ -25,6 +25,7 @@ const props = defineProps<{
|
|
|
25
25
|
const emit = defineEmits<{ restarted: [] }>()
|
|
26
26
|
|
|
27
27
|
const execution = useExecutionStore()
|
|
28
|
+
const { t } = useI18n()
|
|
28
29
|
|
|
29
30
|
const instance = computed(() =>
|
|
30
31
|
props.instanceId ? execution.getInstance(props.instanceId) : undefined,
|
|
@@ -62,7 +63,7 @@ async function restart() {
|
|
|
62
63
|
color="neutral"
|
|
63
64
|
variant="ghost"
|
|
64
65
|
size="sm"
|
|
65
|
-
title="
|
|
66
|
+
:title="t('panels.stepRestart.restartFromStep')"
|
|
66
67
|
@click="armed = true"
|
|
67
68
|
/>
|
|
68
69
|
<template v-else>
|
|
@@ -74,7 +75,7 @@ async function restart() {
|
|
|
74
75
|
:loading="restarting"
|
|
75
76
|
@click="restart"
|
|
76
77
|
>
|
|
77
|
-
|
|
78
|
+
{{ t('panels.stepRestart.restartFromHere') }}
|
|
78
79
|
</UButton>
|
|
79
80
|
<UButton
|
|
80
81
|
color="neutral"
|
|
@@ -83,7 +84,7 @@ async function restart() {
|
|
|
83
84
|
:disabled="restarting"
|
|
84
85
|
@click="armed = false"
|
|
85
86
|
>
|
|
86
|
-
|
|
87
|
+
{{ t('common.cancel') }}
|
|
87
88
|
</UButton>
|
|
88
89
|
</template>
|
|
89
90
|
</template>
|
|
@@ -25,6 +25,7 @@ const props = defineProps<{
|
|
|
25
25
|
}>()
|
|
26
26
|
|
|
27
27
|
const models = useModelsStore()
|
|
28
|
+
const { t, d } = useI18n()
|
|
28
29
|
|
|
29
30
|
const { isRunning, durationLabel } = useStepTimer({
|
|
30
31
|
step: () => props.step,
|
|
@@ -36,7 +37,7 @@ const modelLabel = computed(() => (props.step.model ? models.labelForRef(props.s
|
|
|
36
37
|
const runId = computed(() => props.step.runId ?? props.instanceId ?? null)
|
|
37
38
|
|
|
38
39
|
function formatClock(ms?: number | null): string | null {
|
|
39
|
-
return ms ? new Date(ms)
|
|
40
|
+
return ms ? d(new Date(ms), 'long') : null
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
async function copyRunId() {
|
|
@@ -47,13 +48,17 @@ async function copyRunId() {
|
|
|
47
48
|
<template>
|
|
48
49
|
<div class="flex flex-col gap-4">
|
|
49
50
|
<div v-if="stepNumber && totalSteps">
|
|
50
|
-
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
51
|
-
|
|
51
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
52
|
+
{{ t('panels.stepMeta.step') }}
|
|
53
|
+
</h4>
|
|
54
|
+
<p class="text-[12px] text-slate-300">
|
|
55
|
+
{{ t('panels.stepMeta.stepOf', { number: stepNumber, total: totalSteps }) }}
|
|
56
|
+
</p>
|
|
52
57
|
</div>
|
|
53
58
|
|
|
54
59
|
<div v-if="durationLabel">
|
|
55
60
|
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
56
|
-
|
|
61
|
+
{{ t('panels.stepMeta.duration') }}
|
|
57
62
|
</h4>
|
|
58
63
|
<p class="flex items-center gap-1.5 text-[12px] tabular-nums text-slate-300">
|
|
59
64
|
<UIcon
|
|
@@ -62,34 +67,42 @@ async function copyRunId() {
|
|
|
62
67
|
class="h-3 w-3 animate-spin text-indigo-400"
|
|
63
68
|
/>
|
|
64
69
|
{{ durationLabel }}
|
|
65
|
-
<span v-if="isRunning" class="text-[11px] text-slate-500">
|
|
70
|
+
<span v-if="isRunning" class="text-[11px] text-slate-500">{{
|
|
71
|
+
t('panels.stepMeta.elapsed')
|
|
72
|
+
}}</span>
|
|
66
73
|
</p>
|
|
67
74
|
</div>
|
|
68
75
|
|
|
69
76
|
<div v-if="formatClock(step.startedAt)">
|
|
70
|
-
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
77
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
78
|
+
{{ t('panels.stepMeta.started') }}
|
|
79
|
+
</h4>
|
|
71
80
|
<p class="text-[12px] text-slate-300">{{ formatClock(step.startedAt) }}</p>
|
|
72
81
|
</div>
|
|
73
82
|
|
|
74
83
|
<div v-if="formatClock(step.finishedAt)">
|
|
75
84
|
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
76
|
-
|
|
85
|
+
{{ t('panels.stepMeta.finished') }}
|
|
77
86
|
</h4>
|
|
78
87
|
<p class="text-[12px] text-slate-300">{{ formatClock(step.finishedAt) }}</p>
|
|
79
88
|
</div>
|
|
80
89
|
|
|
81
90
|
<div v-if="step.model">
|
|
82
|
-
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
91
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
92
|
+
{{ t('panels.stepMeta.model') }}
|
|
93
|
+
</h4>
|
|
83
94
|
<p class="break-all text-[12px] text-slate-300" :title="step.model">
|
|
84
95
|
{{ modelLabel ?? step.model }}
|
|
85
96
|
</p>
|
|
86
97
|
</div>
|
|
87
98
|
|
|
88
99
|
<div v-if="runId">
|
|
89
|
-
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
100
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
101
|
+
{{ t('panels.stepMeta.run') }}
|
|
102
|
+
</h4>
|
|
90
103
|
<p
|
|
91
104
|
class="cursor-pointer break-all font-mono text-[12px] text-slate-400 hover:text-slate-200"
|
|
92
|
-
:title="
|
|
105
|
+
:title="t('panels.stepMeta.clickToCopy', { id: runId })"
|
|
93
106
|
@click="copyRunId"
|
|
94
107
|
>
|
|
95
108
|
{{ runId }}
|
|
@@ -9,6 +9,8 @@ defineProps<{
|
|
|
9
9
|
phase: TesterStepState | null
|
|
10
10
|
}>()
|
|
11
11
|
|
|
12
|
+
const { t } = useI18n()
|
|
13
|
+
|
|
12
14
|
const SEVERITY_COLOR: Record<string, string> = {
|
|
13
15
|
low: '#64748b',
|
|
14
16
|
medium: '#f59e0b',
|
|
@@ -26,16 +28,17 @@ const OUTCOME_COLOR: Record<string, string> = {
|
|
|
26
28
|
<section class="mt-4 scroll-mt-4">
|
|
27
29
|
<div class="mb-2 flex items-center gap-1.5 text-[11px]">
|
|
28
30
|
<UIcon name="i-lucide-flask-conical" class="h-3.5 w-3.5 text-slate-400" />
|
|
29
|
-
<span class="font-semibold uppercase tracking-wide text-slate-400">
|
|
31
|
+
<span class="font-semibold uppercase tracking-wide text-slate-400">
|
|
32
|
+
{{ t('panels.testReport.title') }}
|
|
33
|
+
</span>
|
|
30
34
|
<UBadge :color="report.greenlight ? 'success' : 'warning'" variant="subtle" size="sm">
|
|
31
|
-
{{
|
|
35
|
+
{{
|
|
36
|
+
report.greenlight ? t('panels.testReport.greenlit') : t('panels.testReport.needsFixes')
|
|
37
|
+
}}
|
|
32
38
|
</UBadge>
|
|
33
39
|
<span v-if="phase && phase.attempts > 0" class="text-[11px] text-slate-500">
|
|
34
|
-
{{ phase.attempts
|
|
35
|
-
|
|
36
|
-
>
|
|
37
|
-
· fixing…</span
|
|
38
|
-
>
|
|
40
|
+
{{ t('panels.testReport.fixAttempts', { count: phase.attempts, max: phase.maxAttempts })
|
|
41
|
+
}}<span v-if="phase.phase === 'fixing'"> {{ t('panels.testReport.fixing') }}</span>
|
|
39
42
|
</span>
|
|
40
43
|
</div>
|
|
41
44
|
<p v-if="report.summary" class="mb-3 text-[13px] leading-relaxed text-slate-300">
|
|
@@ -43,14 +46,14 @@ const OUTCOME_COLOR: Record<string, string> = {
|
|
|
43
46
|
</p>
|
|
44
47
|
|
|
45
48
|
<div v-if="report.tested.length" class="mb-3">
|
|
46
|
-
<div class="mb-1 text-[11px] text-slate-500">
|
|
49
|
+
<div class="mb-1 text-[11px] text-slate-500">{{ t('panels.testReport.tested') }}</div>
|
|
47
50
|
<ul class="space-y-0.5 text-[12px] text-slate-300">
|
|
48
|
-
<li v-for="(
|
|
51
|
+
<li v-for="(item, i) in report.tested" :key="i">• {{ item }}</li>
|
|
49
52
|
</ul>
|
|
50
53
|
</div>
|
|
51
54
|
|
|
52
55
|
<div v-if="report.outcomes.length" class="mb-3 space-y-1">
|
|
53
|
-
<div class="text-[11px] text-slate-500">
|
|
56
|
+
<div class="text-[11px] text-slate-500">{{ t('panels.testReport.outcomes') }}</div>
|
|
54
57
|
<div v-for="(o, i) in report.outcomes" :key="i" class="flex items-start gap-2 text-[12px]">
|
|
55
58
|
<span
|
|
56
59
|
class="mt-1 h-2 w-2 shrink-0 rounded-full"
|
|
@@ -63,7 +66,7 @@ const OUTCOME_COLOR: Record<string, string> = {
|
|
|
63
66
|
</div>
|
|
64
67
|
|
|
65
68
|
<div v-if="report.concerns.length" class="space-y-1">
|
|
66
|
-
<div class="text-[11px] text-slate-500">
|
|
69
|
+
<div class="text-[11px] text-slate-500">{{ t('panels.testReport.concerns') }}</div>
|
|
67
70
|
<div
|
|
68
71
|
v-for="(c, i) in report.concerns"
|
|
69
72
|
:key="i"
|
|
@@ -6,6 +6,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
6
6
|
|
|
7
7
|
const board = useBoardStore()
|
|
8
8
|
const ui = useUiStore()
|
|
9
|
+
const { t } = useI18n()
|
|
9
10
|
|
|
10
11
|
const isFrame = computed(() => (props.block.level ?? 'frame') === 'frame')
|
|
11
12
|
const modules = computed(() => (isFrame.value ? board.modulesOf(props.block.id) : []))
|
|
@@ -23,7 +24,7 @@ function addTask() {
|
|
|
23
24
|
<!-- modules (services only) -->
|
|
24
25
|
<div v-if="modules.length">
|
|
25
26
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
26
|
-
|
|
27
|
+
{{ t('inspector.container.modules', { count: modules.length }) }}
|
|
27
28
|
</div>
|
|
28
29
|
<ul class="space-y-1">
|
|
29
30
|
<li
|
|
@@ -34,9 +35,13 @@ function addTask() {
|
|
|
34
35
|
>
|
|
35
36
|
<UIcon name="i-lucide-package" class="h-3.5 w-3.5 text-violet-400" />
|
|
36
37
|
<span class="truncate text-xs text-slate-200">{{ m.title }}</span>
|
|
37
|
-
<span class="ml-auto text-[10px] text-slate-500"
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
<span class="ml-auto text-[10px] text-slate-500">{{
|
|
39
|
+
t(
|
|
40
|
+
'inspector.container.taskCount',
|
|
41
|
+
{ count: board.tasksOf(m.id).length },
|
|
42
|
+
board.tasksOf(m.id).length,
|
|
43
|
+
)
|
|
44
|
+
}}</span>
|
|
40
45
|
</li>
|
|
41
46
|
</ul>
|
|
42
47
|
</div>
|
|
@@ -44,31 +49,37 @@ function addTask() {
|
|
|
44
49
|
<div>
|
|
45
50
|
<div class="mb-1 flex items-center justify-between">
|
|
46
51
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
47
|
-
{{
|
|
52
|
+
{{
|
|
53
|
+
isFrame
|
|
54
|
+
? t('inspector.container.allTasks', { count: tasks.length })
|
|
55
|
+
: t('inspector.container.tasks', { count: tasks.length })
|
|
56
|
+
}}
|
|
48
57
|
</span>
|
|
49
58
|
<UButton size="xs" variant="soft" color="primary" icon="i-lucide-plus" @click="addTask">
|
|
50
|
-
|
|
59
|
+
{{ t('inspector.container.addTask') }}
|
|
51
60
|
</UButton>
|
|
52
61
|
</div>
|
|
53
62
|
<ul v-if="tasks.length" class="space-y-1">
|
|
54
63
|
<li
|
|
55
|
-
v-for="
|
|
56
|
-
:key="
|
|
64
|
+
v-for="task in tasks"
|
|
65
|
+
:key="task.id"
|
|
57
66
|
class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1 hover:bg-slate-800/60"
|
|
58
|
-
@click="ui.select(
|
|
67
|
+
@click="ui.select(task.id)"
|
|
59
68
|
>
|
|
60
69
|
<span
|
|
61
70
|
class="h-2 w-2 shrink-0 rounded-full"
|
|
62
|
-
:style="{ backgroundColor: STATUS_META[
|
|
71
|
+
:style="{ backgroundColor: STATUS_META[task.status].color }"
|
|
63
72
|
/>
|
|
64
|
-
<span class="truncate text-xs text-slate-200">{{
|
|
65
|
-
<span class="ml-auto text-[10px] text-slate-500">{{
|
|
73
|
+
<span class="truncate text-xs text-slate-200">{{ task.title }}</span>
|
|
74
|
+
<span class="ml-auto text-[10px] text-slate-500">{{
|
|
75
|
+
STATUS_META[task.status].label
|
|
76
|
+
}}</span>
|
|
66
77
|
</li>
|
|
67
78
|
</ul>
|
|
68
|
-
<div v-else class="text-[11px] text-slate-500">
|
|
79
|
+
<div v-else class="text-[11px] text-slate-500">{{ t('inspector.container.noTasks') }}</div>
|
|
69
80
|
</div>
|
|
70
81
|
<p v-if="isFrame" class="text-[11px] text-slate-500">
|
|
71
|
-
|
|
82
|
+
{{ t('inspector.container.servicesHint') }}
|
|
72
83
|
</p>
|
|
73
84
|
</div>
|
|
74
85
|
</template>
|
|
@@ -10,6 +10,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
10
10
|
|
|
11
11
|
const board = useBoardStore()
|
|
12
12
|
const ui = useUiStore()
|
|
13
|
+
const { t } = useI18n()
|
|
13
14
|
|
|
14
15
|
const members = computed(() => board.epicMembers(props.block.id))
|
|
15
16
|
const done = computed(() => members.value.filter((m) => m.status === 'done').length)
|
|
@@ -47,13 +48,15 @@ const groups = computed(() => {
|
|
|
47
48
|
<div>
|
|
48
49
|
<div class="mb-1 flex items-center justify-between">
|
|
49
50
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
50
|
-
|
|
51
|
+
{{ t('inspector.epicChildren.title') }}
|
|
51
52
|
</span>
|
|
52
|
-
<span class="text-[11px] text-slate-500">{{
|
|
53
|
+
<span class="text-[11px] text-slate-500">{{
|
|
54
|
+
t('inspector.epicChildren.doneCount', { done, total: members.length })
|
|
55
|
+
}}</span>
|
|
53
56
|
</div>
|
|
54
57
|
|
|
55
58
|
<div v-if="members.length === 0" class="text-[11px] text-slate-500">
|
|
56
|
-
|
|
59
|
+
{{ t('inspector.epicChildren.empty') }}
|
|
57
60
|
</div>
|
|
58
61
|
|
|
59
62
|
<div v-else class="space-y-2">
|
|
@@ -64,7 +67,7 @@ const groups = computed(() => {
|
|
|
64
67
|
>
|
|
65
68
|
<div class="mb-1 flex items-center gap-1 text-[11px] font-medium text-slate-300">
|
|
66
69
|
<UIcon name="i-lucide-box" class="h-3 w-3 text-slate-500" />
|
|
67
|
-
{{ group.service?.title ?? '
|
|
70
|
+
{{ group.service?.title ?? t('inspector.epicChildren.unassigned') }}
|
|
68
71
|
</div>
|
|
69
72
|
<div v-for="(mod, mi) in [...group.modules.values()]" :key="mi" class="pl-1">
|
|
70
73
|
<div v-if="mod.module" class="text-[10px] uppercase tracking-wide text-slate-500">
|