@cat-factory/app 0.190.1 → 0.191.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/layout/InfraSetupBanner.vue +139 -35
- package/app/components/layout/NotificationsInbox.vue +5 -0
- package/app/components/slack/SlackPanel.vue +2 -0
- package/app/composables/useWorkspaceStream.ts +6 -0
- package/app/stores/ui/modals.ts +31 -8
- package/app/stores/workspace/infraSetup.ts +77 -0
- package/app/stores/workspace.spec.ts +129 -0
- package/app/stores/workspace.ts +12 -8
- package/app/utils/infraSetup.ts +29 -0
- package/i18n/locales/de.json +9 -0
- package/i18n/locales/en.json +9 -0
- package/i18n/locales/es.json +9 -0
- package/i18n/locales/fr.json +9 -0
- package/i18n/locales/he.json +9 -0
- package/i18n/locales/it.json +9 -0
- package/i18n/locales/ja.json +9 -0
- package/i18n/locales/pl.json +9 -0
- package/i18n/locales/tr.json +9 -0
- package/i18n/locales/uk.json +9 -0
- package/package.json +2 -2
|
@@ -22,17 +22,36 @@
|
|
|
22
22
|
// account that also has no storage — an accepted trade-off (the setting stays reachable from
|
|
23
23
|
// account settings, and the SESSION dismissal re-nags on the next load regardless).
|
|
24
24
|
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
25
|
+
// Two KINDS of card share this surface, and the difference drives the dismissal fork:
|
|
26
|
+
// - a setup gap (`not_defined`) is a stable operator decision, so both dismissals are offered;
|
|
27
|
+
// - an OUTAGE (`unreachable` — configured, but the reachability watcher's live probe can't reach
|
|
28
|
+
// it) is a health state, so ONLY the session dismissal is offered. A permanent "don't notify me
|
|
29
|
+
// again" on a transient failure would let one click silence every future outage, and the outage
|
|
30
|
+
// that matters is always the next one. `isInfraSetupHealthStatus` (contracts) is the single
|
|
31
|
+
// definition both this component and the store's re-nag logic key off.
|
|
32
|
+
//
|
|
33
|
+
// BOTH dismissals are keyed by the CLAIM, never by the area alone, because the two cards say
|
|
34
|
+
// different things about the same area: silencing "you haven't configured this" must not also
|
|
35
|
+
// silence the outage card raised after the operator configures it and the provider then dies.
|
|
36
|
+
//
|
|
37
|
+
// Freshness note: a setup gap clears on the next board load after the operator configures the area
|
|
38
|
+
// via the deep-link, not the instant the config panel saves — the projection is recomputed on
|
|
39
|
+
// snapshot (re)load. An OUTAGE is different: it arrives and clears live, pushed as an `infraSetup`
|
|
40
|
+
// event by the watcher and applied by `workspace.patchInfraSetup`.
|
|
28
41
|
import { useLocalStorage } from '@vueuse/core'
|
|
29
42
|
import { computed } from 'vue'
|
|
30
43
|
// The localStorage key holding the permanent per-user dismissals lives in `@cat-factory/contracts`
|
|
31
44
|
// (a dependency-free package the SPA and the e2e suite both import), so the key + shape can't drift
|
|
32
45
|
// between this component and the e2e seed in `backend/internal/e2e/tests/helpers.ts` (`pinWorkspace`).
|
|
33
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
INFRA_SETUP_DISMISSED_STORAGE_KEY,
|
|
48
|
+
type InfraSetupProbedArea,
|
|
49
|
+
isInfraSetupHealthStatus,
|
|
50
|
+
isInfraSetupProbedArea,
|
|
51
|
+
} from '@cat-factory/contracts'
|
|
34
52
|
import type { DropdownMenuItem } from '@nuxt/ui'
|
|
35
|
-
import type { InfraSetupArea } from '~/types/domain'
|
|
53
|
+
import type { InfraSetupArea, InfraSetupStatus } from '~/types/domain'
|
|
54
|
+
import { infraSetupDismissalKey, type InfraSetupCardKind } from '~/utils/infraSetup'
|
|
36
55
|
|
|
37
56
|
const { t } = useI18n()
|
|
38
57
|
const ui = useUiStore()
|
|
@@ -75,6 +94,18 @@ const AREA_META: Record<
|
|
|
75
94
|
},
|
|
76
95
|
}
|
|
77
96
|
|
|
97
|
+
// Outage titles are keyed over the PROBED areas only, not over every area: `binaryStorage` has no
|
|
98
|
+
// reachability probe, so an entry for it would be copy that ships in every locale and can never
|
|
99
|
+
// render. Each probed area carries its OWN title rather than interpolating the area name into a
|
|
100
|
+
// shared one — a predicate adjective ("is unreachable") agrees with its subject's gender in most of
|
|
101
|
+
// the locales we ship, so `{area} is unreachable` cannot be translated correctly as one string. The
|
|
102
|
+
// outage BODY and action ARE shared, because neither refers back to the area; each locale's body
|
|
103
|
+
// opens with its own fixed subject noun for exactly that reason.
|
|
104
|
+
const UNREACHABLE_TITLE_KEYS: Record<InfraSetupProbedArea, string> = {
|
|
105
|
+
agentExecutor: 'layout.infraSetupBanner.agentExecutor.unreachableTitle',
|
|
106
|
+
ephemeralEnvironments: 'layout.infraSetupBanner.ephemeralEnvironments.unreachableTitle',
|
|
107
|
+
}
|
|
108
|
+
|
|
78
109
|
// Permanent, per-user dismissals: one shared localStorage record keyed BY user id (so it's
|
|
79
110
|
// scoped to the signed-in user and doesn't leak across accounts on a shared browser). No
|
|
80
111
|
// signed-in user (local/auth-off single-user mode) ⇒ the `local` bucket.
|
|
@@ -94,30 +125,64 @@ function dismissPermanently(area: InfraSetupArea) {
|
|
|
94
125
|
}
|
|
95
126
|
}
|
|
96
127
|
|
|
97
|
-
|
|
128
|
+
/** One rendered card: the area plus which CLAIM it is making about it. */
|
|
129
|
+
interface AreaCard {
|
|
130
|
+
area: InfraSetupArea
|
|
131
|
+
/** `outage` for a live-health status — drives the copy, the severity styling and the dismissals. */
|
|
132
|
+
kind: InfraSetupCardKind
|
|
133
|
+
/** The failing probe's reason, when this session saw the transition that raised the card. */
|
|
134
|
+
detail?: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const visible = computed<AreaCard[]>(() => {
|
|
98
138
|
const status = workspace.infraSetup
|
|
99
139
|
if (!status) return []
|
|
100
|
-
return AREAS.filter(
|
|
101
|
-
(area)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
140
|
+
return AREAS.filter((area) => {
|
|
141
|
+
const kind = cardKind(status[area])
|
|
142
|
+
if (!kind) return false
|
|
143
|
+
// Both dismissals are keyed by the CLAIM, not by the area: silencing "you haven't configured
|
|
144
|
+
// this" must not also silence "you configured it and it is now down". The permanent dismissal
|
|
145
|
+
// only ever covers a setup gap (`dismissPermanently` is offered nowhere else).
|
|
146
|
+
if (ui.infraSetupSessionDismissed.includes(infraSetupDismissalKey(area, kind))) return false
|
|
147
|
+
return !(kind === 'setup' && dismissedForUser.value.includes(area))
|
|
148
|
+
}).map((area) => ({
|
|
149
|
+
area,
|
|
150
|
+
kind: cardKind(status[area])!,
|
|
151
|
+
...(workspace.infraSetupDetails[area] ? { detail: workspace.infraSetupDetails[area] } : {}),
|
|
152
|
+
}))
|
|
106
153
|
})
|
|
107
154
|
|
|
108
|
-
|
|
109
|
-
function
|
|
155
|
+
/** Which card an area's status raises, or null when it raises none (`configured`/`not_applicable`). */
|
|
156
|
+
function cardKind(status: InfraSetupStatus): InfraSetupCardKind | null {
|
|
157
|
+
if (isInfraSetupHealthStatus(status)) return 'outage'
|
|
158
|
+
return status === 'not_defined' ? 'setup' : null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The card's title key: the per-area outage title for an outage, else the setup-gap title. */
|
|
162
|
+
function titleKey(card: AreaCard): string {
|
|
163
|
+
return card.kind === 'outage' && isInfraSetupProbedArea(card.area)
|
|
164
|
+
? UNREACHABLE_TITLE_KEYS[card.area]
|
|
165
|
+
: AREA_META[card.area].titleKey
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The dismiss dropdown: the product wants the user asked WHICH kind of dismissal on close. An
|
|
170
|
+
* outage offers the session option ONLY — see the fork note at the top of this file.
|
|
171
|
+
*/
|
|
172
|
+
function dismissMenu(card: AreaCard): DropdownMenuItem[][] {
|
|
173
|
+
const session = {
|
|
174
|
+
label: t('layout.infraSetupBanner.dismiss.session'),
|
|
175
|
+
icon: 'i-lucide-clock',
|
|
176
|
+
onSelect: () => ui.dismissInfraSetupForSession(card.area, card.kind),
|
|
177
|
+
}
|
|
178
|
+
if (card.kind === 'outage') return [[session]]
|
|
110
179
|
return [
|
|
111
180
|
[
|
|
112
|
-
|
|
113
|
-
label: t('layout.infraSetupBanner.dismiss.session'),
|
|
114
|
-
icon: 'i-lucide-clock',
|
|
115
|
-
onSelect: () => ui.dismissInfraSetupForSession(area),
|
|
116
|
-
},
|
|
181
|
+
session,
|
|
117
182
|
{
|
|
118
183
|
label: t('layout.infraSetupBanner.dismiss.permanent'),
|
|
119
184
|
icon: 'i-lucide-bell-off',
|
|
120
|
-
onSelect: () => dismissPermanently(area),
|
|
185
|
+
onSelect: () => dismissPermanently(card.area),
|
|
121
186
|
},
|
|
122
187
|
],
|
|
123
188
|
]
|
|
@@ -135,42 +200,81 @@ function dismissMenu(area: InfraSetupArea): DropdownMenuItem[][] {
|
|
|
135
200
|
role="status"
|
|
136
201
|
aria-live="polite"
|
|
137
202
|
>
|
|
203
|
+
<!-- An OUTAGE reads red, a setup gap amber: one is something breaking now, the other is
|
|
204
|
+
something never switched on, and a reader has to be able to tell at a glance. -->
|
|
138
205
|
<div
|
|
139
|
-
v-for="
|
|
140
|
-
:key="area"
|
|
141
|
-
class="pointer-events-auto w-full max-w-3xl rounded-2xl border-2
|
|
142
|
-
:
|
|
206
|
+
v-for="card in visible"
|
|
207
|
+
:key="card.area"
|
|
208
|
+
class="pointer-events-auto w-full max-w-3xl rounded-2xl border-2 p-5 shadow-2xl backdrop-blur"
|
|
209
|
+
:class="
|
|
210
|
+
card.kind === 'outage'
|
|
211
|
+
? 'border-red-500/70 bg-red-950/95'
|
|
212
|
+
: 'border-amber-500/70 bg-amber-950/95'
|
|
213
|
+
"
|
|
214
|
+
:data-testid="`infra-setup-banner-${card.area}`"
|
|
215
|
+
:data-infra-status="card.kind === 'outage' ? 'unreachable' : 'not_defined'"
|
|
143
216
|
>
|
|
144
217
|
<div class="flex items-start gap-4">
|
|
145
|
-
<UIcon
|
|
218
|
+
<UIcon
|
|
219
|
+
:name="card.kind === 'outage' ? 'i-lucide-plug-zap' : AREA_META[card.area].icon"
|
|
220
|
+
class="mt-0.5 h-9 w-9 shrink-0"
|
|
221
|
+
:class="card.kind === 'outage' ? 'text-red-400' : 'text-amber-400'"
|
|
222
|
+
/>
|
|
146
223
|
<div class="min-w-0 flex-1">
|
|
147
224
|
<div class="flex items-start justify-between gap-3">
|
|
148
|
-
<h2
|
|
149
|
-
|
|
225
|
+
<h2
|
|
226
|
+
class="text-lg font-semibold"
|
|
227
|
+
:class="card.kind === 'outage' ? 'text-red-100' : 'text-amber-100'"
|
|
228
|
+
>
|
|
229
|
+
{{ t(titleKey(card)) }}
|
|
150
230
|
</h2>
|
|
151
|
-
<UDropdownMenu :items="dismissMenu(
|
|
231
|
+
<UDropdownMenu :items="dismissMenu(card)" :content="{ align: 'end' }">
|
|
152
232
|
<UButton
|
|
153
233
|
color="neutral"
|
|
154
234
|
variant="ghost"
|
|
155
235
|
size="xs"
|
|
156
236
|
icon="i-lucide-x"
|
|
157
237
|
:aria-label="t('common.close')"
|
|
158
|
-
:data-testid="`infra-setup-dismiss-${area}`"
|
|
238
|
+
:data-testid="`infra-setup-dismiss-${card.area}`"
|
|
159
239
|
/>
|
|
160
240
|
</UDropdownMenu>
|
|
161
241
|
</div>
|
|
162
|
-
<p
|
|
163
|
-
|
|
242
|
+
<p
|
|
243
|
+
class="mt-1 text-sm"
|
|
244
|
+
:class="card.kind === 'outage' ? 'text-red-200/90' : 'text-amber-200/90'"
|
|
245
|
+
>
|
|
246
|
+
{{
|
|
247
|
+
card.kind === 'outage'
|
|
248
|
+
? t('layout.infraSetupBanner.unreachable.body')
|
|
249
|
+
: t(AREA_META[card.area].bodyKey)
|
|
250
|
+
}}
|
|
251
|
+
</p>
|
|
252
|
+
<!-- The failing probe's OWN reason, when this session saw the transition that raised the
|
|
253
|
+
card: a refused connection, a rejected token and a timeout need different fixes, and
|
|
254
|
+
the generic body cannot tell them apart. Absent after a reload (it rides the live
|
|
255
|
+
event, never the deduped notification), so it is an addition to the copy above and
|
|
256
|
+
never the only thing that explains the card. -->
|
|
257
|
+
<p
|
|
258
|
+
v-if="card.detail"
|
|
259
|
+
class="mt-2 truncate font-mono text-xs text-red-300/80"
|
|
260
|
+
:title="card.detail"
|
|
261
|
+
:data-testid="`infra-setup-detail-${card.area}`"
|
|
262
|
+
>
|
|
263
|
+
{{ t('layout.infraSetupBanner.unreachable.reason', { detail: card.detail }) }}
|
|
164
264
|
</p>
|
|
165
265
|
<div class="mt-4">
|
|
166
266
|
<UButton
|
|
167
|
-
color="warning"
|
|
267
|
+
:color="card.kind === 'outage' ? 'error' : 'warning'"
|
|
168
268
|
variant="solid"
|
|
169
269
|
icon="i-lucide-settings"
|
|
170
|
-
:data-testid="`infra-setup-configure-${area}`"
|
|
171
|
-
@click="AREA_META[area].onConfigure()"
|
|
270
|
+
:data-testid="`infra-setup-configure-${card.area}`"
|
|
271
|
+
@click="AREA_META[card.area].onConfigure()"
|
|
172
272
|
>
|
|
173
|
-
{{
|
|
273
|
+
{{
|
|
274
|
+
card.kind === 'outage'
|
|
275
|
+
? t('layout.infraSetupBanner.unreachable.action')
|
|
276
|
+
: t(AREA_META[card.area].actionKey)
|
|
277
|
+
}}
|
|
174
278
|
</UButton>
|
|
175
279
|
</div>
|
|
176
280
|
</div>
|
|
@@ -88,6 +88,10 @@ const META: Record<Notification['type'], { icon: string; color: Accent }> = {
|
|
|
88
88
|
// sealed). Not block-scoped; "act" drops the listed stale ciphertexts so they can be re-entered
|
|
89
89
|
// (or restore the previous key to recover them instead).
|
|
90
90
|
key_drift: { icon: 'i-lucide-key-round', color: 'error' },
|
|
91
|
+
// A configured infrastructure connection stopped answering its live probe. Not block-scoped and
|
|
92
|
+
// nothing to act on from here — the fix is on the provider's side, and the card clears itself when
|
|
93
|
+
// the reachability watcher sees it answer again; "act" just marks it read.
|
|
94
|
+
infra_unreachable: { icon: 'i-lucide-plug-zap', color: 'error' },
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
// Per-type primary-action label. An exhaustive Record keyed off the notification
|
|
@@ -114,6 +118,7 @@ const ACTION_KEYS: Record<Notification['type'], string> = {
|
|
|
114
118
|
platform_health: 'layout.notifications.action.platform_health',
|
|
115
119
|
budget_paused: 'layout.notifications.action.budget_paused',
|
|
116
120
|
key_drift: 'layout.notifications.action.key_drift',
|
|
121
|
+
infra_unreachable: 'layout.notifications.action.infra_unreachable',
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
/** The localized primary-action label for a notification (te()-guarded against a
|
|
@@ -43,6 +43,7 @@ const ROUTABLE = computed<{ type: NotificationType; label: string }[]>(() => [
|
|
|
43
43
|
{ type: 'pr_review_ready', label: t('slack.routable.pr_review_ready') },
|
|
44
44
|
{ type: 'initiative', label: t('slack.routable.initiative') },
|
|
45
45
|
{ type: 'platform_health', label: t('slack.routable.platform_health') },
|
|
46
|
+
{ type: 'infra_unreachable', label: t('slack.routable.infra_unreachable') },
|
|
46
47
|
])
|
|
47
48
|
|
|
48
49
|
/** Notification-role options for a mapped member (drives who gets @-mentioned). */
|
|
@@ -75,6 +76,7 @@ const routes = reactive<Record<NotificationType, SlackRoute>>({
|
|
|
75
76
|
budget_paused: { enabled: false, channel: '' },
|
|
76
77
|
// In-app only (not in ROUTABLE), but the map is exhaustive over the type.
|
|
77
78
|
key_drift: { enabled: false, channel: '' },
|
|
79
|
+
infra_unreachable: { enabled: false, channel: '' },
|
|
78
80
|
})
|
|
79
81
|
const mentionsEnabled = ref(false)
|
|
80
82
|
// Editable member rows carry a client-only stable `uid` (see `slackMemberMapping`) so
|
|
@@ -111,6 +111,12 @@ export function useWorkspaceStream() {
|
|
|
111
111
|
// inspector's "Test environment creation" control shows the live stage + final
|
|
112
112
|
// outcome in place without a refetch. No board block.
|
|
113
113
|
environmentTest.upsert(event.run)
|
|
114
|
+
} else if (event.type === 'infraSetup') {
|
|
115
|
+
// The reachability watcher found a configured infrastructure area dead (or answering again) —
|
|
116
|
+
// patch that one area so the setup banner appears/clears immediately. A full refresh would
|
|
117
|
+
// pay the whole snapshot aggregate for a one-field delta, and the projection the snapshot
|
|
118
|
+
// recomputes already folds the same recorded state.
|
|
119
|
+
workspace.patchInfraSetup(event.area, event.status, event.detail)
|
|
114
120
|
} else if (event.type === 'notification') {
|
|
115
121
|
// A PR needs a merge decision, a pipeline finished, or CI gave up — patch the
|
|
116
122
|
// inbox + per-block badge in place (resolved ones drop out of the inbox).
|
package/app/stores/ui/modals.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { ref } from 'vue'
|
|
|
2
2
|
import type { DocumentSourceKind, InfraSetupArea, TaskSourceKind } from '~/types/domain'
|
|
3
3
|
import type { InfrastructureTab, ProviderConnectionKind } from '~/types/providerConnections'
|
|
4
4
|
import type { PendingContext } from '~/composables/useContextLinking'
|
|
5
|
+
import {
|
|
6
|
+
infraSetupDismissalKey,
|
|
7
|
+
type InfraSetupCardKind,
|
|
8
|
+
type InfraSetupDismissalKey,
|
|
9
|
+
} from '~/utils/infraSetup'
|
|
5
10
|
import {
|
|
6
11
|
DEFAULT_PROVISION_DEEP_LINK_PARAM,
|
|
7
12
|
DEFAULT_PROVISION_DEEP_LINK_VALUE,
|
|
@@ -820,18 +825,35 @@ function createAiOnboardingModals() {
|
|
|
820
825
|
const aiSetupDismissed = ref(false)
|
|
821
826
|
const aiPresetDismissed = ref(false)
|
|
822
827
|
|
|
823
|
-
// Infra-setup banner: per-SESSION dismissals,
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
828
|
+
// Infra-setup banner: per-SESSION dismissals, cleared on workspace switch exactly like the
|
|
829
|
+
// AI-onboarding flags (a dismissal in one workspace must not suppress the independent prompt for
|
|
830
|
+
// another). The PERMANENT "don't notify me again" dismissal is per-USER and persists in
|
|
831
|
+
// localStorage from the banner component; this only covers "hide for now".
|
|
832
|
+
//
|
|
833
|
+
// Keyed by area AND KIND, not by area alone: the two cards an area can raise are different
|
|
834
|
+
// claims. Dismissing "you haven't configured this" for the session must not also silence the
|
|
835
|
+
// OUTAGE card that appears after the operator configures it and the provider then dies — the same
|
|
836
|
+
// asymmetry that makes the permanent dismissal setup-gap-only, one tier down.
|
|
837
|
+
const infraSetupSessionDismissed = ref<InfraSetupDismissalKey[]>([])
|
|
838
|
+
function dismissInfraSetupForSession(area: InfraSetupArea, kind: InfraSetupCardKind) {
|
|
839
|
+
const key = infraSetupDismissalKey(area, kind)
|
|
840
|
+
if (!infraSetupSessionDismissed.value.includes(key))
|
|
841
|
+
infraSetupSessionDismissed.value = [...infraSetupSessionDismissed.value, key]
|
|
831
842
|
}
|
|
832
843
|
function resetInfraSetupDismissals() {
|
|
833
844
|
infraSetupSessionDismissed.value = []
|
|
834
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Drop an area's OUTAGE session dismissal — called when that area RECOVERS, so a transient health
|
|
848
|
+
* state (`unreachable`) re-nags the next time it fails. Without it, "hide for now" on one outage
|
|
849
|
+
* would quietly cover every later outage for the rest of the session, which is precisely the
|
|
850
|
+
* semantics `isInfraSetupHealthStatus` exists to keep away from a health state. The area's
|
|
851
|
+
* setup-gap dismissal is left alone: recovery says nothing about that claim.
|
|
852
|
+
*/
|
|
853
|
+
function clearInfraSetupSessionDismissal(area: InfraSetupArea) {
|
|
854
|
+
const key = infraSetupDismissalKey(area, 'outage')
|
|
855
|
+
infraSetupSessionDismissed.value = infraSetupSessionDismissed.value.filter((k) => k !== key)
|
|
856
|
+
}
|
|
835
857
|
|
|
836
858
|
// Default-test-environment banner: a single per-SESSION dismissal, cleared on workspace switch
|
|
837
859
|
// like the flags above. There is deliberately no PERMANENT dismissal here (unlike the
|
|
@@ -889,6 +911,7 @@ function createAiOnboardingModals() {
|
|
|
889
911
|
infraSetupSessionDismissed,
|
|
890
912
|
dismissInfraSetupForSession,
|
|
891
913
|
resetInfraSetupDismissals,
|
|
914
|
+
clearInfraSetupSessionDismissal,
|
|
892
915
|
defaultProvisionDismissed,
|
|
893
916
|
dismissDefaultProvision,
|
|
894
917
|
resetDefaultProvisionDismissal,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { applyInfraSetupTransition, isInfraSetupHealthStatus } from '@cat-factory/contracts'
|
|
3
|
+
import type { InfraSetup, InfraSetupArea, InfraSetupStatus } from '~/types/domain'
|
|
4
|
+
|
|
5
|
+
// The workspace store's infra-setup slice: the per-area setup/health projection the setup banner
|
|
6
|
+
// renders, and the live `infraSetup` event patch the reachability watcher pushes into it. Its own
|
|
7
|
+
// collaborator rather than more lines in the store body, because it is the one slice with RULES
|
|
8
|
+
// (which prior state a probe verdict may overwrite, and what a recovery does to a dismissal) instead
|
|
9
|
+
// of a plain assign-from-snapshot.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create the infra-setup slice. `hydrate` takes the authoritative projection off a snapshot;
|
|
13
|
+
* `patchInfraSetup` applies one live transition.
|
|
14
|
+
*/
|
|
15
|
+
export function createInfraSetupState() {
|
|
16
|
+
/**
|
|
17
|
+
* Per-area infrastructure-setup status (ephemeral environments / agent executor / binary
|
|
18
|
+
* storage) from the snapshot, driving the infra-setup banner. Null on an older backend that
|
|
19
|
+
* doesn't compute it (⇒ no banner).
|
|
20
|
+
*/
|
|
21
|
+
const infraSetup = ref<InfraSetup | null>(null)
|
|
22
|
+
/**
|
|
23
|
+
* The failing probe's own operator-facing reason per area ("connect ECONNREFUSED …", "HTTP 401"),
|
|
24
|
+
* from the live `infraSetup` event only — a refused connection reads very differently from a
|
|
25
|
+
* rejected token, and it is the one thing on the banner that says WHY.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NOT on the snapshot: the reason varies between passes and the notification card is
|
|
28
|
+
* content-deduped, so persisting it there would re-toast the inbox for the whole outage. The
|
|
29
|
+
* consequence is that a reload mid-outage renders the banner with no reason line, which is honest
|
|
30
|
+
* (this session never saw the probe) and is why the reason is an ADDITION to the copy rather than
|
|
31
|
+
* a replacement for it.
|
|
32
|
+
*/
|
|
33
|
+
const infraSetupDetails = ref<Partial<Record<InfraSetupArea, string>>>({})
|
|
34
|
+
|
|
35
|
+
function hydrate(next: InfraSetup | null | undefined) {
|
|
36
|
+
infraSetup.value = next ?? null
|
|
37
|
+
// The reasons belong to the live pushes this session observed, so an authoritative snapshot
|
|
38
|
+
// supersedes them: keeping one would caption a freshly-read status with a stale probe.
|
|
39
|
+
infraSetupDetails.value = {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Patch ONE infra area's status from a live `infraSetup` event, so the setup banner appears (or
|
|
44
|
+
* clears) the moment the reachability watcher notices rather than on the next snapshot load.
|
|
45
|
+
*
|
|
46
|
+
* A targeted upsert, deliberately not a `refresh()`: this is a one-field delta on a projection
|
|
47
|
+
* the snapshot recomputes wholesale, and a coalesced full refresh here would pay the ~18-read
|
|
48
|
+
* aggregate for it. A no-op before the first snapshot has landed — `hydrate` is about to set the
|
|
49
|
+
* authoritative projection, which already carries the recorded outage.
|
|
50
|
+
*
|
|
51
|
+
* The write goes through contracts' `applyInfraSetupTransition`, the SAME rule the backend's
|
|
52
|
+
* snapshot fold uses, so live and reloaded state cannot disagree: only a `configured` area may
|
|
53
|
+
* become `unreachable`. Assigning unconditionally (as this once did) rendered a red "check that
|
|
54
|
+
* the service is running" banner over a `not_applicable`/`not_defined` area, which then vanished
|
|
55
|
+
* on the next reload — a banner that contradicts the projection is worse than a late one.
|
|
56
|
+
*
|
|
57
|
+
* Recovering an area also drops its OUTAGE session dismissal, so a health state re-nags when it
|
|
58
|
+
* recurs (see `isInfraSetupHealthStatus`): without this, dismissing one outage would silence the
|
|
59
|
+
* next one for the rest of the session.
|
|
60
|
+
*/
|
|
61
|
+
function patchInfraSetup(area: InfraSetupArea, status: InfraSetupStatus, detail?: string) {
|
|
62
|
+
const current = infraSetup.value
|
|
63
|
+
if (!current) return
|
|
64
|
+
const next = applyInfraSetupTransition(current, area, status)
|
|
65
|
+
// Refused by the shared rule (the projection out-ranks this probe), so nothing about the area
|
|
66
|
+
// changed and its reason must not be captioned onto a status it does not describe.
|
|
67
|
+
if (next === current) return
|
|
68
|
+
infraSetup.value = next
|
|
69
|
+
infraSetupDetails.value = { ...infraSetupDetails.value, [area]: detail }
|
|
70
|
+
// `useUiStore` is resolved through the auto-import at CALL time, as it was in the store body
|
|
71
|
+
// this moved out of: the ui store is only needed on a recovery, and reaching for it lazily keeps
|
|
72
|
+
// this slice constructible before pinia has that store (and stubbable in the store unit tests).
|
|
73
|
+
if (!isInfraSetupHealthStatus(status)) useUiStore().clearInfraSetupSessionDismissal(area)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { infraSetup, infraSetupDetails, hydrate, patchInfraSetup }
|
|
77
|
+
}
|
|
@@ -236,3 +236,132 @@ describe('workspace store cold-open speculative snapshot', () => {
|
|
|
236
236
|
expect(ws.access).toBeNull()
|
|
237
237
|
})
|
|
238
238
|
})
|
|
239
|
+
|
|
240
|
+
// The reachability watcher pushes ONE area's transition as an `infraSetup` event, which the stream
|
|
241
|
+
// applies through `patchInfraSetup`. Two properties matter and neither is visible from the backend:
|
|
242
|
+
// the patch is TARGETED (a full refresh here would pay the whole snapshot aggregate for a one-field
|
|
243
|
+
// delta), and recovering an area drops its SESSION dismissal so a health state re-nags when it
|
|
244
|
+
// recurs — without which dismissing one outage would silence every later one for the session.
|
|
245
|
+
describe('workspace store infraSetup patching', () => {
|
|
246
|
+
/** A ui-store stub recording which areas had their session dismissal cleared. */
|
|
247
|
+
function stubUiStore() {
|
|
248
|
+
const cleared: string[] = []
|
|
249
|
+
vi.stubGlobal('useUiStore', () => ({
|
|
250
|
+
clearInfraSetupSessionDismissal: (area: string) => cleared.push(area),
|
|
251
|
+
}))
|
|
252
|
+
return cleared
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function openBoard(infraSetup: Record<string, string>) {
|
|
256
|
+
const snap = {
|
|
257
|
+
...snapshot('ws1', [block('f1')]),
|
|
258
|
+
infraSetup,
|
|
259
|
+
} as unknown as WorkspaceSnapshot
|
|
260
|
+
const getWorkspace = vi.fn().mockResolvedValue(snap)
|
|
261
|
+
vi.stubGlobal('useApi', () => ({ getWorkspace }))
|
|
262
|
+
const ws = useWorkspaceStore()
|
|
263
|
+
await ws.switchTo('ws1')
|
|
264
|
+
return { ws, getWorkspace }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
it('patches one area without refetching the snapshot', async () => {
|
|
268
|
+
stubUiStore()
|
|
269
|
+
const { ws, getWorkspace } = await openBoard({
|
|
270
|
+
agentExecutor: 'configured',
|
|
271
|
+
ephemeralEnvironments: 'configured',
|
|
272
|
+
binaryStorage: 'not_defined',
|
|
273
|
+
})
|
|
274
|
+
ws.patchInfraSetup('agentExecutor', 'unreachable')
|
|
275
|
+
expect(ws.infraSetup).toEqual({
|
|
276
|
+
agentExecutor: 'unreachable',
|
|
277
|
+
ephemeralEnvironments: 'configured',
|
|
278
|
+
binaryStorage: 'not_defined',
|
|
279
|
+
})
|
|
280
|
+
// One fetch: the initial switchTo. The live patch triggered no snapshot refresh.
|
|
281
|
+
expect(getWorkspace).toHaveBeenCalledTimes(1)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('clears the area session dismissal on RECOVERY so the next outage re-nags', async () => {
|
|
285
|
+
const cleared = stubUiStore()
|
|
286
|
+
const { ws } = await openBoard({
|
|
287
|
+
agentExecutor: 'unreachable',
|
|
288
|
+
ephemeralEnvironments: 'configured',
|
|
289
|
+
binaryStorage: 'configured',
|
|
290
|
+
})
|
|
291
|
+
ws.patchInfraSetup('agentExecutor', 'configured')
|
|
292
|
+
expect(ws.infraSetup?.agentExecutor).toBe('configured')
|
|
293
|
+
expect(cleared).toEqual(['agentExecutor'])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('leaves the session dismissal alone while the area is still unreachable', async () => {
|
|
297
|
+
const cleared = stubUiStore()
|
|
298
|
+
const { ws } = await openBoard({
|
|
299
|
+
agentExecutor: 'configured',
|
|
300
|
+
ephemeralEnvironments: 'configured',
|
|
301
|
+
binaryStorage: 'configured',
|
|
302
|
+
})
|
|
303
|
+
ws.patchInfraSetup('agentExecutor', 'unreachable')
|
|
304
|
+
expect(cleared).toEqual([])
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('is a no-op before the first snapshot has landed', () => {
|
|
308
|
+
// An event can arrive before the projection exists; `hydrate` is about to set the authoritative
|
|
309
|
+
// one (which already folds the recorded outage), so inventing a partial projection here would
|
|
310
|
+
// render a banner from a single field with the other areas unknown.
|
|
311
|
+
stubUiStore()
|
|
312
|
+
const ws = useWorkspaceStore()
|
|
313
|
+
ws.patchInfraSetup('agentExecutor', 'unreachable')
|
|
314
|
+
expect(ws.infraSetup).toBeNull()
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('refuses to mark an area unreachable that the projection does not call configured', async () => {
|
|
318
|
+
// The live patch honours the SAME rule as the backend's snapshot fold
|
|
319
|
+
// (`applyInfraSetupTransition`). Assigning unconditionally rendered a red "check that the
|
|
320
|
+
// service is running" banner over a `not_applicable`/`not_defined` area — which then vanished on
|
|
321
|
+
// the next reload, because the projection out-ranks the probe. A banner that contradicts the
|
|
322
|
+
// projection is worse than a late one.
|
|
323
|
+
stubUiStore()
|
|
324
|
+
const { ws } = await openBoard({
|
|
325
|
+
agentExecutor: 'not_applicable',
|
|
326
|
+
ephemeralEnvironments: 'not_defined',
|
|
327
|
+
binaryStorage: 'configured',
|
|
328
|
+
})
|
|
329
|
+
ws.patchInfraSetup('agentExecutor', 'unreachable')
|
|
330
|
+
ws.patchInfraSetup('ephemeralEnvironments', 'unreachable')
|
|
331
|
+
expect(ws.infraSetup?.agentExecutor).toBe('not_applicable')
|
|
332
|
+
expect(ws.infraSetup?.ephemeralEnvironments).toBe('not_defined')
|
|
333
|
+
// A refused patch must not caption its reason onto a status it does not describe.
|
|
334
|
+
expect(ws.infraSetupDetails).toEqual({})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('keeps the probe reason for the banner, and drops it on recovery', async () => {
|
|
338
|
+
// The reason is the one thing on the banner that says WHY (a refused connection reads very
|
|
339
|
+
// differently from a rejected token), and it rides the live event only — the notification card
|
|
340
|
+
// is content-deduped, so persisting it there would re-toast the inbox for the whole outage.
|
|
341
|
+
stubUiStore()
|
|
342
|
+
const { ws } = await openBoard({
|
|
343
|
+
agentExecutor: 'configured',
|
|
344
|
+
ephemeralEnvironments: 'configured',
|
|
345
|
+
binaryStorage: 'configured',
|
|
346
|
+
})
|
|
347
|
+
ws.patchInfraSetup('agentExecutor', 'unreachable', 'connect ECONNREFUSED 10.0.0.4:6443')
|
|
348
|
+
expect(ws.infraSetupDetails.agentExecutor).toBe('connect ECONNREFUSED 10.0.0.4:6443')
|
|
349
|
+
ws.patchInfraSetup('agentExecutor', 'configured')
|
|
350
|
+
expect(ws.infraSetupDetails.agentExecutor).toBeUndefined()
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('drops live probe reasons when an authoritative snapshot lands', async () => {
|
|
354
|
+
stubUiStore()
|
|
355
|
+
const { ws } = await openBoard({
|
|
356
|
+
agentExecutor: 'configured',
|
|
357
|
+
ephemeralEnvironments: 'configured',
|
|
358
|
+
binaryStorage: 'configured',
|
|
359
|
+
})
|
|
360
|
+
ws.patchInfraSetup('agentExecutor', 'unreachable', 'HTTP 502')
|
|
361
|
+
expect(ws.infraSetupDetails.agentExecutor).toBe('HTTP 502')
|
|
362
|
+
await ws.refresh()
|
|
363
|
+
// A reload mid-outage renders the banner with no reason line, which is honest: this session
|
|
364
|
+
// never saw the probe, and captioning a fresh status with a stale reason would not be.
|
|
365
|
+
expect(ws.infraSetupDetails).toEqual({})
|
|
366
|
+
})
|
|
367
|
+
})
|
package/app/stores/workspace.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { defineStore } from 'pinia'
|
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
3
|
import type {
|
|
4
4
|
BudgetCaps,
|
|
5
|
-
InfraSetup,
|
|
6
5
|
SpendStatus,
|
|
7
6
|
WorkspaceAccess,
|
|
8
7
|
WorkspaceListItem,
|
|
@@ -11,6 +10,7 @@ import type {
|
|
|
11
10
|
import { useAccountsStore } from '~/stores/accounts'
|
|
12
11
|
import { useBoardStore } from '~/stores/board'
|
|
13
12
|
import { applySnapshotToStores, resetPerBoardCaches } from '~/stores/workspace/hydrate'
|
|
13
|
+
import { createInfraSetupState } from '~/stores/workspace/infraSetup'
|
|
14
14
|
import { markBoot } from '~/utils/bootMarks'
|
|
15
15
|
import { retryWhileBackendUnreachable } from '~/utils/backendReady'
|
|
16
16
|
|
|
@@ -50,12 +50,14 @@ export const useWorkspaceStore = defineStore(
|
|
|
50
50
|
const userSpend = ref<SpendStatus | null>(null)
|
|
51
51
|
/** Operator hard ceilings on the account/user budget tiers (null until first load). */
|
|
52
52
|
const budgetCaps = ref<BudgetCaps | null>(null)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
// The infra-setup slice (the banner's projection + the live reachability patch), extracted
|
|
54
|
+
// because it is the one slice with RULES rather than a plain assign-from-snapshot.
|
|
55
|
+
const {
|
|
56
|
+
infraSetup,
|
|
57
|
+
infraSetupDetails,
|
|
58
|
+
hydrate: hydrateInfraSetup,
|
|
59
|
+
patchInfraSetup,
|
|
60
|
+
} = createInfraSetupState()
|
|
59
61
|
/**
|
|
60
62
|
* The signed-in caller's resolved workspace-RBAC access to the ACTIVE board — their
|
|
61
63
|
* effective role + the permission set it grants, from the auth gate's resolution
|
|
@@ -92,7 +94,7 @@ export const useWorkspaceStore = defineStore(
|
|
|
92
94
|
accountSpend.value = snapshot.accountSpend ?? null
|
|
93
95
|
userSpend.value = snapshot.userSpend ?? null
|
|
94
96
|
budgetCaps.value = snapshot.budgetCaps ?? null
|
|
95
|
-
|
|
97
|
+
hydrateInfraSetup(snapshot.infraSetup)
|
|
96
98
|
access.value = snapshot.access ?? null
|
|
97
99
|
// Keep the board list in step (e.g. a freshly created board, or a rename). The
|
|
98
100
|
// snapshot's `workspace` carries no `viewerRole` (that's a `GET /workspaces` list
|
|
@@ -283,6 +285,8 @@ export const useWorkspaceStore = defineStore(
|
|
|
283
285
|
userSpend,
|
|
284
286
|
budgetCaps,
|
|
285
287
|
infraSetup,
|
|
288
|
+
infraSetupDetails,
|
|
289
|
+
patchInfraSetup,
|
|
286
290
|
access,
|
|
287
291
|
init,
|
|
288
292
|
switchTo,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { InfraSetupArea } from '~/types/domain'
|
|
2
|
+
|
|
3
|
+
// Shared vocabulary for the infra-setup banner's two card kinds, used by the banner itself and by
|
|
4
|
+
// the ui store's session-dismissal book-keeping.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Which CLAIM an infra-setup card is making about an area. They share one banner surface but they
|
|
8
|
+
* are not interchangeable, and every dismissal rule keys off the difference:
|
|
9
|
+
* - `setup` — "you never configured this". A stable operator decision, so it may be dismissed
|
|
10
|
+
* permanently as well as for the session.
|
|
11
|
+
* - `outage` — "you DID configure it, and a live probe cannot reach it". A health state, so it is
|
|
12
|
+
* session-dismissible only and must re-nag on recurrence.
|
|
13
|
+
*/
|
|
14
|
+
export type InfraSetupCardKind = 'setup' | 'outage'
|
|
15
|
+
|
|
16
|
+
/** A session-dismissal key: one area's one KIND of card. */
|
|
17
|
+
export type InfraSetupDismissalKey = `${InfraSetupArea}:${InfraSetupCardKind}`
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The session-dismissal key for one area's card kind. A composite key rather than a bare area,
|
|
21
|
+
* because dismissing the setup nag must not silence the outage card that a later failure raises for
|
|
22
|
+
* the same area — a different claim about a different state.
|
|
23
|
+
*/
|
|
24
|
+
export function infraSetupDismissalKey(
|
|
25
|
+
area: InfraSetupArea,
|
|
26
|
+
kind: InfraSetupCardKind,
|
|
27
|
+
): InfraSetupDismissalKey {
|
|
28
|
+
return `${area}:${kind}`
|
|
29
|
+
}
|
package/i18n/locales/de.json
CHANGED
|
@@ -1979,6 +1979,7 @@
|
|
|
1979
1979
|
"clarity_review": "Als gelesen markieren",
|
|
1980
1980
|
"release_regression": "Bestätigen",
|
|
1981
1981
|
"platform_health": "Bestätigen",
|
|
1982
|
+
"infra_unreachable": "Bestätigen",
|
|
1982
1983
|
"decision_required": "Als gelesen markieren",
|
|
1983
1984
|
"human_test_ready": "Als gelesen markieren",
|
|
1984
1985
|
"visual_confirmation_ready": "Als gelesen markieren",
|
|
@@ -2027,11 +2028,13 @@
|
|
|
2027
2028
|
"infraSetupBanner": {
|
|
2028
2029
|
"agentExecutor": {
|
|
2029
2030
|
"title": "Agent-Executor nicht konfiguriert",
|
|
2031
|
+
"unreachableTitle": "Agent-Executor ist nicht erreichbar",
|
|
2030
2032
|
"body": "Dieses Deployment führt Agenten auf einem selbst gehosteten Runner-Pool aus, aber es ist keiner registriert, sodass kein Agent laufen kann, bis Sie einen verbinden.",
|
|
2031
2033
|
"action": "Runner-Pool konfigurieren"
|
|
2032
2034
|
},
|
|
2033
2035
|
"ephemeralEnvironments": {
|
|
2034
2036
|
"title": "Testumgebung nicht konfiguriert",
|
|
2037
|
+
"unreachableTitle": "Anbieter für Testumgebungen ist nicht erreichbar",
|
|
2035
2038
|
"body": "Es ist kein Anbieter für ephemere Umgebungen registriert, sodass Test-Agenten, die eine aktive Vorschau-Umgebung benötigen, nicht laufen können. Öffnen Sie unten „Testumgebungen“ und verbinden Sie einen Kubernetes-Cluster oder einen benutzerdefinierten HTTP-Umgebungsanbieter, um sie zu aktivieren.",
|
|
2036
2039
|
"action": "Environment konfigurieren"
|
|
2037
2040
|
},
|
|
@@ -2043,6 +2046,11 @@
|
|
|
2043
2046
|
"dismiss": {
|
|
2044
2047
|
"session": "Für diese Sitzung verwerfen",
|
|
2045
2048
|
"permanent": "Ich bin mit den Einschränkungen einverstanden, nicht erneut benachrichtigen"
|
|
2049
|
+
},
|
|
2050
|
+
"unreachable": {
|
|
2051
|
+
"body": "Die Verbindung ist eingerichtet, aber eine Live-Prüfung konnte sie gerade nicht erreichen. Damit ist das ein Ausfall und keine fehlende Einrichtung: Agenten, die darauf angewiesen sind, schlagen fehl, bis wieder eine Antwort kommt. Prüfen Sie, ob der Dienst läuft, und testen Sie die Verbindung dann erneut.",
|
|
2052
|
+
"reason": "Letzte Prüfung: {detail}",
|
|
2053
|
+
"action": "Verbindung prüfen"
|
|
2046
2054
|
}
|
|
2047
2055
|
},
|
|
2048
2056
|
"spendWarningBanner": {
|
|
@@ -4737,6 +4745,7 @@
|
|
|
4737
4745
|
},
|
|
4738
4746
|
"routable": {
|
|
4739
4747
|
"platform_health": "Plattformzustand",
|
|
4748
|
+
"infra_unreachable": "Infrastrukturausfälle",
|
|
4740
4749
|
"merge_review": "Merge-Review",
|
|
4741
4750
|
"pipeline_complete": "Pipeline abgeschlossen",
|
|
4742
4751
|
"ci_failed": "CI fehlgeschlagen",
|
package/i18n/locales/en.json
CHANGED
|
@@ -1962,6 +1962,7 @@
|
|
|
1962
1962
|
"clarity_review": "Mark read",
|
|
1963
1963
|
"release_regression": "Acknowledge",
|
|
1964
1964
|
"platform_health": "Acknowledge",
|
|
1965
|
+
"infra_unreachable": "Acknowledge",
|
|
1965
1966
|
"decision_required": "Mark read",
|
|
1966
1967
|
"human_test_ready": "Mark read",
|
|
1967
1968
|
"visual_confirmation_ready": "Mark read",
|
|
@@ -2010,11 +2011,13 @@
|
|
|
2010
2011
|
"infraSetupBanner": {
|
|
2011
2012
|
"agentExecutor": {
|
|
2012
2013
|
"title": "Agent executor not configured",
|
|
2014
|
+
"unreachableTitle": "Agent executor is unreachable",
|
|
2013
2015
|
"body": "This deployment runs agents on a self-hosted runner pool, but none is registered, so no agent can run until you connect one.",
|
|
2014
2016
|
"action": "Configure runner pool"
|
|
2015
2017
|
},
|
|
2016
2018
|
"ephemeralEnvironments": {
|
|
2017
2019
|
"title": "Test environment not configured",
|
|
2020
|
+
"unreachableTitle": "Test environment provider is unreachable",
|
|
2018
2021
|
"body": "No ephemeral environment provider is registered, so testing agents that need a live preview environment can't run. Open Test environments below and connect a Kubernetes cluster or a custom HTTP environment provider to enable them.",
|
|
2019
2022
|
"action": "Configure environment"
|
|
2020
2023
|
},
|
|
@@ -2026,6 +2029,11 @@
|
|
|
2026
2029
|
"dismiss": {
|
|
2027
2030
|
"session": "Dismiss for this session",
|
|
2028
2031
|
"permanent": "I'm OK with the limitations, don't notify me again"
|
|
2032
|
+
},
|
|
2033
|
+
"unreachable": {
|
|
2034
|
+
"body": "The connection is set up, but a live check could not reach it just now. That makes this an outage rather than a missing setup: agents that depend on it will fail until it answers again. Check that the service is running, then retest the connection.",
|
|
2035
|
+
"reason": "Last probe: {detail}",
|
|
2036
|
+
"action": "Check connection"
|
|
2029
2037
|
}
|
|
2030
2038
|
},
|
|
2031
2039
|
"spendWarningBanner": {
|
|
@@ -3695,6 +3703,7 @@
|
|
|
3695
3703
|
},
|
|
3696
3704
|
"routable": {
|
|
3697
3705
|
"platform_health": "Platform health",
|
|
3706
|
+
"infra_unreachable": "Infrastructure outages",
|
|
3698
3707
|
"merge_review": "Merge review",
|
|
3699
3708
|
"pipeline_complete": "Pipeline complete",
|
|
3700
3709
|
"ci_failed": "CI failed",
|
package/i18n/locales/es.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "Marcar como leída",
|
|
1887
1887
|
"release_regression": "Confirmar recepción",
|
|
1888
1888
|
"platform_health": "Confirmar recepción",
|
|
1889
|
+
"infra_unreachable": "Confirmar",
|
|
1889
1890
|
"decision_required": "Marcar como leída",
|
|
1890
1891
|
"human_test_ready": "Marcar como leída",
|
|
1891
1892
|
"visual_confirmation_ready": "Marcar como leída",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "Ejecutor de agentes no configurado",
|
|
1944
|
+
"unreachableTitle": "El ejecutor de agentes no está accesible",
|
|
1943
1945
|
"body": "Este despliegue ejecuta los agentes en un pool de runners autoalojado, pero no hay ninguno registrado, así que ningún agente podrá ejecutarse hasta que conectes uno.",
|
|
1944
1946
|
"action": "Configurar pool de runners"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "Entorno de pruebas no configurado",
|
|
1950
|
+
"unreachableTitle": "El proveedor de entornos de prueba no está accesible",
|
|
1948
1951
|
"body": "No hay ningún proveedor de entornos efímeros registrado, así que los agentes de pruebas que necesitan un entorno de vista previa activo no pueden ejecutarse. Abre Entornos de prueba abajo y conecta un clúster de Kubernetes o un proveedor de entornos HTTP personalizado para habilitarlos.",
|
|
1949
1952
|
"action": "Configurar entorno"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "Descartar durante esta sesión",
|
|
1958
1961
|
"permanent": "Acepto las limitaciones, no volver a avisarme"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "La conexión está configurada, pero una comprobación en vivo no ha podido alcanzarla ahora mismo. Por eso se trata de una interrupción y no de una configuración pendiente: los agentes que dependen de ella fallarán hasta que vuelva a responder. Comprueba que el servicio esté en marcha y vuelve a probar la conexión.",
|
|
1965
|
+
"reason": "Última comprobación: {detail}",
|
|
1966
|
+
"action": "Comprobar conexión"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3586,6 +3594,7 @@
|
|
|
3586
3594
|
},
|
|
3587
3595
|
"routable": {
|
|
3588
3596
|
"platform_health": "Estado de la plataforma",
|
|
3597
|
+
"infra_unreachable": "Interrupciones de infraestructura",
|
|
3589
3598
|
"merge_review": "Revision de fusion",
|
|
3590
3599
|
"pipeline_complete": "Pipeline completado",
|
|
3591
3600
|
"ci_failed": "CI fallido",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "Marquer comme lu",
|
|
1887
1887
|
"release_regression": "Accuser réception",
|
|
1888
1888
|
"platform_health": "Accuser réception",
|
|
1889
|
+
"infra_unreachable": "Accuser réception",
|
|
1889
1890
|
"decision_required": "Marquer comme lu",
|
|
1890
1891
|
"human_test_ready": "Marquer comme lu",
|
|
1891
1892
|
"visual_confirmation_ready": "Marquer comme lu",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "Exécuteur d'agents non configuré",
|
|
1944
|
+
"unreachableTitle": "L'exécuteur d'agents est inaccessible",
|
|
1943
1945
|
"body": "Ce déploiement exécute les agents sur un pool de runners auto-hébergé, mais aucun n'est enregistré, donc aucun agent ne pourra s'exécuter tant que vous n'en connectez pas un.",
|
|
1944
1946
|
"action": "Configurer le pool de runners"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "Environnement de test non configuré",
|
|
1950
|
+
"unreachableTitle": "Le fournisseur d'environnements de test est inaccessible",
|
|
1948
1951
|
"body": "Aucun fournisseur d'environnements éphémères n'est enregistré, donc les agents de test qui nécessitent un environnement de prévisualisation actif ne peuvent pas s'exécuter. Ouvrez Environnements de test ci-dessous et connectez un cluster Kubernetes ou un fournisseur d'environnements HTTP personnalisé pour les activer.",
|
|
1949
1952
|
"action": "Configurer l'environnement"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "Ignorer pour cette session",
|
|
1958
1961
|
"permanent": "J'accepte les limitations, ne plus me notifier"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "La connexion est configurée, mais une vérification en direct n'a pas pu l'atteindre à l'instant. Il s'agit donc d'une panne et non d'une configuration manquante : les agents qui en dépendent échoueront jusqu'à ce qu'une réponse revienne. Vérifiez que le service fonctionne, puis testez la connexion à nouveau.",
|
|
1965
|
+
"reason": "Dernière vérification : {detail}",
|
|
1966
|
+
"action": "Vérifier la connexion"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3586,6 +3594,7 @@
|
|
|
3586
3594
|
},
|
|
3587
3595
|
"routable": {
|
|
3588
3596
|
"platform_health": "Santé de la plateforme",
|
|
3597
|
+
"infra_unreachable": "Pannes d'infrastructure",
|
|
3589
3598
|
"merge_review": "Revue de fusion",
|
|
3590
3599
|
"pipeline_complete": "Pipeline termine",
|
|
3591
3600
|
"ci_failed": "Echec de CI",
|
package/i18n/locales/he.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "סמן כנקרא",
|
|
1887
1887
|
"release_regression": "אשר",
|
|
1888
1888
|
"platform_health": "אשר",
|
|
1889
|
+
"infra_unreachable": "אישור",
|
|
1889
1890
|
"decision_required": "סמן כנקרא",
|
|
1890
1891
|
"human_test_ready": "סמן כנקרא",
|
|
1891
1892
|
"visual_confirmation_ready": "סמן כנקרא",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "מנוע הסוכנים אינו מוגדר",
|
|
1944
|
+
"unreachableTitle": "מריץ הסוכנים אינו נגיש",
|
|
1943
1945
|
"body": "פריסה זו מריצה סוכנים במאגר ראנרים בניהול עצמי, אך אף מאגר אינו רשום, ולכן אף סוכן לא יוכל לפעול עד שתחברו אחד.",
|
|
1944
1946
|
"action": "הגדרת מאגר ראנרים"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "סביבת בדיקות אינה מוגדרת",
|
|
1950
|
+
"unreachableTitle": "ספק סביבות הבדיקה אינו נגיש",
|
|
1948
1951
|
"body": "לא רשום שום ספק סביבות ארעיות, ולכן סוכני בדיקה הזקוקים לסביבת תצוגה מקדימה פעילה אינם יכולים לפעול. פתחו את 'סביבות בדיקה' למטה וחברו אשכול Kubernetes או ספק סביבות HTTP מותאם אישית כדי להפעיל אותם.",
|
|
1949
1952
|
"action": "הגדרת סביבה"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "התעלם להפעלה זו",
|
|
1958
1961
|
"permanent": "אני מסכים למגבלות, אל תודיעו לי שוב"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "החיבור מוגדר, אך בדיקה חיה לא הצליחה להגיע אליו כרגע. לכן מדובר בתקלה ולא בהגדרה חסרה: סוכנים שתלויים בו ייכשלו עד שתחזור תשובה. בדקו שהשירות פועל, ולאחר מכן בדקו את החיבור מחדש.",
|
|
1965
|
+
"reason": "בדיקה אחרונה: {detail}",
|
|
1966
|
+
"action": "בדיקת החיבור"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3597,6 +3605,7 @@
|
|
|
3597
3605
|
},
|
|
3598
3606
|
"routable": {
|
|
3599
3607
|
"platform_health": "תקינות הפלטפורמה",
|
|
3608
|
+
"infra_unreachable": "תקלות תשתית",
|
|
3600
3609
|
"merge_review": "סקירת מיזוג",
|
|
3601
3610
|
"pipeline_complete": "הצינור הושלם",
|
|
3602
3611
|
"ci_failed": "CI נכשל",
|
package/i18n/locales/it.json
CHANGED
|
@@ -1979,6 +1979,7 @@
|
|
|
1979
1979
|
"clarity_review": "Segna come letto",
|
|
1980
1980
|
"release_regression": "Conferma presa visione",
|
|
1981
1981
|
"platform_health": "Conferma presa visione",
|
|
1982
|
+
"infra_unreachable": "Conferma",
|
|
1982
1983
|
"decision_required": "Segna come letto",
|
|
1983
1984
|
"human_test_ready": "Segna come letto",
|
|
1984
1985
|
"visual_confirmation_ready": "Segna come letto",
|
|
@@ -2027,11 +2028,13 @@
|
|
|
2027
2028
|
"infraSetupBanner": {
|
|
2028
2029
|
"agentExecutor": {
|
|
2029
2030
|
"title": "Esecutore degli agenti non configurato",
|
|
2031
|
+
"unreachableTitle": "L'esecutore degli agenti non è raggiungibile",
|
|
2030
2032
|
"body": "Questo deployment esegue gli agenti su un runner pool self-hosted, ma nessuno è registrato, quindi nessun agente può funzionare finché non ne connetti uno.",
|
|
2031
2033
|
"action": "Configura il runner pool"
|
|
2032
2034
|
},
|
|
2033
2035
|
"ephemeralEnvironments": {
|
|
2034
2036
|
"title": "Ambiente di test non configurato",
|
|
2037
|
+
"unreachableTitle": "Il provider di ambienti di test non è raggiungibile",
|
|
2035
2038
|
"body": "Nessun provider di ambiente effimero è registrato, quindi gli agenti di test che necessitano di un ambiente di anteprima live non possono funzionare. Apri Ambienti di test qui sotto e connetti un cluster Kubernetes o un provider di ambienti HTTP personalizzato per abilitarli.",
|
|
2036
2039
|
"action": "Configura l'ambiente"
|
|
2037
2040
|
},
|
|
@@ -2043,6 +2046,11 @@
|
|
|
2043
2046
|
"dismiss": {
|
|
2044
2047
|
"session": "Ignora per questa sessione",
|
|
2045
2048
|
"permanent": "Accetto le limitazioni, non notificarmi più"
|
|
2049
|
+
},
|
|
2050
|
+
"unreachable": {
|
|
2051
|
+
"body": "La connessione è configurata, ma una verifica in tempo reale non è riuscita a raggiungerla in questo momento. Si tratta quindi di un'interruzione e non di una configurazione mancante: gli agenti che ne dipendono falliranno finché non arriverà di nuovo una risposta. Controlla che il servizio sia attivo, poi riprova la connessione.",
|
|
2052
|
+
"reason": "Ultima verifica: {detail}",
|
|
2053
|
+
"action": "Verifica connessione"
|
|
2046
2054
|
}
|
|
2047
2055
|
},
|
|
2048
2056
|
"spendWarningBanner": {
|
|
@@ -4737,6 +4745,7 @@
|
|
|
4737
4745
|
},
|
|
4738
4746
|
"routable": {
|
|
4739
4747
|
"platform_health": "Salute della piattaforma",
|
|
4748
|
+
"infra_unreachable": "Interruzioni dell’infrastruttura",
|
|
4740
4749
|
"merge_review": "Revisione del merge",
|
|
4741
4750
|
"pipeline_complete": "Pipeline completata",
|
|
4742
4751
|
"ci_failed": "CI non riuscita",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "既読にする",
|
|
1887
1887
|
"release_regression": "確認",
|
|
1888
1888
|
"platform_health": "確認",
|
|
1889
|
+
"infra_unreachable": "確認",
|
|
1889
1890
|
"decision_required": "既読にする",
|
|
1890
1891
|
"human_test_ready": "既読にする",
|
|
1891
1892
|
"visual_confirmation_ready": "既読にする",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "エージェント実行環境が未設定です",
|
|
1944
|
+
"unreachableTitle": "エージェント実行環境に到達できません",
|
|
1943
1945
|
"body": "このデプロイはセルフホストのランナープールでエージェントを実行しますが、登録されているものがないため、接続するまでエージェントを実行できません。",
|
|
1944
1946
|
"action": "ランナープールを設定"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "テスト環境が未設定です",
|
|
1950
|
+
"unreachableTitle": "テスト環境プロバイダーに到達できません",
|
|
1948
1951
|
"body": "エフェメラル環境プロバイダーが登録されていないため、稼働中のプレビュー環境を必要とするテストエージェントを実行できません。下の「テスト環境」を開き、Kubernetes クラスターまたはカスタム HTTP 環境プロバイダーを接続して有効にしてください。",
|
|
1949
1952
|
"action": "環境を設定"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "このセッションでは非表示にする",
|
|
1958
1961
|
"permanent": "制限を理解しました。今後通知しない"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "接続は設定済みですが、ライブチェックでは到達できませんでした。つまり設定漏れではなく障害です。応答が戻るまで、これに依存するエージェントは失敗します。サービスが稼働しているか確認したうえで、接続を再テストしてください。",
|
|
1965
|
+
"reason": "最後のチェック: {detail}",
|
|
1966
|
+
"action": "接続を確認"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3598,6 +3606,7 @@
|
|
|
3598
3606
|
},
|
|
3599
3607
|
"routable": {
|
|
3600
3608
|
"platform_health": "プラットフォームの健全性",
|
|
3609
|
+
"infra_unreachable": "インフラ障害",
|
|
3601
3610
|
"merge_review": "マージレビュー",
|
|
3602
3611
|
"pipeline_complete": "パイプライン完了",
|
|
3603
3612
|
"ci_failed": "CI失敗",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "Oznacz jako przeczytane",
|
|
1887
1887
|
"release_regression": "Potwierdź",
|
|
1888
1888
|
"platform_health": "Potwierdź",
|
|
1889
|
+
"infra_unreachable": "Potwierdź",
|
|
1889
1890
|
"decision_required": "Oznacz jako przeczytane",
|
|
1890
1891
|
"human_test_ready": "Oznacz jako przeczytane",
|
|
1891
1892
|
"visual_confirmation_ready": "Oznacz jako przeczytane",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "Wykonawca agentów nie jest skonfigurowany",
|
|
1944
|
+
"unreachableTitle": "Wykonawca agentów jest nieosiągalny",
|
|
1943
1945
|
"body": "To wdrożenie uruchamia agentów w samodzielnie hostowanej puli runnerów, ale żadna nie jest zarejestrowana, więc żaden agent nie będzie mógł działać, dopóki jej nie podłączysz.",
|
|
1944
1946
|
"action": "Skonfiguruj pulę runnerów"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "Środowisko testowe nie jest skonfigurowane",
|
|
1950
|
+
"unreachableTitle": "Dostawca środowisk testowych jest nieosiągalny",
|
|
1948
1951
|
"body": "Nie zarejestrowano żadnego dostawcy środowisk efemerycznych, więc agenci testowi wymagający działającego środowiska podglądu nie mogą działać. Otwórz poniżej „Środowiska testowe” i podłącz klaster Kubernetes lub niestandardowego dostawcę środowisk HTTP, aby je włączyć.",
|
|
1949
1952
|
"action": "Skonfiguruj środowisko"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "Odrzuć na tę sesję",
|
|
1958
1961
|
"permanent": "Akceptuję ograniczenia, nie powiadamiaj mnie ponownie"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "Połączenie jest skonfigurowane, ale kontrola na żywo nie mogła go teraz uzyskać. To awaria, a nie brak konfiguracji: agenci, którzy od tego zależą, będą kończyć się błędem, dopóki nie pojawi się znowu odpowiedź. Sprawdź, czy usługa działa, a następnie ponownie przetestuj połączenie.",
|
|
1965
|
+
"reason": "Ostatnia kontrola: {detail}",
|
|
1966
|
+
"action": "Sprawdź połączenie"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3586,6 +3594,7 @@
|
|
|
3586
3594
|
},
|
|
3587
3595
|
"routable": {
|
|
3588
3596
|
"platform_health": "Kondycja platformy",
|
|
3597
|
+
"infra_unreachable": "Awarie infrastruktury",
|
|
3589
3598
|
"merge_review": "Przeglad scalenia",
|
|
3590
3599
|
"pipeline_complete": "Pipeline ukonczony",
|
|
3591
3600
|
"ci_failed": "Niepowodzenie CI",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "Okundu olarak işaretle",
|
|
1887
1887
|
"release_regression": "Onayla",
|
|
1888
1888
|
"platform_health": "Onayla",
|
|
1889
|
+
"infra_unreachable": "Onayla",
|
|
1889
1890
|
"decision_required": "Okundu işaretle",
|
|
1890
1891
|
"human_test_ready": "Okundu işaretle",
|
|
1891
1892
|
"visual_confirmation_ready": "Okundu işaretle",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "Aracı yürütücüsü yapılandırılmadı",
|
|
1944
|
+
"unreachableTitle": "Aracı yürütücüsüne erişilemiyor",
|
|
1943
1945
|
"body": "Bu dağıtım aracıları kendi barındırdığınız bir runner havuzunda çalıştırır, ancak kayıtlı bir havuz yok; bu yüzden bir tane bağlayana kadar hiçbir aracı çalışamaz.",
|
|
1944
1946
|
"action": "Runner havuzunu yapılandır"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "Test ortamı yapılandırılmadı",
|
|
1950
|
+
"unreachableTitle": "Test ortamı sağlayıcısına erişilemiyor",
|
|
1948
1951
|
"body": "Kayıtlı bir geçici ortam sağlayıcısı yok; bu nedenle çalışan bir önizleme ortamına ihtiyaç duyan test aracıları çalışamaz. Aşağıdaki Test ortamları sekmesini açın ve etkinleştirmek için bir Kubernetes kümesi ya da özel bir HTTP ortam sağlayıcısı bağlayın.",
|
|
1949
1952
|
"action": "Ortamı yapılandır"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "Bu oturum için kapat",
|
|
1958
1961
|
"permanent": "Kısıtlamaları kabul ediyorum, beni tekrar bilgilendirme"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "Bağlantı yapılandırılmış durumda, ancak canlı bir denetim şu anda ona ulaşamadı. Bu yüzden eksik bir yapılandırma değil bir kesinti söz konusu: buna bağlı aracılar yeniden yanıt gelene kadar başarısız olacak. Hizmetin çalıştığını doğrulayın, ardından bağlantıyı yeniden test edin.",
|
|
1965
|
+
"reason": "Son denetim: {detail}",
|
|
1966
|
+
"action": "Bağlantıyı denetle"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3598,6 +3606,7 @@
|
|
|
3598
3606
|
},
|
|
3599
3607
|
"routable": {
|
|
3600
3608
|
"platform_health": "Platform sağlığı",
|
|
3609
|
+
"infra_unreachable": "Altyapı kesintileri",
|
|
3601
3610
|
"merge_review": "Birleştirme incelemesi",
|
|
3602
3611
|
"pipeline_complete": "Pipeline tamamlandı",
|
|
3603
3612
|
"ci_failed": "CI başarısız",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -1886,6 +1886,7 @@
|
|
|
1886
1886
|
"clarity_review": "Позначити прочитаним",
|
|
1887
1887
|
"release_regression": "Підтвердити отримання",
|
|
1888
1888
|
"platform_health": "Підтвердити отримання",
|
|
1889
|
+
"infra_unreachable": "Підтвердити",
|
|
1889
1890
|
"decision_required": "Позначити прочитаним",
|
|
1890
1891
|
"human_test_ready": "Позначити прочитаним",
|
|
1891
1892
|
"visual_confirmation_ready": "Позначити прочитаним",
|
|
@@ -1940,11 +1941,13 @@
|
|
|
1940
1941
|
"infraSetupBanner": {
|
|
1941
1942
|
"agentExecutor": {
|
|
1942
1943
|
"title": "Виконавець агентів не налаштований",
|
|
1944
|
+
"unreachableTitle": "Виконавець агентів недоступний",
|
|
1943
1945
|
"body": "Це розгортання запускає агентів у самостійно розміщеному пулі раннерів, але жоден не зареєстрований, тож жоден агент не зможе працювати, доки ви не підключите його.",
|
|
1944
1946
|
"action": "Налаштувати пул раннерів"
|
|
1945
1947
|
},
|
|
1946
1948
|
"ephemeralEnvironments": {
|
|
1947
1949
|
"title": "Тестове середовище не налаштоване",
|
|
1950
|
+
"unreachableTitle": "Постачальник тестових середовищ недоступний",
|
|
1948
1951
|
"body": "Не зареєстровано жодного постачальника ефемерних середовищ, тож тестові агенти, яким потрібне робоче середовище попереднього перегляду, не можуть працювати. Відкрийте нижче «Тестові середовища» та підключіть кластер Kubernetes або власного постачальника середовищ HTTP, щоб увімкнути їх.",
|
|
1949
1952
|
"action": "Налаштувати середовище"
|
|
1950
1953
|
},
|
|
@@ -1956,6 +1959,11 @@
|
|
|
1956
1959
|
"dismiss": {
|
|
1957
1960
|
"session": "Відхилити на цю сесію",
|
|
1958
1961
|
"permanent": "Мене влаштовують обмеження, більше не сповіщати"
|
|
1962
|
+
},
|
|
1963
|
+
"unreachable": {
|
|
1964
|
+
"body": "З’єднання налаштоване, але жива перевірка щойно не змогла до нього дістатися. Тому це збій, а не відсутнє налаштування: агенти, які від нього залежать, завершуватимуться з помилкою, доки не з’явиться відповідь. Перевірте, чи працює служба, а потім протестуйте з’єднання ще раз.",
|
|
1965
|
+
"reason": "Остання перевірка: {detail}",
|
|
1966
|
+
"action": "Перевірити з’єднання"
|
|
1959
1967
|
}
|
|
1960
1968
|
},
|
|
1961
1969
|
"spendWarningBanner": {
|
|
@@ -3586,6 +3594,7 @@
|
|
|
3586
3594
|
},
|
|
3587
3595
|
"routable": {
|
|
3588
3596
|
"platform_health": "Стан платформи",
|
|
3597
|
+
"infra_unreachable": "Збої інфраструктури",
|
|
3589
3598
|
"merge_review": "Перевірка злиття",
|
|
3590
3599
|
"pipeline_complete": "Конвеєр завершено",
|
|
3591
3600
|
"ci_failed": "Збій CI",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.191.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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.198.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|