@cat-factory/app 0.54.0 → 0.54.2
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/bootstrap/BootstrapModal.vue +81 -69
- package/app/components/environments/EnvironmentStatusPanel.vue +37 -14
- package/app/components/focus/BlockFocusView.vue +92 -98
- package/app/components/fragments/FragmentLibraryManager.vue +104 -69
- package/app/components/fragments/FragmentLibraryPanel.vue +2 -1
- package/app/components/kaizen/KaizenPanel.vue +36 -25
- package/app/components/kaizen/KaizenStepStatus.vue +13 -10
- package/app/components/media/ArtifactLightbox.vue +20 -14
- package/app/components/media/ImageCompare.vue +35 -22
- package/app/components/provisioning/ProvisioningLogsDrawer.vue +27 -14
- package/app/components/recurring/RecurrenceEditor.vue +22 -15
- package/app/components/sandbox/SandboxPanel.vue +126 -59
- package/app/pages/index.vue +20 -8
- package/app/stores/workspace.ts +10 -5
- package/i18n/locales/en.json +409 -0
- package/i18n/locales/es.json +400 -0
- package/i18n/locales/fr.json +400 -0
- package/i18n/locales/pl.json +400 -0
- package/i18n/locales/uk.json +400 -0
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ const bootstrap = useBootstrapStore()
|
|
|
14
14
|
const agentRuns = useAgentRunsStore()
|
|
15
15
|
const github = useGitHubStore()
|
|
16
16
|
const toast = useToast()
|
|
17
|
+
const { t } = useI18n()
|
|
17
18
|
|
|
18
19
|
const open = computed({
|
|
19
20
|
get: () => ui.bootstrapOpen,
|
|
@@ -53,18 +54,18 @@ const hasRepoOptions = computed(() => repoOptions.value.length > 0)
|
|
|
53
54
|
// ---- launch form -----------------------------------------------------------
|
|
54
55
|
type LaunchMode = 'reference' | 'scratch'
|
|
55
56
|
const mode = ref<LaunchMode>('reference')
|
|
56
|
-
const modeItems = [
|
|
57
|
+
const modeItems = computed(() => [
|
|
57
58
|
{
|
|
58
|
-
label: '
|
|
59
|
+
label: t('bootstrap.mode.reference.label'),
|
|
59
60
|
value: 'reference' as const,
|
|
60
|
-
description: '
|
|
61
|
+
description: t('bootstrap.mode.reference.description'),
|
|
61
62
|
},
|
|
62
63
|
{
|
|
63
|
-
label: '
|
|
64
|
+
label: t('bootstrap.mode.scratch.label'),
|
|
64
65
|
value: 'scratch' as const,
|
|
65
|
-
description: '
|
|
66
|
+
description: t('bootstrap.mode.scratch.description'),
|
|
66
67
|
},
|
|
67
|
-
]
|
|
68
|
+
])
|
|
68
69
|
|
|
69
70
|
const selectedArchId = ref<string | undefined>(undefined)
|
|
70
71
|
const repoName = ref('')
|
|
@@ -83,9 +84,9 @@ const REPO_NAME_RE = /^[A-Za-z0-9_.-]+$/
|
|
|
83
84
|
const repoNameError = computed<string | undefined>(() => {
|
|
84
85
|
const value = repoName.value.trim()
|
|
85
86
|
if (!value) return undefined
|
|
86
|
-
if (value.includes('/')) return '
|
|
87
|
-
if (!REPO_NAME_RE.test(value)) return '
|
|
88
|
-
if (value.length > 100) return '
|
|
87
|
+
if (value.includes('/')) return t('bootstrap.repoName.error.hasSlash')
|
|
88
|
+
if (!REPO_NAME_RE.test(value)) return t('bootstrap.repoName.error.invalidChars')
|
|
89
|
+
if (value.length > 100) return t('bootstrap.repoName.error.tooLong')
|
|
89
90
|
return undefined
|
|
90
91
|
})
|
|
91
92
|
|
|
@@ -159,14 +160,14 @@ async function openCreateRepo() {
|
|
|
159
160
|
description: description.value.trim() || undefined,
|
|
160
161
|
})
|
|
161
162
|
toast.add({
|
|
162
|
-
title: '
|
|
163
|
+
title: t('bootstrap.toast.repoCreated'),
|
|
163
164
|
description: `${repo.owner}/${repo.name}`,
|
|
164
165
|
icon: 'i-lucide-check',
|
|
165
166
|
color: 'success',
|
|
166
167
|
})
|
|
167
168
|
} catch (e) {
|
|
168
169
|
toast.add({
|
|
169
|
-
title: '
|
|
170
|
+
title: t('bootstrap.toast.repoCreateFailed'),
|
|
170
171
|
description: e instanceof Error ? e.message : String(e),
|
|
171
172
|
icon: 'i-lucide-triangle-alert',
|
|
172
173
|
color: 'error',
|
|
@@ -214,8 +215,8 @@ async function launch() {
|
|
|
214
215
|
// The container couldn't even start (pre-flight failure, e.g. the target
|
|
215
216
|
// repo isn't empty) — surfaced synchronously, before any board frame.
|
|
216
217
|
toast.add({
|
|
217
|
-
title: '
|
|
218
|
-
description: job.error ?? '
|
|
218
|
+
title: t('bootstrap.toast.failed'),
|
|
219
|
+
description: job.error ?? t('bootstrap.toast.failedFallback'),
|
|
219
220
|
icon: 'i-lucide-triangle-alert',
|
|
220
221
|
color: 'error',
|
|
221
222
|
})
|
|
@@ -224,8 +225,8 @@ async function launch() {
|
|
|
224
225
|
// shows on the board and tracks live progress; the run continues in the
|
|
225
226
|
// background and becomes a real, droppable service when it finishes.
|
|
226
227
|
toast.add({
|
|
227
|
-
title: '
|
|
228
|
-
description:
|
|
228
|
+
title: t('bootstrap.toast.started'),
|
|
229
|
+
description: t('bootstrap.toast.startedDesc', { repo: job.repoName }),
|
|
229
230
|
icon: 'i-lucide-loader-circle',
|
|
230
231
|
color: 'info',
|
|
231
232
|
})
|
|
@@ -238,7 +239,7 @@ async function launch() {
|
|
|
238
239
|
}
|
|
239
240
|
} catch (e) {
|
|
240
241
|
toast.add({
|
|
241
|
-
title: '
|
|
242
|
+
title: t('bootstrap.toast.bootstrapFailed'),
|
|
242
243
|
description: e instanceof Error ? e.message : String(e),
|
|
243
244
|
icon: 'i-lucide-triangle-alert',
|
|
244
245
|
color: 'error',
|
|
@@ -329,7 +330,7 @@ async function saveArch() {
|
|
|
329
330
|
archRepoSlug.value = undefined
|
|
330
331
|
} catch (e) {
|
|
331
332
|
toast.add({
|
|
332
|
-
title: '
|
|
333
|
+
title: t('bootstrap.toast.saveArchFailed'),
|
|
333
334
|
description: e instanceof Error ? e.message : String(e),
|
|
334
335
|
icon: 'i-lucide-triangle-alert',
|
|
335
336
|
color: 'error',
|
|
@@ -345,7 +346,7 @@ async function removeArch(a: ReferenceArchitecture) {
|
|
|
345
346
|
if (selectedArchId.value === a.id) selectedArchId.value = undefined
|
|
346
347
|
} catch (e) {
|
|
347
348
|
toast.add({
|
|
348
|
-
title: '
|
|
349
|
+
title: t('bootstrap.toast.deleteFailed'),
|
|
349
350
|
description: e instanceof Error ? e.message : String(e),
|
|
350
351
|
icon: 'i-lucide-triangle-alert',
|
|
351
352
|
color: 'error',
|
|
@@ -359,21 +360,22 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
359
360
|
succeeded: 'success',
|
|
360
361
|
failed: 'error',
|
|
361
362
|
}
|
|
363
|
+
|
|
364
|
+
// Exhaustive status→label map of literal `t(...)` keys (keeps the typed-key drift guard live).
|
|
365
|
+
const statusLabel = computed<Record<BootstrapStatus, string>>(() => ({
|
|
366
|
+
pending: t('bootstrap.status.pending'),
|
|
367
|
+
running: t('bootstrap.status.running'),
|
|
368
|
+
succeeded: t('bootstrap.status.succeeded'),
|
|
369
|
+
failed: t('bootstrap.status.failed'),
|
|
370
|
+
}))
|
|
362
371
|
</script>
|
|
363
372
|
|
|
364
373
|
<template>
|
|
365
|
-
<UModal v-model:open="open" title="
|
|
374
|
+
<UModal v-model:open="open" :title="t('bootstrap.title')" :ui="{ content: 'max-w-2xl' }">
|
|
366
375
|
<template #body>
|
|
367
376
|
<div class="space-y-6">
|
|
368
377
|
<p class="text-sm text-slate-400">
|
|
369
|
-
|
|
370
|
-
container — either by adapting one of your reference architectures, or from scratch
|
|
371
|
-
following a freeform prompt. cat-factory pushes the initial commit into that repo;
|
|
372
|
-
{{
|
|
373
|
-
github.canCreateRepos
|
|
374
|
-
? 'for this account it can create the repository for you too.'
|
|
375
|
-
: 'you create the repository (one click below), so it needs no repo-creation permission.'
|
|
376
|
-
}}
|
|
378
|
+
{{ github.canCreateRepos ? t('bootstrap.intro.canCreate') : t('bootstrap.intro.manual') }}
|
|
377
379
|
</p>
|
|
378
380
|
|
|
379
381
|
<!-- not connected: a run needs GitHub, so discover & link before launching -->
|
|
@@ -384,8 +386,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
384
386
|
<div class="flex items-start gap-2">
|
|
385
387
|
<UIcon name="i-lucide-plug-zap" class="mt-0.5 h-4 w-4 shrink-0 text-amber-400" />
|
|
386
388
|
<p class="text-sm text-amber-200/90">
|
|
387
|
-
|
|
388
|
-
repository. Link an installation the App is already on, or install it.
|
|
389
|
+
{{ t('bootstrap.github.prompt') }}
|
|
389
390
|
</p>
|
|
390
391
|
</div>
|
|
391
392
|
<GitHubConnect />
|
|
@@ -394,38 +395,38 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
394
395
|
<!-- launch -->
|
|
395
396
|
<section class="space-y-4">
|
|
396
397
|
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
397
|
-
|
|
398
|
+
{{ t('bootstrap.section.newRepo') }}
|
|
398
399
|
</h3>
|
|
399
400
|
|
|
400
|
-
<UFormField label="
|
|
401
|
+
<UFormField :label="t('bootstrap.mode.label')" required>
|
|
401
402
|
<URadioGroup v-model="mode" :items="modeItems" />
|
|
402
403
|
</UFormField>
|
|
403
404
|
|
|
404
405
|
<template v-if="usingReference">
|
|
405
406
|
<UFormField
|
|
406
|
-
label="
|
|
407
|
-
description="
|
|
407
|
+
:label="t('bootstrap.reference.label')"
|
|
408
|
+
:description="t('bootstrap.reference.description')"
|
|
408
409
|
required
|
|
409
410
|
>
|
|
410
411
|
<div v-if="!bootstrap.hasArchitectures" class="text-sm text-slate-400">
|
|
411
|
-
|
|
412
|
+
{{ t('bootstrap.reference.empty') }}
|
|
412
413
|
</div>
|
|
413
414
|
<USelect
|
|
414
415
|
v-else
|
|
415
416
|
v-model="selectedArchId"
|
|
416
417
|
:items="archOptions"
|
|
417
|
-
placeholder="
|
|
418
|
+
:placeholder="t('bootstrap.reference.placeholder')"
|
|
418
419
|
class="w-full"
|
|
419
420
|
/>
|
|
420
421
|
</UFormField>
|
|
421
422
|
</template>
|
|
422
423
|
|
|
423
424
|
<UFormField
|
|
424
|
-
label="
|
|
425
|
+
:label="t('bootstrap.targetRepo.label')"
|
|
425
426
|
:description="
|
|
426
427
|
repoOwner
|
|
427
|
-
?
|
|
428
|
-
: '
|
|
428
|
+
? t('bootstrap.targetRepo.descWithOwner', { owner: repoOwner })
|
|
429
|
+
: t('bootstrap.targetRepo.descNoOwner')
|
|
429
430
|
"
|
|
430
431
|
required
|
|
431
432
|
:error="repoNameError"
|
|
@@ -441,12 +442,16 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
441
442
|
:disabled="!repoName.trim() || !!repoNameError"
|
|
442
443
|
:title="
|
|
443
444
|
github.canCreateRepos
|
|
444
|
-
? '
|
|
445
|
-
:
|
|
445
|
+
? t('bootstrap.createRepo.titleNow')
|
|
446
|
+
: t('bootstrap.createRepo.titleGitHub')
|
|
446
447
|
"
|
|
447
448
|
@click="openCreateRepo"
|
|
448
449
|
>
|
|
449
|
-
{{
|
|
450
|
+
{{
|
|
451
|
+
github.canCreateRepos
|
|
452
|
+
? t('bootstrap.createRepo.now')
|
|
453
|
+
: t('bootstrap.createRepo.onGitHub')
|
|
454
|
+
}}
|
|
450
455
|
</UButton>
|
|
451
456
|
</div>
|
|
452
457
|
<UButton
|
|
@@ -456,15 +461,18 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
456
461
|
size="sm"
|
|
457
462
|
icon="i-lucide-shield-check"
|
|
458
463
|
trailing-icon="i-lucide-external-link"
|
|
459
|
-
title="
|
|
464
|
+
:title="t('bootstrap.grantAccess.title')"
|
|
460
465
|
@click="openManageInstall"
|
|
461
466
|
>
|
|
462
|
-
|
|
467
|
+
{{ t('bootstrap.grantAccess.label') }}
|
|
463
468
|
</UButton>
|
|
464
469
|
</div>
|
|
465
470
|
</UFormField>
|
|
466
471
|
|
|
467
|
-
<UFormField
|
|
472
|
+
<UFormField
|
|
473
|
+
:label="t('bootstrap.description.label')"
|
|
474
|
+
:description="t('bootstrap.description.help')"
|
|
475
|
+
>
|
|
468
476
|
<UInput
|
|
469
477
|
v-model="description"
|
|
470
478
|
placeholder="Handles payment intents and refunds"
|
|
@@ -475,13 +483,13 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
475
483
|
<UFormField
|
|
476
484
|
:label="
|
|
477
485
|
usingReference
|
|
478
|
-
? '
|
|
479
|
-
: '
|
|
486
|
+
? t('bootstrap.instructions.labelReference')
|
|
487
|
+
: t('bootstrap.instructions.labelScratch')
|
|
480
488
|
"
|
|
481
489
|
:description="
|
|
482
490
|
usingReference
|
|
483
|
-
? '
|
|
484
|
-
: '
|
|
491
|
+
? t('bootstrap.instructions.descReference')
|
|
492
|
+
: t('bootstrap.instructions.descScratch')
|
|
485
493
|
"
|
|
486
494
|
:required="!usingReference"
|
|
487
495
|
>
|
|
@@ -497,10 +505,10 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
497
505
|
/>
|
|
498
506
|
</UFormField>
|
|
499
507
|
|
|
500
|
-
<UFormField label="
|
|
508
|
+
<UFormField :label="t('bootstrap.visibility.label')">
|
|
501
509
|
<div class="flex items-center gap-2">
|
|
502
510
|
<USwitch v-model="isPrivate" />
|
|
503
|
-
<span class="text-sm text-slate-300">
|
|
511
|
+
<span class="text-sm text-slate-300">{{ t('bootstrap.visibility.private') }}</span>
|
|
504
512
|
</div>
|
|
505
513
|
</UFormField>
|
|
506
514
|
|
|
@@ -512,7 +520,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
512
520
|
:disabled="!canLaunch"
|
|
513
521
|
@click="launch"
|
|
514
522
|
>
|
|
515
|
-
|
|
523
|
+
{{ t('bootstrap.launch') }}
|
|
516
524
|
</UButton>
|
|
517
525
|
</div>
|
|
518
526
|
</section>
|
|
@@ -520,7 +528,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
520
528
|
<!-- recent jobs -->
|
|
521
529
|
<section v-if="agentRuns.bootstrapJobs.length" class="space-y-2">
|
|
522
530
|
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
523
|
-
|
|
531
|
+
{{ t('bootstrap.recent.title') }}
|
|
524
532
|
</h3>
|
|
525
533
|
<div
|
|
526
534
|
v-for="job in agentRuns.bootstrapJobs.slice(0, 5)"
|
|
@@ -532,8 +540,8 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
532
540
|
<div class="truncate text-[11px] text-slate-500">
|
|
533
541
|
{{
|
|
534
542
|
job.referenceArchitectureName
|
|
535
|
-
?
|
|
536
|
-
: '
|
|
543
|
+
? t('bootstrap.recent.fromArch', { name: job.referenceArchitectureName })
|
|
544
|
+
: t('bootstrap.recent.fromScratch')
|
|
537
545
|
}}
|
|
538
546
|
</div>
|
|
539
547
|
</div>
|
|
@@ -544,10 +552,10 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
544
552
|
target="_blank"
|
|
545
553
|
class="text-[11px] text-indigo-400 hover:underline"
|
|
546
554
|
>
|
|
547
|
-
|
|
555
|
+
{{ t('bootstrap.recent.open') }}
|
|
548
556
|
</ULink>
|
|
549
557
|
<UBadge :color="statusColor[job.status]" variant="subtle" size="sm">
|
|
550
|
-
{{ job.status }}
|
|
558
|
+
{{ statusLabel[job.status] }}
|
|
551
559
|
</UBadge>
|
|
552
560
|
</div>
|
|
553
561
|
</div>
|
|
@@ -559,7 +567,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
559
567
|
<section class="space-y-3">
|
|
560
568
|
<div class="flex items-center justify-between">
|
|
561
569
|
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
562
|
-
|
|
570
|
+
{{ t('bootstrap.arch.title') }}
|
|
563
571
|
</h3>
|
|
564
572
|
<UButton
|
|
565
573
|
size="xs"
|
|
@@ -568,7 +576,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
568
576
|
icon="i-lucide-plus"
|
|
569
577
|
@click="startCreate"
|
|
570
578
|
>
|
|
571
|
-
|
|
579
|
+
{{ t('bootstrap.arch.add') }}
|
|
572
580
|
</UButton>
|
|
573
581
|
</div>
|
|
574
582
|
|
|
@@ -608,8 +616,8 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
608
616
|
>
|
|
609
617
|
<UFormField
|
|
610
618
|
v-if="hasRepoOptions"
|
|
611
|
-
label="
|
|
612
|
-
description="
|
|
619
|
+
:label="t('bootstrap.arch.pickRepo.label')"
|
|
620
|
+
:description="t('bootstrap.arch.pickRepo.description')"
|
|
613
621
|
>
|
|
614
622
|
<USelect
|
|
615
623
|
v-model="archRepoSlug"
|
|
@@ -619,27 +627,31 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
619
627
|
/>
|
|
620
628
|
</UFormField>
|
|
621
629
|
|
|
622
|
-
<UFormField
|
|
630
|
+
<UFormField
|
|
631
|
+
:label="t('bootstrap.arch.name.label')"
|
|
632
|
+
:description="t('bootstrap.arch.name.description')"
|
|
633
|
+
required
|
|
634
|
+
>
|
|
623
635
|
<UInput v-model="archForm.name" placeholder="Service Template" class="w-full" />
|
|
624
636
|
</UFormField>
|
|
625
637
|
<div class="grid grid-cols-2 gap-2">
|
|
626
|
-
<UFormField label="
|
|
638
|
+
<UFormField :label="t('bootstrap.arch.repoOwner')" required>
|
|
627
639
|
<UInput v-model="archForm.repoOwner" placeholder="acme" class="w-full" />
|
|
628
640
|
</UFormField>
|
|
629
|
-
<UFormField label="
|
|
641
|
+
<UFormField :label="t('bootstrap.arch.repoName')" required>
|
|
630
642
|
<UInput v-model="archForm.repoName" placeholder="service-template" class="w-full" />
|
|
631
643
|
</UFormField>
|
|
632
644
|
</div>
|
|
633
|
-
<UFormField label="
|
|
645
|
+
<UFormField :label="t('bootstrap.description.label')">
|
|
634
646
|
<UInput
|
|
635
647
|
v-model="archForm.description"
|
|
636
|
-
placeholder="
|
|
648
|
+
:placeholder="t('bootstrap.arch.descriptionPlaceholder')"
|
|
637
649
|
class="w-full"
|
|
638
650
|
/>
|
|
639
651
|
</UFormField>
|
|
640
652
|
<UFormField
|
|
641
|
-
label="
|
|
642
|
-
description="
|
|
653
|
+
:label="t('bootstrap.arch.defaultInstructions.label')"
|
|
654
|
+
:description="t('bootstrap.arch.defaultInstructions.description')"
|
|
643
655
|
>
|
|
644
656
|
<UTextarea
|
|
645
657
|
v-model="archForm.defaultInstructions"
|
|
@@ -650,7 +662,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
650
662
|
</UFormField>
|
|
651
663
|
<div class="flex justify-end gap-2">
|
|
652
664
|
<UButton color="neutral" variant="ghost" @click="showArchForm = false">
|
|
653
|
-
|
|
665
|
+
{{ t('common.cancel') }}
|
|
654
666
|
</UButton>
|
|
655
667
|
<UButton
|
|
656
668
|
color="primary"
|
|
@@ -658,7 +670,7 @@ const statusColor: Record<BootstrapStatus, 'neutral' | 'info' | 'success' | 'err
|
|
|
658
670
|
:disabled="!canSaveArch"
|
|
659
671
|
@click="saveArch"
|
|
660
672
|
>
|
|
661
|
-
{{ archForm.id ? '
|
|
673
|
+
{{ archForm.id ? t('common.save') : t('bootstrap.arch.add') }}
|
|
662
674
|
</UButton>
|
|
663
675
|
</div>
|
|
664
676
|
</div>
|
|
@@ -7,27 +7,50 @@ import type { RunEnvironment, HumanTestEnvironmentStatus } from '~/types/executi
|
|
|
7
7
|
|
|
8
8
|
defineProps<{ environment: RunEnvironment | null; degradedReason?: string | null }>()
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
const { t, d } = useI18n()
|
|
11
|
+
|
|
12
|
+
// Exhaustive enum→label map of literal `t(...)` keys (keeps the typed-key drift guard
|
|
13
|
+
// live); the color/icon stay static, English-neutral styling.
|
|
14
|
+
const ENV_STATUS_META = computed<
|
|
15
|
+
Record<HumanTestEnvironmentStatus, { label: string; color: string; icon: string }>
|
|
16
|
+
>(() => ({
|
|
17
|
+
provisioning: {
|
|
18
|
+
label: t('environments.status.provisioning'),
|
|
19
|
+
color: 'text-amber-300',
|
|
20
|
+
icon: 'i-lucide-loader-circle',
|
|
21
|
+
},
|
|
22
|
+
ready: {
|
|
23
|
+
label: t('environments.status.ready'),
|
|
24
|
+
color: 'text-emerald-300',
|
|
25
|
+
icon: 'i-lucide-circle-dot',
|
|
26
|
+
},
|
|
27
|
+
failed: {
|
|
28
|
+
label: t('environments.status.failed'),
|
|
29
|
+
color: 'text-rose-300',
|
|
30
|
+
icon: 'i-lucide-circle-alert',
|
|
31
|
+
},
|
|
32
|
+
expired: {
|
|
33
|
+
label: t('environments.status.expired'),
|
|
34
|
+
color: 'text-slate-400',
|
|
35
|
+
icon: 'i-lucide-circle-off',
|
|
36
|
+
},
|
|
18
37
|
tearing_down: {
|
|
19
|
-
label: '
|
|
38
|
+
label: t('environments.status.tearing_down'),
|
|
20
39
|
color: 'text-slate-400',
|
|
21
40
|
icon: 'i-lucide-loader-circle',
|
|
22
41
|
},
|
|
23
|
-
torn_down: {
|
|
24
|
-
|
|
42
|
+
torn_down: {
|
|
43
|
+
label: t('environments.status.torn_down'),
|
|
44
|
+
color: 'text-slate-400',
|
|
45
|
+
icon: 'i-lucide-circle-off',
|
|
46
|
+
},
|
|
47
|
+
}))
|
|
25
48
|
</script>
|
|
26
49
|
|
|
27
50
|
<template>
|
|
28
51
|
<section class="rounded-lg border border-slate-800 bg-slate-900/60 p-3">
|
|
29
52
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
30
|
-
|
|
53
|
+
{{ t('environments.title') }}
|
|
31
54
|
</h3>
|
|
32
55
|
<div v-if="environment" class="space-y-2">
|
|
33
56
|
<div class="flex items-center gap-2 text-[13px]">
|
|
@@ -57,7 +80,7 @@ const ENV_STATUS_META: Record<
|
|
|
57
80
|
{{ environment.url }}
|
|
58
81
|
</a>
|
|
59
82
|
<p v-if="environment.expiresAt" class="text-[11px] text-slate-500">
|
|
60
|
-
|
|
83
|
+
{{ t('environments.expires', { date: d(new Date(environment.expiresAt), 'long') }) }}
|
|
61
84
|
</p>
|
|
62
85
|
<!-- The verbatim provider error when the environment failed/expired. -->
|
|
63
86
|
<pre
|
|
@@ -70,7 +93,7 @@ const ENV_STATUS_META: Record<
|
|
|
70
93
|
>
|
|
71
94
|
</div>
|
|
72
95
|
<p v-else class="text-[12px] text-slate-500">
|
|
73
|
-
{{ degradedReason ?? '
|
|
96
|
+
{{ degradedReason ?? t('environments.empty') }}
|
|
74
97
|
</p>
|
|
75
98
|
</section>
|
|
76
99
|
</template>
|