@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
|
@@ -9,6 +9,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
9
9
|
const recurring = useRecurringPipelinesStore()
|
|
10
10
|
const pipelines = usePipelinesStore()
|
|
11
11
|
const toast = useToast()
|
|
12
|
+
const { t, d } = useI18n()
|
|
12
13
|
|
|
13
14
|
const schedule = computed(() => recurring.byBlock(props.block.id))
|
|
14
15
|
const runs = computed(() =>
|
|
@@ -32,17 +33,30 @@ const pipelineName = computed(
|
|
|
32
33
|
() => pipelines.getPipeline(schedule.value?.pipelineId ?? '')?.name ?? schedule.value?.pipelineId,
|
|
33
34
|
)
|
|
34
35
|
|
|
36
|
+
const WEEKDAY_KEYS = [
|
|
37
|
+
'inspector.recurring.weekdays.sun',
|
|
38
|
+
'inspector.recurring.weekdays.mon',
|
|
39
|
+
'inspector.recurring.weekdays.tue',
|
|
40
|
+
'inspector.recurring.weekdays.wed',
|
|
41
|
+
'inspector.recurring.weekdays.thu',
|
|
42
|
+
'inspector.recurring.weekdays.fri',
|
|
43
|
+
'inspector.recurring.weekdays.sat',
|
|
44
|
+
] as const
|
|
45
|
+
|
|
35
46
|
function describeCadence(r: Recurrence): string {
|
|
36
|
-
const every =
|
|
47
|
+
const every =
|
|
48
|
+
r.intervalHours % 24 === 0
|
|
49
|
+
? t('inspector.recurring.intervalDays', { count: r.intervalHours / 24 })
|
|
50
|
+
: t('inspector.recurring.intervalHours', { count: r.intervalHours })
|
|
37
51
|
const days =
|
|
38
52
|
r.weekdays.length === 0
|
|
39
|
-
? '
|
|
40
|
-
: r.weekdays.map((
|
|
53
|
+
? t('inspector.recurring.anyDay')
|
|
54
|
+
: r.weekdays.map((wd) => t(WEEKDAY_KEYS[wd]!)).join(' ')
|
|
41
55
|
const window =
|
|
42
56
|
r.windowStartHour === null && r.windowEndHour === null
|
|
43
57
|
? ''
|
|
44
58
|
: ` · ${String(r.windowStartHour ?? 0).padStart(2, '0')}:00–${String(r.windowEndHour ?? 24).padStart(2, '0')}:00`
|
|
45
|
-
return
|
|
59
|
+
return t('inspector.recurring.cadence', { every, days, window, timezone: r.timezone })
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
function startEdit() {
|
|
@@ -58,7 +72,11 @@ async function saveEdit() {
|
|
|
58
72
|
await recurring.update(schedule.value.id, { recurrence: draft.value })
|
|
59
73
|
editing.value = false
|
|
60
74
|
} catch (e) {
|
|
61
|
-
toast.add({
|
|
75
|
+
toast.add({
|
|
76
|
+
title: t('inspector.recurring.updateFailed'),
|
|
77
|
+
description: errMsg(e),
|
|
78
|
+
color: 'error',
|
|
79
|
+
})
|
|
62
80
|
} finally {
|
|
63
81
|
busy.value = false
|
|
64
82
|
}
|
|
@@ -70,7 +88,11 @@ async function toggleEnabled() {
|
|
|
70
88
|
try {
|
|
71
89
|
await recurring.update(schedule.value.id, { enabled: !schedule.value.enabled })
|
|
72
90
|
} catch (e) {
|
|
73
|
-
toast.add({
|
|
91
|
+
toast.add({
|
|
92
|
+
title: t('inspector.recurring.updateFailed'),
|
|
93
|
+
description: errMsg(e),
|
|
94
|
+
color: 'error',
|
|
95
|
+
})
|
|
74
96
|
} finally {
|
|
75
97
|
busy.value = false
|
|
76
98
|
}
|
|
@@ -82,7 +104,11 @@ async function runNow() {
|
|
|
82
104
|
try {
|
|
83
105
|
await recurring.runNow(schedule.value.id)
|
|
84
106
|
} catch (e) {
|
|
85
|
-
toast.add({
|
|
107
|
+
toast.add({
|
|
108
|
+
title: t('inspector.recurring.runNowFailed'),
|
|
109
|
+
description: errMsg(e),
|
|
110
|
+
color: 'error',
|
|
111
|
+
})
|
|
86
112
|
} finally {
|
|
87
113
|
busy.value = false
|
|
88
114
|
}
|
|
@@ -98,8 +124,17 @@ const RUN_COLOR: Record<string, string> = {
|
|
|
98
124
|
failed: 'text-rose-400',
|
|
99
125
|
skipped: 'text-slate-500',
|
|
100
126
|
}
|
|
127
|
+
const RUN_STATUS_KEYS: Record<string, string> = {
|
|
128
|
+
running: 'inspector.recurring.runStatus.running',
|
|
129
|
+
done: 'inspector.recurring.runStatus.done',
|
|
130
|
+
failed: 'inspector.recurring.runStatus.failed',
|
|
131
|
+
skipped: 'inspector.recurring.runStatus.skipped',
|
|
132
|
+
}
|
|
133
|
+
function runStatusLabel(status: string): string {
|
|
134
|
+
return RUN_STATUS_KEYS[status] ? t(RUN_STATUS_KEYS[status]!) : status
|
|
135
|
+
}
|
|
101
136
|
function fmtTime(ms: number) {
|
|
102
|
-
return new Date(ms)
|
|
137
|
+
return d(new Date(ms), 'long')
|
|
103
138
|
}
|
|
104
139
|
</script>
|
|
105
140
|
|
|
@@ -113,10 +148,10 @@ function fmtTime(ms: number) {
|
|
|
113
148
|
class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-indigo-300"
|
|
114
149
|
>
|
|
115
150
|
<UIcon name="i-lucide-repeat" class="h-3.5 w-3.5" />
|
|
116
|
-
|
|
151
|
+
{{ t('inspector.recurring.title') }}
|
|
117
152
|
</span>
|
|
118
153
|
<UBadge :color="schedule.enabled ? 'primary' : 'neutral'" variant="subtle" size="xs">
|
|
119
|
-
{{ schedule.enabled ? '
|
|
154
|
+
{{ schedule.enabled ? t('inspector.recurring.active') : t('inspector.recurring.paused') }}
|
|
120
155
|
</UBadge>
|
|
121
156
|
</div>
|
|
122
157
|
|
|
@@ -126,10 +161,12 @@ function fmtTime(ms: number) {
|
|
|
126
161
|
|
|
127
162
|
<template v-if="!editing">
|
|
128
163
|
<p class="text-[11px] text-slate-400">{{ describeCadence(schedule.recurrence) }}</p>
|
|
129
|
-
<p class="text-[11px] text-slate-500">
|
|
164
|
+
<p class="text-[11px] text-slate-500">
|
|
165
|
+
{{ t('inspector.recurring.nextRun', { time: fmtTime(schedule.nextRunAt) }) }}
|
|
166
|
+
</p>
|
|
130
167
|
<div class="flex flex-wrap gap-1.5 pt-1">
|
|
131
168
|
<UButton size="xs" variant="soft" icon="i-lucide-play" :loading="busy" @click="runNow">
|
|
132
|
-
|
|
169
|
+
{{ t('inspector.recurring.runNow') }}
|
|
133
170
|
</UButton>
|
|
134
171
|
<UButton
|
|
135
172
|
size="xs"
|
|
@@ -139,7 +176,7 @@ function fmtTime(ms: number) {
|
|
|
139
176
|
:loading="busy"
|
|
140
177
|
@click="toggleEnabled"
|
|
141
178
|
>
|
|
142
|
-
{{ schedule.enabled ? '
|
|
179
|
+
{{ schedule.enabled ? t('inspector.recurring.pause') : t('inspector.recurring.resume') }}
|
|
143
180
|
</UButton>
|
|
144
181
|
<UButton
|
|
145
182
|
size="xs"
|
|
@@ -148,7 +185,7 @@ function fmtTime(ms: number) {
|
|
|
148
185
|
icon="i-lucide-pencil"
|
|
149
186
|
@click="startEdit"
|
|
150
187
|
>
|
|
151
|
-
|
|
188
|
+
{{ t('inspector.recurring.editCadence') }}
|
|
152
189
|
</UButton>
|
|
153
190
|
</div>
|
|
154
191
|
</template>
|
|
@@ -156,19 +193,23 @@ function fmtTime(ms: number) {
|
|
|
156
193
|
<template v-else-if="draft">
|
|
157
194
|
<RecurringRecurrenceEditor v-model="draft" />
|
|
158
195
|
<div class="flex justify-end gap-1.5 pt-1">
|
|
159
|
-
<UButton size="xs" variant="ghost" color="neutral" @click="editing = false">
|
|
160
|
-
|
|
196
|
+
<UButton size="xs" variant="ghost" color="neutral" @click="editing = false">{{
|
|
197
|
+
t('common.cancel')
|
|
198
|
+
}}</UButton>
|
|
199
|
+
<UButton size="xs" color="primary" :loading="busy" @click="saveEdit">{{
|
|
200
|
+
t('common.save')
|
|
201
|
+
}}</UButton>
|
|
161
202
|
</div>
|
|
162
203
|
</template>
|
|
163
204
|
|
|
164
205
|
<!-- run history -->
|
|
165
206
|
<div v-if="runs.length" class="space-y-1 border-t border-slate-800 pt-2">
|
|
166
207
|
<span class="text-[10px] font-semibold uppercase tracking-wide text-slate-500">
|
|
167
|
-
|
|
208
|
+
{{ t('inspector.recurring.recentRuns') }}
|
|
168
209
|
</span>
|
|
169
210
|
<div v-for="run in runs" :key="run.id" class="flex items-center gap-2 text-[11px]">
|
|
170
|
-
<span :class="RUN_COLOR[run.status] ?? 'text-slate-400'" class="w-14 shrink-0
|
|
171
|
-
{{ run.status }}
|
|
211
|
+
<span :class="RUN_COLOR[run.status] ?? 'text-slate-400'" class="w-14 shrink-0">
|
|
212
|
+
{{ runStatusLabel(run.status) }}
|
|
172
213
|
</span>
|
|
173
214
|
<span class="truncate text-slate-500">{{ fmtTime(run.startedAt) }}</span>
|
|
174
215
|
<span v-if="run.outcome" class="ml-auto truncate text-slate-500">{{ run.outcome }}</span>
|
|
@@ -9,6 +9,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
9
9
|
|
|
10
10
|
const board = useBoardStore()
|
|
11
11
|
const fragments = useFragmentsStore()
|
|
12
|
+
const { t } = useI18n()
|
|
12
13
|
|
|
13
14
|
onMounted(() => fragments.ensureLoaded())
|
|
14
15
|
|
|
@@ -49,7 +50,7 @@ function removeFragment(id: string) {
|
|
|
49
50
|
<div>
|
|
50
51
|
<div class="mb-1 flex items-center justify-between">
|
|
51
52
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
52
|
-
|
|
53
|
+
{{ t('inspector.fragments.serviceTitle') }}
|
|
53
54
|
</span>
|
|
54
55
|
<UDropdownMenu v-if="fragmentMenu.length" :items="fragmentMenu">
|
|
55
56
|
<UButton
|
|
@@ -76,7 +77,7 @@ function removeFragment(id: string) {
|
|
|
76
77
|
</UBadge>
|
|
77
78
|
</div>
|
|
78
79
|
<div v-else class="text-[11px] text-slate-500">
|
|
79
|
-
|
|
80
|
+
{{ t('inspector.fragments.serviceEmpty') }}
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
82
83
|
</template>
|
|
@@ -11,6 +11,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
11
11
|
const store = useReleaseHealthStore()
|
|
12
12
|
const ui = useUiStore()
|
|
13
13
|
const toast = useToast()
|
|
14
|
+
const { t } = useI18n()
|
|
14
15
|
|
|
15
16
|
const busy = ref(false)
|
|
16
17
|
const draft = reactive({ monitorIds: '', sloIds: '', envTag: '' })
|
|
@@ -56,9 +57,13 @@ async function save() {
|
|
|
56
57
|
sloIds: parseIds(draft.sloIds),
|
|
57
58
|
envTag: draft.envTag.trim() || null,
|
|
58
59
|
})
|
|
59
|
-
toast.add({
|
|
60
|
+
toast.add({
|
|
61
|
+
title: t('inspector.releaseHealth.savedToast'),
|
|
62
|
+
icon: 'i-lucide-check',
|
|
63
|
+
color: 'success',
|
|
64
|
+
})
|
|
60
65
|
} catch (e) {
|
|
61
|
-
notifyError('
|
|
66
|
+
notifyError(t('inspector.releaseHealth.saveFailed'), e)
|
|
62
67
|
} finally {
|
|
63
68
|
busy.value = false
|
|
64
69
|
}
|
|
@@ -72,7 +77,7 @@ async function clear() {
|
|
|
72
77
|
draft.sloIds = ''
|
|
73
78
|
draft.envTag = ''
|
|
74
79
|
} catch (e) {
|
|
75
|
-
notifyError('
|
|
80
|
+
notifyError(t('inspector.releaseHealth.clearFailed'), e)
|
|
76
81
|
} finally {
|
|
77
82
|
busy.value = false
|
|
78
83
|
}
|
|
@@ -83,7 +88,7 @@ async function clear() {
|
|
|
83
88
|
<div class="space-y-2">
|
|
84
89
|
<div class="flex items-center justify-between">
|
|
85
90
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
86
|
-
|
|
91
|
+
{{ t('inspector.releaseHealth.title') }}
|
|
87
92
|
</span>
|
|
88
93
|
<UButton
|
|
89
94
|
v-if="saved"
|
|
@@ -94,7 +99,7 @@ async function clear() {
|
|
|
94
99
|
:loading="busy"
|
|
95
100
|
@click="clear"
|
|
96
101
|
>
|
|
97
|
-
|
|
102
|
+
{{ t('inspector.releaseHealth.clear') }}
|
|
98
103
|
</UButton>
|
|
99
104
|
</div>
|
|
100
105
|
|
|
@@ -103,32 +108,32 @@ async function clear() {
|
|
|
103
108
|
v-if="!connected"
|
|
104
109
|
class="flex items-center justify-between gap-2 rounded-md border border-slate-800 bg-slate-900/60 px-2 py-1.5 text-[11px] text-slate-400"
|
|
105
110
|
>
|
|
106
|
-
<span>
|
|
111
|
+
<span>{{ t('inspector.releaseHealth.connectPrompt') }}</span>
|
|
107
112
|
<UButton
|
|
108
113
|
color="neutral"
|
|
109
114
|
variant="soft"
|
|
110
115
|
size="xs"
|
|
111
116
|
icon="i-lucide-plug"
|
|
112
|
-
title="
|
|
117
|
+
:title="t('inspector.releaseHealth.connectFirst')"
|
|
113
118
|
@click="ui.openObservabilityConnection()"
|
|
114
119
|
>
|
|
115
|
-
|
|
120
|
+
{{ t('inspector.releaseHealth.connect') }}
|
|
116
121
|
</UButton>
|
|
117
122
|
</div>
|
|
118
123
|
|
|
119
124
|
<div v-else class="space-y-2">
|
|
120
125
|
<p class="text-[11px] text-slate-500">
|
|
121
|
-
|
|
126
|
+
{{ t('inspector.releaseHealth.hint') }}
|
|
122
127
|
</p>
|
|
123
128
|
<div class="grid grid-cols-2 gap-2">
|
|
124
|
-
<UFormField label="
|
|
129
|
+
<UFormField :label="t('inspector.releaseHealth.monitorIds')">
|
|
125
130
|
<UInput v-model="draft.monitorIds" placeholder="123, 456" size="sm" class="w-full" />
|
|
126
131
|
</UFormField>
|
|
127
|
-
<UFormField label="
|
|
132
|
+
<UFormField :label="t('inspector.releaseHealth.sloIds')">
|
|
128
133
|
<UInput v-model="draft.sloIds" placeholder="abc, def" size="sm" class="w-full" />
|
|
129
134
|
</UFormField>
|
|
130
135
|
</div>
|
|
131
|
-
<UFormField label="
|
|
136
|
+
<UFormField :label="t('inspector.releaseHealth.envTag')">
|
|
132
137
|
<UInput v-model="draft.envTag" placeholder="prod" size="sm" class="w-full" />
|
|
133
138
|
</UFormField>
|
|
134
139
|
<div class="flex justify-end">
|
|
@@ -140,7 +145,7 @@ async function clear() {
|
|
|
140
145
|
:loading="busy"
|
|
141
146
|
@click="save"
|
|
142
147
|
>
|
|
143
|
-
|
|
148
|
+
{{ t('inspector.releaseHealth.saveMonitoring') }}
|
|
144
149
|
</UButton>
|
|
145
150
|
</div>
|
|
146
151
|
</div>
|
|
@@ -20,6 +20,7 @@ const board = useBoardStore()
|
|
|
20
20
|
const accounts = useAccountsStore()
|
|
21
21
|
const github = useGitHubStore()
|
|
22
22
|
const services = useServicesStore()
|
|
23
|
+
const { t } = useI18n()
|
|
23
24
|
|
|
24
25
|
const composePath = computed(() => props.block.testComposePath ?? '')
|
|
25
26
|
const noInfra = computed(() => props.block.noInfraDependencies === true)
|
|
@@ -29,14 +30,20 @@ const noInfra = computed(() => props.block.noInfraDependencies === true)
|
|
|
29
30
|
// against a provisioned environment. A task can override it per-task in its agent
|
|
30
31
|
// settings. Absent ⇒ the built-in `ephemeral`.
|
|
31
32
|
type TestEnvironment = 'local' | 'ephemeral'
|
|
32
|
-
const TEST_ENVIRONMENTS
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
const TEST_ENVIRONMENTS = computed<{ value: TestEnvironment; label: string; hint: string }[]>(
|
|
34
|
+
() => [
|
|
35
|
+
{
|
|
36
|
+
value: 'ephemeral',
|
|
37
|
+
label: t('inspector.testConfig.env.ephemeral'),
|
|
38
|
+
hint: t('inspector.testConfig.env.ephemeralHint'),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: 'local',
|
|
42
|
+
label: t('inspector.testConfig.env.local'),
|
|
43
|
+
hint: t('inspector.testConfig.env.localHint'),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
)
|
|
40
47
|
const effectiveTestEnv = computed<TestEnvironment>(
|
|
41
48
|
() => props.block.defaultTestEnvironment ?? 'ephemeral',
|
|
42
49
|
)
|
|
@@ -86,20 +93,20 @@ function toggleNoInfra(value: boolean) {
|
|
|
86
93
|
board.updateBlock(props.block.id, { noInfraDependencies: value })
|
|
87
94
|
}
|
|
88
95
|
|
|
89
|
-
const PROVIDERS
|
|
96
|
+
const PROVIDERS = computed<{ value: CloudProvider; label: string }[]>(() => [
|
|
90
97
|
{ value: 'cloudflare', label: 'Cloudflare' },
|
|
91
|
-
{ value: 'docker', label: '
|
|
98
|
+
{ value: 'docker', label: t('inspector.testConfig.providers.docker') },
|
|
92
99
|
{ value: 'aws', label: 'AWS' },
|
|
93
100
|
{ value: 'gcp', label: 'GCP' },
|
|
94
101
|
{ value: 'azure', label: 'Azure' },
|
|
95
|
-
{ value: 'custom', label: '
|
|
96
|
-
]
|
|
97
|
-
const SIZES
|
|
98
|
-
{ value: 'small', label: '
|
|
99
|
-
{ value: 'medium', label: '
|
|
100
|
-
{ value: 'large', label: '
|
|
101
|
-
{ value: 'xlarge', label: '
|
|
102
|
-
]
|
|
102
|
+
{ value: 'custom', label: t('inspector.testConfig.providers.custom') },
|
|
103
|
+
])
|
|
104
|
+
const SIZES = computed<{ value: InstanceSize; label: string }[]>(() => [
|
|
105
|
+
{ value: 'small', label: t('inspector.testConfig.sizes.small') },
|
|
106
|
+
{ value: 'medium', label: t('inspector.testConfig.sizes.medium') },
|
|
107
|
+
{ value: 'large', label: t('inspector.testConfig.sizes.large') },
|
|
108
|
+
{ value: 'xlarge', label: t('inspector.testConfig.sizes.xlarge') },
|
|
109
|
+
])
|
|
103
110
|
|
|
104
111
|
function setProvider(value: CloudProvider) {
|
|
105
112
|
board.updateBlock(props.block.id, { cloudProvider: value })
|
|
@@ -114,11 +121,11 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
114
121
|
<template>
|
|
115
122
|
<div class="space-y-3">
|
|
116
123
|
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
117
|
-
|
|
124
|
+
{{ t('inspector.testConfig.title') }}
|
|
118
125
|
</div>
|
|
119
126
|
|
|
120
127
|
<div class="space-y-1">
|
|
121
|
-
<span class="text-[11px] text-slate-400">
|
|
128
|
+
<span class="text-[11px] text-slate-400">{{ t('inspector.testConfig.defaultEnv') }}</span>
|
|
122
129
|
<div class="flex flex-wrap gap-1">
|
|
123
130
|
<UButton
|
|
124
131
|
v-for="e in TEST_ENVIRONMENTS"
|
|
@@ -133,13 +140,12 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
133
140
|
</UButton>
|
|
134
141
|
</div>
|
|
135
142
|
<p class="text-[11px] leading-snug text-slate-500">
|
|
136
|
-
|
|
137
|
-
agent settings.
|
|
143
|
+
{{ t('inspector.testConfig.defaultEnvHint') }}
|
|
138
144
|
</p>
|
|
139
145
|
</div>
|
|
140
146
|
|
|
141
147
|
<div class="space-y-1">
|
|
142
|
-
<label class="text-[11px] text-slate-400">
|
|
148
|
+
<label class="text-[11px] text-slate-400">{{ t('inspector.testConfig.composePath') }}</label>
|
|
143
149
|
<div class="flex items-center gap-1">
|
|
144
150
|
<UInput
|
|
145
151
|
:model-value="composePath"
|
|
@@ -159,20 +165,20 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
159
165
|
color="neutral"
|
|
160
166
|
icon="i-lucide-folder-search"
|
|
161
167
|
:disabled="noInfra"
|
|
162
|
-
title="
|
|
168
|
+
:title="t('inspector.testConfig.browseRepo')"
|
|
163
169
|
@click="openBrowse"
|
|
164
170
|
/>
|
|
165
171
|
</div>
|
|
166
172
|
<p class="text-[11px] leading-snug text-slate-500">
|
|
167
|
-
|
|
173
|
+
{{ t('inspector.testConfig.composeHint') }}
|
|
168
174
|
</p>
|
|
169
175
|
</div>
|
|
170
176
|
|
|
171
|
-
<UModal v-model:open="browseOpen" title="
|
|
177
|
+
<UModal v-model:open="browseOpen" :title="t('inspector.testConfig.selectComposeTitle')">
|
|
172
178
|
<template #body>
|
|
173
179
|
<div v-if="repoContext" class="space-y-3">
|
|
174
180
|
<p class="text-xs text-slate-400">
|
|
175
|
-
|
|
181
|
+
{{ t('inspector.testConfig.selectComposeHint') }}
|
|
176
182
|
</p>
|
|
177
183
|
<RepoTreeBrowser
|
|
178
184
|
v-model="pickedPath"
|
|
@@ -183,12 +189,16 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
183
189
|
<div class="flex items-center justify-between gap-2">
|
|
184
190
|
<p class="truncate text-xs text-slate-400">
|
|
185
191
|
<template v-if="pickedPath">
|
|
186
|
-
|
|
192
|
+
<i18n-t keypath="inspector.testConfig.selected" tag="span" scope="global">
|
|
193
|
+
<template #path>
|
|
194
|
+
<code class="text-slate-200">{{ pickedPath }}</code>
|
|
195
|
+
</template>
|
|
196
|
+
</i18n-t>
|
|
187
197
|
</template>
|
|
188
|
-
<template v-else>
|
|
198
|
+
<template v-else>{{ t('inspector.testConfig.noFileSelected') }}</template>
|
|
189
199
|
</p>
|
|
190
200
|
<UButton size="xs" color="primary" :disabled="!pickedPath" @click="applyPicked">
|
|
191
|
-
|
|
201
|
+
{{ t('inspector.testConfig.useThisFile') }}
|
|
192
202
|
</UButton>
|
|
193
203
|
</div>
|
|
194
204
|
</div>
|
|
@@ -200,12 +210,11 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
200
210
|
:model-value="noInfra"
|
|
201
211
|
@update:model-value="(v: boolean | 'indeterminate') => toggleNoInfra(v === true)"
|
|
202
212
|
/>
|
|
203
|
-
|
|
213
|
+
{{ t('inspector.testConfig.noInfra') }}
|
|
204
214
|
</label>
|
|
205
215
|
|
|
206
216
|
<p v-if="missingInfra" class="text-[11px] leading-snug text-amber-500">
|
|
207
|
-
|
|
208
|
-
Tester won't start.
|
|
217
|
+
{{ t('inspector.testConfig.missingInfra') }}
|
|
209
218
|
</p>
|
|
210
219
|
|
|
211
220
|
<!-- Provisioning hints: advisory inputs to the ephemeral-environment provisioner.
|
|
@@ -220,18 +229,18 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
220
229
|
:name="showProvisioning ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
|
|
221
230
|
class="h-3.5 w-3.5"
|
|
222
231
|
/>
|
|
223
|
-
|
|
232
|
+
{{ t('inspector.testConfig.provisioningTitle') }}
|
|
224
233
|
</button>
|
|
225
234
|
|
|
226
235
|
<div v-if="showProvisioning" class="mt-2 space-y-3">
|
|
227
236
|
<p class="text-[11px] leading-snug text-slate-500">
|
|
228
|
-
|
|
229
|
-
to deploy to and how large an instance to request. Ignored for local (docker-compose)
|
|
230
|
-
testing.
|
|
237
|
+
{{ t('inspector.testConfig.provisioningHint') }}
|
|
231
238
|
</p>
|
|
232
239
|
|
|
233
240
|
<div class="space-y-1">
|
|
234
|
-
<span class="text-[11px] text-slate-400">
|
|
241
|
+
<span class="text-[11px] text-slate-400">{{
|
|
242
|
+
t('inspector.testConfig.cloudProvider')
|
|
243
|
+
}}</span>
|
|
235
244
|
<div class="flex flex-wrap gap-1">
|
|
236
245
|
<UButton
|
|
237
246
|
v-for="p in PROVIDERS"
|
|
@@ -247,7 +256,9 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
|
|
|
247
256
|
</div>
|
|
248
257
|
|
|
249
258
|
<div class="space-y-1">
|
|
250
|
-
<span class="text-[11px] text-slate-400">
|
|
259
|
+
<span class="text-[11px] text-slate-400">{{
|
|
260
|
+
t('inspector.testConfig.instanceSize')
|
|
261
|
+
}}</span>
|
|
251
262
|
<div class="flex flex-wrap gap-1">
|
|
252
263
|
<UButton
|
|
253
264
|
v-for="s in SIZES"
|
|
@@ -13,6 +13,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
13
13
|
const board = useBoardStore()
|
|
14
14
|
const agentConfig = useAgentConfigStore()
|
|
15
15
|
const execution = useExecutionStore()
|
|
16
|
+
const { t } = useI18n()
|
|
16
17
|
|
|
17
18
|
// The descriptors that apply: those contributed by the task's pinned pipeline, plus
|
|
18
19
|
// any whose value is already set (so an existing choice always stays visible/editable
|
|
@@ -77,20 +78,20 @@ function setValue(id: string, value: string) {
|
|
|
77
78
|
<template>
|
|
78
79
|
<div v-if="descriptors.length" class="space-y-3">
|
|
79
80
|
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
80
|
-
|
|
81
|
+
{{ t('inspector.agentConfig.title') }}
|
|
81
82
|
</div>
|
|
82
83
|
<div v-for="d in descriptors" :key="d.id" class="space-y-1">
|
|
83
84
|
<div class="flex items-center justify-between">
|
|
84
85
|
<span class="text-[11px] text-slate-400">{{ d.label }}</span>
|
|
85
86
|
<div class="flex items-center gap-1.5">
|
|
86
|
-
<span v-if="isInherited(d)" class="text-[10px] text-slate-500"
|
|
87
|
-
|
|
88
|
-
>
|
|
87
|
+
<span v-if="isInherited(d)" class="text-[10px] text-slate-500">{{
|
|
88
|
+
t('inspector.agentConfig.inherited')
|
|
89
|
+
}}</span>
|
|
89
90
|
<UIcon
|
|
90
91
|
v-if="isFrozen(d.agentKind)"
|
|
91
92
|
name="i-lucide-lock"
|
|
92
93
|
class="h-3 w-3 text-slate-500"
|
|
93
|
-
title="
|
|
94
|
+
:title="t('inspector.agentConfig.frozen')"
|
|
94
95
|
/>
|
|
95
96
|
</div>
|
|
96
97
|
</div>
|
|
@@ -5,6 +5,7 @@ const props = defineProps<{ block: Block }>()
|
|
|
5
5
|
|
|
6
6
|
const board = useBoardStore()
|
|
7
7
|
const { depLabel } = useDepLabels()
|
|
8
|
+
const { t } = useI18n()
|
|
8
9
|
|
|
9
10
|
const deps = computed(() =>
|
|
10
11
|
(props.block.dependsOn ?? []).map((id) => board.getBlock(id)).filter((b): b is Block => !!b),
|
|
@@ -35,7 +36,7 @@ function removeDep(depId: string) {
|
|
|
35
36
|
<div>
|
|
36
37
|
<div class="mb-1 flex items-center justify-between">
|
|
37
38
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
38
|
-
|
|
39
|
+
{{ t('inspector.dependencies.title') }}
|
|
39
40
|
</span>
|
|
40
41
|
<UDropdownMenu v-if="depMenu.length" :items="depMenu">
|
|
41
42
|
<UButton
|
|
@@ -55,16 +56,20 @@ function removeDep(depId: string) {
|
|
|
55
56
|
variant="subtle"
|
|
56
57
|
size="sm"
|
|
57
58
|
class="cursor-pointer"
|
|
58
|
-
:title="
|
|
59
|
+
:title="
|
|
60
|
+
d.status === 'done'
|
|
61
|
+
? t('inspector.dependencies.merged')
|
|
62
|
+
: t('inspector.dependencies.notMerged')
|
|
63
|
+
"
|
|
59
64
|
@click="removeDep(d.id)"
|
|
60
65
|
>
|
|
61
66
|
{{ label(d) }}
|
|
62
67
|
<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
|
|
63
68
|
</UBadge>
|
|
64
69
|
</div>
|
|
65
|
-
<div v-else class="text-[11px] text-slate-500">
|
|
70
|
+
<div v-else class="text-[11px] text-slate-500">{{ t('inspector.dependencies.empty') }}</div>
|
|
66
71
|
<div v-if="!runnable" class="mt-1 text-[10px] text-amber-400">
|
|
67
|
-
|
|
72
|
+
{{ t('inspector.dependencies.blocked') }}
|
|
68
73
|
</div>
|
|
69
74
|
</div>
|
|
70
75
|
</template>
|
|
@@ -6,18 +6,19 @@ import { computed } from 'vue'
|
|
|
6
6
|
import type { Block } from '~/types/domain'
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{ block: Block }>()
|
|
9
|
+
const { t, n } = useI18n()
|
|
9
10
|
|
|
10
11
|
const estimate = computed(() => props.block.estimate ?? null)
|
|
11
12
|
|
|
12
|
-
const AXES =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const AXES = computed(
|
|
14
|
+
() =>
|
|
15
|
+
[
|
|
16
|
+
{ key: 'complexity', label: t('inspector.estimate.complexity') },
|
|
17
|
+
{ key: 'risk', label: t('inspector.estimate.risk') },
|
|
18
|
+
{ key: 'impact', label: t('inspector.estimate.impact') },
|
|
19
|
+
] as const,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
|
-
function pct(n: number): number {
|
|
19
|
-
return Math.round(n * 100)
|
|
20
|
-
}
|
|
21
22
|
/** Cool→hot bar colour by severity (low = sky, mid = amber, high = rose). */
|
|
22
23
|
function barClass(n: number): string {
|
|
23
24
|
if (n >= 0.66) return 'bg-rose-500'
|
|
@@ -32,7 +33,7 @@ function barClass(n: number): string {
|
|
|
32
33
|
class="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-slate-400"
|
|
33
34
|
>
|
|
34
35
|
<UIcon name="i-lucide-gauge" class="h-3.5 w-3.5" />
|
|
35
|
-
|
|
36
|
+
{{ t('inspector.estimate.title') }}
|
|
36
37
|
</div>
|
|
37
38
|
<div class="space-y-1.5 rounded-lg border border-slate-800 bg-slate-900/40 p-2.5">
|
|
38
39
|
<div v-for="axis in AXES" :key="axis.key" class="flex items-center gap-2">
|
|
@@ -41,12 +42,12 @@ function barClass(n: number): string {
|
|
|
41
42
|
<div
|
|
42
43
|
class="h-full rounded-full"
|
|
43
44
|
:class="barClass(estimate[axis.key])"
|
|
44
|
-
:style="{ width: `${
|
|
45
|
+
:style="{ width: `${Math.round(estimate[axis.key] * 100)}%` }"
|
|
45
46
|
/>
|
|
46
47
|
</div>
|
|
47
|
-
<span class="w-9 shrink-0 text-right text-xs tabular-nums text-slate-300"
|
|
48
|
-
|
|
49
|
-
>
|
|
48
|
+
<span class="w-9 shrink-0 text-right text-xs tabular-nums text-slate-300">{{
|
|
49
|
+
n(estimate[axis.key], { key: 'percent' })
|
|
50
|
+
}}</span>
|
|
50
51
|
</div>
|
|
51
52
|
<p v-if="estimate.rationale" class="pt-1 text-xs leading-relaxed text-slate-500">
|
|
52
53
|
{{ estimate.rationale }}
|