@cat-factory/app 0.63.0 → 0.64.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/panels/inspector/ServiceTestConfig.vue +85 -0
- package/app/components/settings/InfraHandlersConfigurator.vue +119 -8
- package/app/components/settings/KubernetesEngineForm.vue +36 -0
- package/app/composables/api/infraHandlers.ts +6 -0
- package/app/spa-loading-template.html +109 -8
- package/app/stores/infraConfig.ts +8 -0
- package/app/stores/providerConnections.ts +20 -2
- package/i18n/locales/en.json +6 -0
- package/i18n/locales/es.json +6 -0
- package/i18n/locales/fr.json +6 -0
- package/i18n/locales/he.json +6 -0
- package/i18n/locales/ja.json +6 -0
- package/i18n/locales/pl.json +6 -0
- package/i18n/locales/tr.json +6 -0
- package/i18n/locales/uk.json +6 -0
- package/package.json +2 -2
|
@@ -10,8 +10,11 @@ import type {
|
|
|
10
10
|
import type {
|
|
11
11
|
KubernetesManifestSource,
|
|
12
12
|
KubernetesRenderer,
|
|
13
|
+
ProvisioningComposeServiceCandidate,
|
|
14
|
+
ProvisioningManifestRootCandidate,
|
|
13
15
|
ProvisioningOverlayCandidate,
|
|
14
16
|
ProvisioningRecommendation,
|
|
17
|
+
ProvisioningServiceDirCandidate,
|
|
15
18
|
} from '@cat-factory/contracts'
|
|
16
19
|
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
17
20
|
|
|
@@ -204,6 +207,11 @@ function applyPicked() {
|
|
|
204
207
|
const detecting = ref(false)
|
|
205
208
|
const detectError = ref(false)
|
|
206
209
|
const detectResult = ref<ProvisioningRecommendation | null>(null)
|
|
210
|
+
// Advisory, LOCAL-ONLY selection: which compose `services:` key the user picked. It is NOT persisted
|
|
211
|
+
// (the compose backend targets the file, not a single service), so it lives only in component state
|
|
212
|
+
// and merely drives the chip highlight. Without it the highlight would compare `composePath` — which
|
|
213
|
+
// every candidate shares — and light up ALL chips at once, making the picker look non-functional.
|
|
214
|
+
const pickedComposeService = ref<string | null>(null)
|
|
207
215
|
|
|
208
216
|
// A detection result is scoped to the inspected block — clear it (and any error) when the
|
|
209
217
|
// selection changes, so block B never shows block A's stale recommendation / overlay chips.
|
|
@@ -212,6 +220,7 @@ watch(
|
|
|
212
220
|
() => {
|
|
213
221
|
detectResult.value = null
|
|
214
222
|
detectError.value = false
|
|
223
|
+
pickedComposeService.value = null
|
|
215
224
|
},
|
|
216
225
|
)
|
|
217
226
|
|
|
@@ -235,6 +244,9 @@ async function detectFromRepo() {
|
|
|
235
244
|
prefer: provisionType.value,
|
|
236
245
|
})
|
|
237
246
|
detectResult.value = rec
|
|
247
|
+
// Pre-select the recommended compose service so the picker opens on a real choice.
|
|
248
|
+
pickedComposeService.value =
|
|
249
|
+
rec.composeServiceCandidates?.find((c) => c.recommended)?.service ?? null
|
|
238
250
|
// Only prefill when the detector actually inferred something. A `detected: false`
|
|
239
251
|
// recommendation is `infraless`; applying it would WIPE the service's existing
|
|
240
252
|
// provisioning (board.updateBlock persists immediately). Leave the current config
|
|
@@ -255,6 +267,25 @@ function applyOverlay(candidate: ProvisioningOverlayCandidate) {
|
|
|
255
267
|
setKubePath(candidate.path)
|
|
256
268
|
}
|
|
257
269
|
|
|
270
|
+
// Point the manifest path at a different k8s root (and match its renderer) the user picks.
|
|
271
|
+
function applyManifestRoot(candidate: ProvisioningManifestRootCandidate) {
|
|
272
|
+
setKubePath(candidate.path)
|
|
273
|
+
setKubeRenderer(candidate.renderer)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Point the manifest path at a different root-shared monorepo deploy slice the user picks.
|
|
277
|
+
function applyServiceDir(candidate: ProvisioningServiceDirCandidate) {
|
|
278
|
+
setKubePath(candidate.path)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Point the compose file at the picked candidate's file and record the advisory service selection.
|
|
282
|
+
// The service KEY is not persisted (the compose backend targets the file, not a single service); the
|
|
283
|
+
// picked key is tracked locally only to drive the chip highlight and the note.
|
|
284
|
+
function applyComposeService(candidate: ProvisioningComposeServiceCandidate) {
|
|
285
|
+
setComposePath(candidate.composePath)
|
|
286
|
+
pickedComposeService.value = candidate.service
|
|
287
|
+
}
|
|
288
|
+
|
|
258
289
|
function provisionTypeLabel(type: ProvisionType): string {
|
|
259
290
|
return t(`inspector.testConfig.provisionTypes.${type}`)
|
|
260
291
|
}
|
|
@@ -350,6 +381,42 @@ function setSize(value: InstanceSize) {
|
|
|
350
381
|
}}
|
|
351
382
|
</p>
|
|
352
383
|
|
|
384
|
+
<div v-if="detectResult.serviceDirCandidates?.length" class="space-y-1">
|
|
385
|
+
<span class="text-[11px] text-slate-400">{{
|
|
386
|
+
t('inspector.testConfig.detect.serviceDirTitle')
|
|
387
|
+
}}</span>
|
|
388
|
+
<div class="flex flex-wrap gap-1">
|
|
389
|
+
<UButton
|
|
390
|
+
v-for="s in detectResult.serviceDirCandidates"
|
|
391
|
+
:key="s.path"
|
|
392
|
+
:color="kubePath === s.path ? 'primary' : 'neutral'"
|
|
393
|
+
:variant="kubePath === s.path ? 'soft' : 'ghost'"
|
|
394
|
+
size="xs"
|
|
395
|
+
@click="applyServiceDir(s)"
|
|
396
|
+
>
|
|
397
|
+
{{ s.name }}
|
|
398
|
+
</UButton>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
<div v-if="detectResult.manifestRootCandidates?.length" class="space-y-1">
|
|
403
|
+
<span class="text-[11px] text-slate-400">{{
|
|
404
|
+
t('inspector.testConfig.detect.manifestRootTitle')
|
|
405
|
+
}}</span>
|
|
406
|
+
<div class="flex flex-wrap gap-1">
|
|
407
|
+
<UButton
|
|
408
|
+
v-for="r in detectResult.manifestRootCandidates"
|
|
409
|
+
:key="r.path"
|
|
410
|
+
:color="kubePath === r.path ? 'primary' : 'neutral'"
|
|
411
|
+
:variant="kubePath === r.path ? 'soft' : 'ghost'"
|
|
412
|
+
size="xs"
|
|
413
|
+
@click="applyManifestRoot(r)"
|
|
414
|
+
>
|
|
415
|
+
{{ r.name }}
|
|
416
|
+
</UButton>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
353
420
|
<div v-if="detectResult.overlayCandidates?.length" class="space-y-1">
|
|
354
421
|
<span class="text-[11px] text-slate-400">{{
|
|
355
422
|
t('inspector.testConfig.detect.overlayTitle')
|
|
@@ -368,6 +435,24 @@ function setSize(value: InstanceSize) {
|
|
|
368
435
|
</div>
|
|
369
436
|
</div>
|
|
370
437
|
|
|
438
|
+
<div v-if="detectResult.composeServiceCandidates?.length" class="space-y-1">
|
|
439
|
+
<span class="text-[11px] text-slate-400">{{
|
|
440
|
+
t('inspector.testConfig.detect.composeServiceTitle')
|
|
441
|
+
}}</span>
|
|
442
|
+
<div class="flex flex-wrap gap-1">
|
|
443
|
+
<UButton
|
|
444
|
+
v-for="c in detectResult.composeServiceCandidates"
|
|
445
|
+
:key="c.service"
|
|
446
|
+
:color="pickedComposeService === c.service ? 'primary' : 'neutral'"
|
|
447
|
+
:variant="pickedComposeService === c.service ? 'soft' : 'ghost'"
|
|
448
|
+
size="xs"
|
|
449
|
+
@click="applyComposeService(c)"
|
|
450
|
+
>
|
|
451
|
+
{{ c.service }}
|
|
452
|
+
</UButton>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
371
456
|
<p v-if="detectResult.urlSource" class="text-[11px] text-slate-500">
|
|
372
457
|
{{
|
|
373
458
|
t('inspector.testConfig.detect.urlSource', { source: detectResult.urlSource.source })
|
|
@@ -79,6 +79,51 @@ watch(
|
|
|
79
79
|
|
|
80
80
|
const busy = ref(false)
|
|
81
81
|
|
|
82
|
+
// Connection-probe state for the kube engine forms (workspace + per-user override kept
|
|
83
|
+
// separate so a probe result lands on the form it came from). The probe reaches the apiserver
|
|
84
|
+
// with the supplied config + token via the per-type handler test endpoint — nothing persisted.
|
|
85
|
+
type TestResult = { ok: boolean; message?: string } | null
|
|
86
|
+
const kubeTesting = ref(false)
|
|
87
|
+
const kubeTestResult = ref<TestResult>(null)
|
|
88
|
+
const kubeOverrideTesting = ref(false)
|
|
89
|
+
const kubeOverrideTestResult = ref<TestResult>(null)
|
|
90
|
+
|
|
91
|
+
async function testKube(payload: { config: KubeHandlerConfig; secrets: Record<string, string> }) {
|
|
92
|
+
kubeTesting.value = true
|
|
93
|
+
kubeTestResult.value = null
|
|
94
|
+
try {
|
|
95
|
+
kubeTestResult.value = await infra.testHandler({
|
|
96
|
+
config: payload.config,
|
|
97
|
+
secrets: payload.secrets,
|
|
98
|
+
})
|
|
99
|
+
} catch (e) {
|
|
100
|
+
kubeTestResult.value = { ok: false, message: e instanceof Error ? e.message : String(e) }
|
|
101
|
+
} finally {
|
|
102
|
+
kubeTesting.value = false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function testKubeOverride(payload: {
|
|
107
|
+
config: KubeHandlerConfig
|
|
108
|
+
secrets: Record<string, string>
|
|
109
|
+
}) {
|
|
110
|
+
kubeOverrideTesting.value = true
|
|
111
|
+
kubeOverrideTestResult.value = null
|
|
112
|
+
try {
|
|
113
|
+
kubeOverrideTestResult.value = await infra.testHandler({
|
|
114
|
+
config: payload.config,
|
|
115
|
+
secrets: payload.secrets,
|
|
116
|
+
})
|
|
117
|
+
} catch (e) {
|
|
118
|
+
kubeOverrideTestResult.value = {
|
|
119
|
+
ok: false,
|
|
120
|
+
message: e instanceof Error ? e.message : String(e),
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
kubeOverrideTesting.value = false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
82
127
|
async function saveKube(payload: { config: KubeHandlerConfig; secrets: Record<string, string> }) {
|
|
83
128
|
busy.value = true
|
|
84
129
|
try {
|
|
@@ -170,6 +215,58 @@ const customSavedManifest = computed<Record<string, unknown> | undefined>(() =>
|
|
|
170
215
|
: undefined
|
|
171
216
|
})
|
|
172
217
|
|
|
218
|
+
// The registry backend that builds the `remote-custom` handler's provider. The generic
|
|
219
|
+
// built-in `manifest` (BYO HTTP API) is the default; a deployment that registered a native
|
|
220
|
+
// custom env backend (e.g. Kargo) can be picked here so the handler is pinned to it instead of
|
|
221
|
+
// silently resolving to the generic manifest provider. Only backends that serve the
|
|
222
|
+
// `remote-custom` engine are offered (the snapshot advertises each backend's engines).
|
|
223
|
+
const providerConnections = useProviderConnectionsStore()
|
|
224
|
+
const customBackendOptions = computed(() =>
|
|
225
|
+
providerConnections
|
|
226
|
+
.backendKindsFor('environment')
|
|
227
|
+
.filter((o) => o.engines?.includes('remote-custom'))
|
|
228
|
+
.map((o) => ({ label: o.label, value: o.kind })),
|
|
229
|
+
)
|
|
230
|
+
const selectedBackendKind = ref<string>('manifest')
|
|
231
|
+
// When editing a saved handler, reflect the backend it was registered with; when switching to a
|
|
232
|
+
// custom type with no handler yet, fall back to the first offered backend (the generic manifest).
|
|
233
|
+
watch(
|
|
234
|
+
[customHandler, customBackendOptions],
|
|
235
|
+
([handler, options]) => {
|
|
236
|
+
const valid = (k: string) => options.some((o) => o.value === k)
|
|
237
|
+
if (handler?.backendKind && valid(handler.backendKind)) {
|
|
238
|
+
selectedBackendKind.value = handler.backendKind
|
|
239
|
+
} else if (!valid(selectedBackendKind.value)) {
|
|
240
|
+
selectedBackendKind.value = options.find((o) => o.value === 'manifest')?.value
|
|
241
|
+
? 'manifest'
|
|
242
|
+
: (options[0]?.value ?? 'manifest')
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
{ immediate: true },
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
// For a NEW handler on a NON-generic backend, prefill the editor from that backend's manifest
|
|
249
|
+
// template (its self-described skeleton + secret refs) so the operator isn't faced with the
|
|
250
|
+
// generic starter. A saved handler's own manifest always takes precedence.
|
|
251
|
+
const templateManifest = ref<Record<string, unknown> | undefined>(undefined)
|
|
252
|
+
watch(
|
|
253
|
+
[selectedBackendKind, customHandler],
|
|
254
|
+
async ([kind, handler]) => {
|
|
255
|
+
if (handler || kind === 'manifest') {
|
|
256
|
+
templateManifest.value = undefined
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
const descriptor = await providerConnections.fetchDescriptor('environment', kind)
|
|
260
|
+
templateManifest.value = descriptor?.manifestTemplate
|
|
261
|
+
},
|
|
262
|
+
{ immediate: true },
|
|
263
|
+
)
|
|
264
|
+
// The manifest to seed the editor with: a saved handler's stored manifest, else the picked
|
|
265
|
+
// backend's template (custom kinds), else undefined (the editor's generic starter).
|
|
266
|
+
const customEditorManifest = computed<Record<string, unknown> | undefined>(
|
|
267
|
+
() => customSavedManifest.value ?? templateManifest.value,
|
|
268
|
+
)
|
|
269
|
+
|
|
173
270
|
async function saveCustom(payload: {
|
|
174
271
|
manifest: Record<string, unknown>
|
|
175
272
|
secrets: Record<string, string>
|
|
@@ -186,6 +283,9 @@ async function saveCustom(payload: {
|
|
|
186
283
|
provisionType: 'custom',
|
|
187
284
|
manifestId: selectedCustomId.value,
|
|
188
285
|
config,
|
|
286
|
+
// Pin the chosen registry backend so a native custom backend (e.g. Kargo) builds the
|
|
287
|
+
// provider — absent, the engine would resolve to the generic manifest provider.
|
|
288
|
+
backendKind: selectedBackendKind.value,
|
|
189
289
|
secrets: payload.secrets,
|
|
190
290
|
})
|
|
191
291
|
toastSaved()
|
|
@@ -280,10 +380,11 @@ function notifyError(e: unknown) {
|
|
|
280
380
|
<KubernetesEngineForm
|
|
281
381
|
:engine="selectedKubeEngine"
|
|
282
382
|
:handler="kubeHandler"
|
|
283
|
-
:supports-test="
|
|
284
|
-
:testing="
|
|
383
|
+
:supports-test="true"
|
|
384
|
+
:testing="kubeTesting"
|
|
285
385
|
:busy="busy"
|
|
286
|
-
:test-result="
|
|
386
|
+
:test-result="kubeTestResult"
|
|
387
|
+
@test="testKube"
|
|
287
388
|
@save="saveKube"
|
|
288
389
|
/>
|
|
289
390
|
|
|
@@ -322,10 +423,11 @@ function notifyError(e: unknown) {
|
|
|
322
423
|
<KubernetesEngineForm
|
|
323
424
|
:engine="selectedKubeEngine"
|
|
324
425
|
:handler="kubeUserHandler"
|
|
325
|
-
:supports-test="
|
|
326
|
-
:testing="
|
|
426
|
+
:supports-test="true"
|
|
427
|
+
:testing="kubeOverrideTesting"
|
|
327
428
|
:busy="busy"
|
|
328
|
-
:test-result="
|
|
429
|
+
:test-result="kubeOverrideTestResult"
|
|
430
|
+
@test="testKubeOverride"
|
|
329
431
|
@save="saveKubeOverride"
|
|
330
432
|
/>
|
|
331
433
|
</div>
|
|
@@ -355,6 +457,15 @@ function notifyError(e: unknown) {
|
|
|
355
457
|
<UFormField :label="t('settings.infrastructure.handler.customTypeLabel')">
|
|
356
458
|
<USelect v-model="selectedCustomId" :items="customTypeItems" />
|
|
357
459
|
</UFormField>
|
|
460
|
+
<!-- Which registered backend builds this custom handler's provider. Shown only when a
|
|
461
|
+
deployment registered a custom backend beyond the generic manifest. -->
|
|
462
|
+
<UFormField
|
|
463
|
+
v-if="customBackendOptions.length > 1"
|
|
464
|
+
:label="t('settings.infrastructure.handler.customBackendLabel')"
|
|
465
|
+
:help="t('settings.infrastructure.handler.customBackendHelp')"
|
|
466
|
+
>
|
|
467
|
+
<USelect v-model="selectedBackendKind" :items="customBackendOptions" />
|
|
468
|
+
</UFormField>
|
|
358
469
|
<p
|
|
359
470
|
v-if="customHandler"
|
|
360
471
|
class="flex items-center justify-between gap-2 text-[12px] text-slate-300"
|
|
@@ -373,9 +484,9 @@ function notifyError(e: unknown) {
|
|
|
373
484
|
</p>
|
|
374
485
|
<ProviderManifestEditor
|
|
375
486
|
v-if="selectedCustomId"
|
|
376
|
-
:key="selectedCustomId"
|
|
487
|
+
:key="`${selectedCustomId}:${selectedBackendKind}`"
|
|
377
488
|
kind="environment"
|
|
378
|
-
:saved-manifest="
|
|
489
|
+
:saved-manifest="customEditorManifest"
|
|
379
490
|
:connected="!!customHandler"
|
|
380
491
|
:stored-secret-keys="customHandler?.secretKeys ?? []"
|
|
381
492
|
:supports-test="false"
|
|
@@ -115,6 +115,35 @@ watch(
|
|
|
115
115
|
{ immediate: true },
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
+
// The local-cluster apiserver address every loopback distro (k3s / k3d / kind / minikube)
|
|
119
|
+
// exposes by default — see `seedForEngine`.
|
|
120
|
+
const LOCAL_K3S_API_SERVER = 'https://127.0.0.1:6443'
|
|
121
|
+
|
|
122
|
+
// Seed the form for the SELECTED engine, so picking an engine gives immediate feedback instead
|
|
123
|
+
// of a dead toggle. `local-k3s` is a low-config local cluster (k3s / k3d / kind all expose a
|
|
124
|
+
// loopback apiserver with a self-signed cert), so prefill its loopback defaults + flag insecure
|
|
125
|
+
// TLS — the operator then only pastes a ServiceAccount token and picks the URL source.
|
|
126
|
+
// `remote-kubernetes` starts clean. Only seeds a FRESH form (never clobbers an edit — a saved
|
|
127
|
+
// handler is prefilled from its stored config by the watch above).
|
|
128
|
+
watch(
|
|
129
|
+
() => props.engine,
|
|
130
|
+
(engine) => {
|
|
131
|
+
if (props.handler) return
|
|
132
|
+
if (engine === 'local-k3s') {
|
|
133
|
+
if (!form.label.trim()) form.label = 'Local k3s'
|
|
134
|
+
if (!form.apiServerUrl.trim()) form.apiServerUrl = LOCAL_K3S_API_SERVER
|
|
135
|
+
form.insecureSkipTlsVerify = true
|
|
136
|
+
} else {
|
|
137
|
+
// Clear the local-only loopback defaults so a remote engine isn't misleadingly prefilled,
|
|
138
|
+
// but leave anything the operator has actually typed.
|
|
139
|
+
if (form.apiServerUrl === LOCAL_K3S_API_SERVER) form.apiServerUrl = ''
|
|
140
|
+
if (form.label === 'Local k3s') form.label = ''
|
|
141
|
+
form.insecureSkipTlsVerify = false
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{ immediate: true },
|
|
145
|
+
)
|
|
146
|
+
|
|
118
147
|
const servicePortValid = computed(() => {
|
|
119
148
|
const raw = form.servicePort.trim()
|
|
120
149
|
if (!raw) return true
|
|
@@ -185,6 +214,13 @@ function optional(label: string): string {
|
|
|
185
214
|
}}
|
|
186
215
|
</p>
|
|
187
216
|
|
|
217
|
+
<p
|
|
218
|
+
v-if="engine === 'local-k3s'"
|
|
219
|
+
class="rounded-md border border-sky-500/30 bg-sky-500/10 p-2 text-[11px] text-sky-200"
|
|
220
|
+
>
|
|
221
|
+
{{ t('settings.infrastructure.kubernetesEngine.localK3sHint') }}
|
|
222
|
+
</p>
|
|
223
|
+
|
|
188
224
|
<UFormField :label="t('settings.infrastructure.kubernetesEngine.label')">
|
|
189
225
|
<UInput
|
|
190
226
|
v-model="form.label"
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
registerEnvironmentHandlerContract,
|
|
6
6
|
removeCustomManifestTypeContract,
|
|
7
7
|
removeEnvironmentUserHandlerContract,
|
|
8
|
+
testEnvironmentHandlerContract,
|
|
8
9
|
unregisterEnvironmentHandlerContract,
|
|
9
10
|
upsertCustomManifestTypeContract,
|
|
10
11
|
upsertEnvironmentUserHandlerContract,
|
|
@@ -13,6 +14,7 @@ import type {
|
|
|
13
14
|
DetectServiceProvisioningInput,
|
|
14
15
|
ProvisionType,
|
|
15
16
|
RegisterEnvironmentHandlerInput,
|
|
17
|
+
TestEnvironmentHandlerInput,
|
|
16
18
|
UpsertCustomManifestTypeInput,
|
|
17
19
|
UpsertEnvironmentUserHandlerBody,
|
|
18
20
|
} from '@cat-factory/contracts'
|
|
@@ -35,6 +37,10 @@ export function infraHandlersApi({ send, ws }: ApiContext) {
|
|
|
35
37
|
registerEnvironmentHandler: (workspaceId: string, body: RegisterEnvironmentHandlerInput) =>
|
|
36
38
|
send(registerEnvironmentHandlerContract, { pathPrefix: ws(workspaceId), body }),
|
|
37
39
|
|
|
40
|
+
// Probe a candidate handler connection before saving (nothing persisted).
|
|
41
|
+
testEnvironmentHandler: (workspaceId: string, body: TestEnvironmentHandlerInput) =>
|
|
42
|
+
send(testEnvironmentHandlerContract, { pathPrefix: ws(workspaceId), body }),
|
|
43
|
+
|
|
38
44
|
// Auto-detect a non-binding recommended provisioning config from a service's repo.
|
|
39
45
|
detectServiceProvisioning: (workspaceId: string, body: DetectServiceProvisioningInput) =>
|
|
40
46
|
send(detectServiceProvisioningContract, { pathPrefix: ws(workspaceId), body }),
|
|
@@ -1,32 +1,133 @@
|
|
|
1
1
|
<!-- Early SPA loading shell painted before the JS bundle parses + Vue mounts
|
|
2
2
|
(Nuxt removes it on mount). Self-contained: main.css/icon fonts aren't loaded
|
|
3
|
-
yet, so styles +
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
yet, so styles + markup are inline pure-CSS. The only copy is the "Cat Factory"
|
|
4
|
+
brand name (a proper noun, identical in every locale) so it stays locale-neutral.
|
|
5
|
+
Wired via app.spaLoadingTemplate in nuxt.config.ts. -->
|
|
6
|
+
<div class="cf-spa-loader" role="status" aria-label="Cat Factory is starting">
|
|
7
|
+
<div class="cf-spa-loader__badge">
|
|
8
|
+
<div class="cf-spa-loader__ring"></div>
|
|
9
|
+
<!-- Cat face, drawn inline so it paints without the icon font. -->
|
|
10
|
+
<svg class="cf-spa-loader__cat" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
11
|
+
<path
|
|
12
|
+
d="M4 4l3 3.2A7.5 7.5 0 0 1 12 6c1.9 0 3.6.5 5 1.2L20 4v8a8 8 0 0 1-16 0V4z"
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
/>
|
|
15
|
+
<circle cx="9" cy="11.5" r="1.1" fill="#0b1020" />
|
|
16
|
+
<circle cx="15" cy="11.5" r="1.1" fill="#0b1020" />
|
|
17
|
+
<path
|
|
18
|
+
d="M10.6 14.4c.4.5 2.4.5 2.8 0"
|
|
19
|
+
stroke="#0b1020"
|
|
20
|
+
stroke-width="1"
|
|
21
|
+
stroke-linecap="round"
|
|
22
|
+
/>
|
|
23
|
+
</svg>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="cf-spa-loader__text">
|
|
26
|
+
<span class="cf-spa-loader__brand">Cat Factory</span>
|
|
27
|
+
<span class="cf-spa-loader__status">
|
|
28
|
+
is starting<span class="cf-spa-loader__dots"><i>.</i><i>.</i><i>.</i></span>
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
7
31
|
</div>
|
|
8
32
|
<style>
|
|
9
33
|
.cf-spa-loader {
|
|
10
34
|
position: fixed;
|
|
11
35
|
inset: 0;
|
|
12
36
|
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
13
38
|
align-items: center;
|
|
14
39
|
justify-content: center;
|
|
40
|
+
gap: 1.5rem;
|
|
15
41
|
/* --board-bg / slate-950, matching the board surface AuthGate renders on. */
|
|
16
42
|
background-color: #0b1020;
|
|
43
|
+
font-family:
|
|
44
|
+
ui-sans-serif,
|
|
45
|
+
system-ui,
|
|
46
|
+
-apple-system,
|
|
47
|
+
'Segoe UI',
|
|
48
|
+
Roboto,
|
|
49
|
+
sans-serif;
|
|
17
50
|
}
|
|
18
|
-
.cf-spa-
|
|
19
|
-
|
|
20
|
-
|
|
51
|
+
.cf-spa-loader__badge {
|
|
52
|
+
position: relative;
|
|
53
|
+
width: 5rem;
|
|
54
|
+
height: 5rem;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
}
|
|
59
|
+
.cf-spa-loader__ring {
|
|
60
|
+
position: absolute;
|
|
61
|
+
inset: 0;
|
|
21
62
|
border-radius: 9999px;
|
|
22
63
|
/* Track in slate-700, the active arc in indigo-500 (the app's primary). */
|
|
23
|
-
border: 3px solid #
|
|
64
|
+
border: 3px solid #1e293b;
|
|
24
65
|
border-top-color: #6366f1;
|
|
25
66
|
animation: cf-spa-spin 1s linear infinite;
|
|
26
67
|
}
|
|
68
|
+
.cf-spa-loader__cat {
|
|
69
|
+
width: 2.5rem;
|
|
70
|
+
height: 2.5rem;
|
|
71
|
+
color: #818cf8;
|
|
72
|
+
animation: cf-spa-bob 1.6s ease-in-out infinite;
|
|
73
|
+
}
|
|
74
|
+
.cf-spa-loader__text {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.25rem;
|
|
79
|
+
}
|
|
80
|
+
.cf-spa-loader__brand {
|
|
81
|
+
font-size: 1.35rem;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
letter-spacing: 0.01em;
|
|
84
|
+
background: linear-gradient(90deg, #818cf8, #6366f1);
|
|
85
|
+
-webkit-background-clip: text;
|
|
86
|
+
background-clip: text;
|
|
87
|
+
color: transparent;
|
|
88
|
+
}
|
|
89
|
+
.cf-spa-loader__status {
|
|
90
|
+
font-size: 0.875rem;
|
|
91
|
+
color: #94a3b8;
|
|
92
|
+
}
|
|
93
|
+
.cf-spa-loader__dots i {
|
|
94
|
+
font-style: normal;
|
|
95
|
+
animation: cf-spa-blink 1.4s infinite both;
|
|
96
|
+
}
|
|
97
|
+
.cf-spa-loader__dots i:nth-child(2) {
|
|
98
|
+
animation-delay: 0.2s;
|
|
99
|
+
}
|
|
100
|
+
.cf-spa-loader__dots i:nth-child(3) {
|
|
101
|
+
animation-delay: 0.4s;
|
|
102
|
+
}
|
|
27
103
|
@keyframes cf-spa-spin {
|
|
28
104
|
to {
|
|
29
105
|
transform: rotate(360deg);
|
|
30
106
|
}
|
|
31
107
|
}
|
|
108
|
+
@keyframes cf-spa-bob {
|
|
109
|
+
0%,
|
|
110
|
+
100% {
|
|
111
|
+
transform: translateY(0);
|
|
112
|
+
}
|
|
113
|
+
50% {
|
|
114
|
+
transform: translateY(-0.2rem);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
@keyframes cf-spa-blink {
|
|
118
|
+
0%,
|
|
119
|
+
100% {
|
|
120
|
+
opacity: 0.2;
|
|
121
|
+
}
|
|
122
|
+
50% {
|
|
123
|
+
opacity: 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
@media (prefers-reduced-motion: reduce) {
|
|
127
|
+
.cf-spa-loader__ring,
|
|
128
|
+
.cf-spa-loader__cat,
|
|
129
|
+
.cf-spa-loader__dots i {
|
|
130
|
+
animation: none;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
32
133
|
</style>
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
EnvironmentHandlerView,
|
|
7
7
|
ProvisionType,
|
|
8
8
|
RegisterEnvironmentHandlerInput,
|
|
9
|
+
TestEnvironmentHandlerInput,
|
|
9
10
|
UpsertCustomManifestTypeInput,
|
|
10
11
|
UpsertEnvironmentUserHandlerBody,
|
|
11
12
|
} from '@cat-factory/contracts'
|
|
@@ -95,6 +96,12 @@ export const useInfraConfigStore = defineStore('infraConfig', () => {
|
|
|
95
96
|
return saved
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/** Probe a candidate handler connection before saving (nothing persisted). */
|
|
100
|
+
async function testHandler(input: TestEnvironmentHandlerInput) {
|
|
101
|
+
const ws = useWorkspaceStore()
|
|
102
|
+
return api.testEnvironmentHandler(ws.requireId(), input)
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
/**
|
|
99
106
|
* Auto-detect a NON-BINDING recommended provisioning config from a service's repo. The SPA
|
|
100
107
|
* prefills the confirm form from the result; nothing is persisted server-side. Detection is
|
|
@@ -172,6 +179,7 @@ export const useInfraConfigStore = defineStore('infraConfig', () => {
|
|
|
172
179
|
ensureLoaded,
|
|
173
180
|
handlerFor,
|
|
174
181
|
registerHandler,
|
|
182
|
+
testHandler,
|
|
175
183
|
detectProvisioning,
|
|
176
184
|
unregisterHandler,
|
|
177
185
|
upsertCustomType,
|
|
@@ -18,8 +18,8 @@ const KINDS: ProviderConnectionKind[] = ['environment', 'runner-pool']
|
|
|
18
18
|
// additionally carry a deployment's programmatically-registered CUSTOM kinds.
|
|
19
19
|
const BUILTIN_BACKEND_KINDS: Record<ProviderConnectionKind, BackendKindOption[]> = {
|
|
20
20
|
environment: [
|
|
21
|
-
{ kind: 'manifest', label: 'HTTP manifest' },
|
|
22
|
-
{ kind: 'kubernetes', label: 'Kubernetes' },
|
|
21
|
+
{ kind: 'manifest', label: 'HTTP manifest', engines: ['remote-custom'] },
|
|
22
|
+
{ kind: 'kubernetes', label: 'Kubernetes', engines: ['local-k3s', 'remote-kubernetes'] },
|
|
23
23
|
],
|
|
24
24
|
'runner-pool': [
|
|
25
25
|
{ kind: 'manifest', label: 'HTTP manifest pool' },
|
|
@@ -111,6 +111,23 @@ export const useProviderConnectionsStore = defineStore('providerConnections', ()
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Fetch (without mutating shared state) the descriptor for a specific backend kind — used by
|
|
116
|
+
* the per-type infra configurator to prefill a custom backend's manifest template/secret
|
|
117
|
+
* fields when the operator picks it. Returns null on a transient describe failure.
|
|
118
|
+
*/
|
|
119
|
+
async function fetchDescriptor(
|
|
120
|
+
kind: ProviderConnectionKind,
|
|
121
|
+
backendKind?: string,
|
|
122
|
+
): Promise<ProviderDescriptor | null> {
|
|
123
|
+
const ws = useWorkspaceStore()
|
|
124
|
+
try {
|
|
125
|
+
return await api.describeProvider(ws.requireId(), kind, backendKind)
|
|
126
|
+
} catch {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
/** Refresh both providers (used by the banner + after a save/remove). */
|
|
115
132
|
async function load() {
|
|
116
133
|
await Promise.all(KINDS.map((k) => loadKind(k)))
|
|
@@ -177,6 +194,7 @@ export const useProviderConnectionsStore = defineStore('providerConnections', ()
|
|
|
177
194
|
load,
|
|
178
195
|
loadKind,
|
|
179
196
|
loadDescriptor,
|
|
197
|
+
fetchDescriptor,
|
|
180
198
|
ensureLoaded,
|
|
181
199
|
descriptorFor,
|
|
182
200
|
connectionFor,
|
package/i18n/locales/en.json
CHANGED
|
@@ -483,6 +483,9 @@
|
|
|
483
483
|
"none": "No Kubernetes manifests or Compose file were detected.",
|
|
484
484
|
"applied": "Suggested a {type} config. Review and adjust the fields below.",
|
|
485
485
|
"overlayTitle": "Ephemeral overlay",
|
|
486
|
+
"serviceDirTitle": "Service deploy folder",
|
|
487
|
+
"manifestRootTitle": "Manifest location",
|
|
488
|
+
"composeServiceTitle": "Compose service",
|
|
486
489
|
"urlSource": "Suggested environment URL source: {source}. The workspace handler owns this; set it there.",
|
|
487
490
|
"namespace": "Manifests pin namespace \"{namespace}\"; recommend honoring it on the workspace handler.",
|
|
488
491
|
"confidenceHigh": "Detected",
|
|
@@ -1344,6 +1347,7 @@
|
|
|
1344
1347
|
"remote-kubernetes": "Remote Kubernetes"
|
|
1345
1348
|
},
|
|
1346
1349
|
"kubernetesEngine": {
|
|
1350
|
+
"localK3sHint": "Prefilled for a local k3s/k3d/kind cluster on this machine. Bind a ServiceAccount to a role, mint its token with `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+), and paste it below. Then choose how the environment URL is derived, and edit the API server URL if your cluster listens on a different port.",
|
|
1347
1351
|
"label": "Connection label",
|
|
1348
1352
|
"labelPlaceholder": "Preview cluster",
|
|
1349
1353
|
"apiServerUrl": "API server URL",
|
|
@@ -1400,6 +1404,8 @@
|
|
|
1400
1404
|
"engineLabel": "Engine",
|
|
1401
1405
|
"customHandlerTitle": "Remote-custom handler",
|
|
1402
1406
|
"customTypeLabel": "Custom type",
|
|
1407
|
+
"customBackendLabel": "Backend",
|
|
1408
|
+
"customBackendHelp": "Which registered backend provisions this custom environment.",
|
|
1403
1409
|
"customConnected": "Handler connected.",
|
|
1404
1410
|
"saved": "Handler saved",
|
|
1405
1411
|
"removed": "Handler removed",
|
package/i18n/locales/es.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "No se detectaron manifiestos de Kubernetes ni archivo Compose.",
|
|
447
447
|
"applied": "Se sugirió una configuración {type}. Revisa y ajusta los campos de abajo.",
|
|
448
448
|
"overlayTitle": "Overlay efímero",
|
|
449
|
+
"serviceDirTitle": "Carpeta de despliegue del servicio",
|
|
450
|
+
"manifestRootTitle": "Ubicación del manifiesto",
|
|
451
|
+
"composeServiceTitle": "Servicio de Compose",
|
|
449
452
|
"urlSource": "Fuente de URL del entorno sugerida: {source}. El gestor del espacio de trabajo la controla; configúrala allí.",
|
|
450
453
|
"namespace": "Los manifiestos fijan el espacio de nombres \"{namespace}\"; se recomienda respetarlo en el gestor del espacio de trabajo.",
|
|
451
454
|
"confidenceHigh": "Detectado",
|
|
@@ -1753,6 +1756,7 @@
|
|
|
1753
1756
|
"remote-kubernetes": "Kubernetes remoto"
|
|
1754
1757
|
},
|
|
1755
1758
|
"kubernetesEngine": {
|
|
1759
|
+
"localK3sHint": "Precargado para un clúster local k3s/k3d/kind en esta máquina. Vincula una ServiceAccount a un rol, genera su token con `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) y pégalo abajo. Luego elige cómo se deriva la URL del entorno y edita la URL del API server si tu clúster escucha en otro puerto.",
|
|
1756
1760
|
"label": "Etiqueta de la conexión",
|
|
1757
1761
|
"labelPlaceholder": "Clúster de vista previa",
|
|
1758
1762
|
"apiServerUrl": "URL del API server",
|
|
@@ -1809,6 +1813,8 @@
|
|
|
1809
1813
|
"engineLabel": "Motor",
|
|
1810
1814
|
"customHandlerTitle": "Gestor remote-custom",
|
|
1811
1815
|
"customTypeLabel": "Tipo personalizado",
|
|
1816
|
+
"customBackendLabel": "Backend",
|
|
1817
|
+
"customBackendHelp": "Qué backend registrado aprovisiona este entorno personalizado.",
|
|
1812
1818
|
"customConnected": "Gestor conectado.",
|
|
1813
1819
|
"saved": "Gestor guardado",
|
|
1814
1820
|
"removed": "Gestor eliminado",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "Aucun manifeste Kubernetes ni fichier Compose détecté.",
|
|
447
447
|
"applied": "Configuration {type} suggérée. Vérifiez et ajustez les champs ci-dessous.",
|
|
448
448
|
"overlayTitle": "Overlay éphémère",
|
|
449
|
+
"serviceDirTitle": "Dossier de déploiement du service",
|
|
450
|
+
"manifestRootTitle": "Emplacement du manifeste",
|
|
451
|
+
"composeServiceTitle": "Service Compose",
|
|
449
452
|
"urlSource": "Source d'URL d'environnement suggérée : {source}. Le gestionnaire de l'espace de travail la contrôle ; définissez-la là.",
|
|
450
453
|
"namespace": "Les manifestes fixent l'espace de noms « {namespace} » ; il est recommandé de le respecter sur le gestionnaire de l'espace de travail.",
|
|
451
454
|
"confidenceHigh": "Détecté",
|
|
@@ -1753,6 +1756,7 @@
|
|
|
1753
1756
|
"remote-kubernetes": "Kubernetes distant"
|
|
1754
1757
|
},
|
|
1755
1758
|
"kubernetesEngine": {
|
|
1759
|
+
"localK3sHint": "Prérempli pour un cluster local k3s/k3d/kind sur cette machine. Liez un ServiceAccount à un rôle, générez son token avec `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) et collez-le ci-dessous. Choisissez ensuite comment l'URL de l'environnement est dérivée, et modifiez l'URL de l'API server si votre cluster écoute sur un autre port.",
|
|
1756
1760
|
"label": "Libellé de la connexion",
|
|
1757
1761
|
"labelPlaceholder": "Cluster de prévisualisation",
|
|
1758
1762
|
"apiServerUrl": "URL de l'API server",
|
|
@@ -1809,6 +1813,8 @@
|
|
|
1809
1813
|
"engineLabel": "Moteur",
|
|
1810
1814
|
"customHandlerTitle": "Gestionnaire remote-custom",
|
|
1811
1815
|
"customTypeLabel": "Type personnalisé",
|
|
1816
|
+
"customBackendLabel": "Backend",
|
|
1817
|
+
"customBackendHelp": "Quel backend enregistré provisionne cet environnement personnalisé.",
|
|
1812
1818
|
"customConnected": "Gestionnaire connecté.",
|
|
1813
1819
|
"saved": "Gestionnaire enregistré",
|
|
1814
1820
|
"removed": "Gestionnaire supprimé",
|
package/i18n/locales/he.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "לא זוהו מניפסטים של Kubernetes או קובץ Compose.",
|
|
447
447
|
"applied": "הוצעה תצורת {type}. בדוק והתאם את השדות למטה.",
|
|
448
448
|
"overlayTitle": "שכבת סביבה זמנית",
|
|
449
|
+
"serviceDirTitle": "תיקיית פריסת השירות",
|
|
450
|
+
"manifestRootTitle": "מיקום המניפסט",
|
|
451
|
+
"composeServiceTitle": "שירות Compose",
|
|
449
452
|
"urlSource": "מקור כתובת הסביבה המוצע: {source}. המטפל של המרחב שולט בכך; הגדר זאת שם.",
|
|
450
453
|
"namespace": "המניפסטים מקבעים את מרחב השמות \"{namespace}\"; מומלץ לכבד אותו במטפל של המרחב.",
|
|
451
454
|
"confidenceHigh": "זוהה",
|
|
@@ -1302,6 +1305,7 @@
|
|
|
1302
1305
|
"remote-kubernetes": "Kubernetes מרוחק"
|
|
1303
1306
|
},
|
|
1304
1307
|
"kubernetesEngine": {
|
|
1308
|
+
"localK3sHint": "מולא מראש עבור אשכול k3s/k3d/kind מקומי במחשב הזה. קשרו ServiceAccount לתפקיד, הנפיקו עבורו token באמצעות `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) והדביקו אותו למטה. לאחר מכן בחרו כיצד נגזרת כתובת ה-URL של הסביבה, וערכו את כתובת ה-API server אם האשכול שלכם מאזין ביציאה אחרת.",
|
|
1305
1309
|
"label": "תווית החיבור",
|
|
1306
1310
|
"labelPlaceholder": "אשכול תצוגה מקדימה",
|
|
1307
1311
|
"apiServerUrl": "כתובת ה-API server",
|
|
@@ -1358,6 +1362,8 @@
|
|
|
1358
1362
|
"engineLabel": "מנוע",
|
|
1359
1363
|
"customHandlerTitle": "מטפל remote-custom",
|
|
1360
1364
|
"customTypeLabel": "סוג מותאם",
|
|
1365
|
+
"customBackendLabel": "Backend",
|
|
1366
|
+
"customBackendHelp": "איזה backend רשום מקצה את הסביבה המותאמת אישית הזו.",
|
|
1361
1367
|
"customConnected": "המטפל מחובר.",
|
|
1362
1368
|
"saved": "המטפל נשמר",
|
|
1363
1369
|
"removed": "המטפל הוסר",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "Kubernetes マニフェストや Compose ファイルは検出されませんでした。",
|
|
447
447
|
"applied": "{type} の設定を提案しました。以下のフィールドを確認して調整してください。",
|
|
448
448
|
"overlayTitle": "一時環境のオーバーレイ",
|
|
449
|
+
"serviceDirTitle": "サービスのデプロイフォルダ",
|
|
450
|
+
"manifestRootTitle": "マニフェストの場所",
|
|
451
|
+
"composeServiceTitle": "Compose サービス",
|
|
449
452
|
"urlSource": "推奨される環境 URL ソース: {source}。これはワークスペースのハンドラーが管理します。そちらで設定してください。",
|
|
450
453
|
"namespace": "マニフェストは名前空間「{namespace}」を固定しています。ワークスペースのハンドラーでそれを尊重することを推奨します。",
|
|
451
454
|
"confidenceHigh": "検出",
|
|
@@ -1304,6 +1307,7 @@
|
|
|
1304
1307
|
"remote-kubernetes": "リモート Kubernetes"
|
|
1305
1308
|
},
|
|
1306
1309
|
"kubernetesEngine": {
|
|
1310
|
+
"localK3sHint": "このマシン上のローカル k3s/k3d/kind クラスター向けにあらかじめ入力されています。ServiceAccount をロールにバインドし、`kubectl create token NAME -n NAMESPACE`(Kubernetes 1.24 以降)でトークンを発行して下記に貼り付けてください。その後、環境 URL の導出方法を選択し、クラスターが別のポートで待ち受けている場合は API サーバー URL を編集してください。",
|
|
1307
1311
|
"label": "接続ラベル",
|
|
1308
1312
|
"labelPlaceholder": "プレビュークラスター",
|
|
1309
1313
|
"apiServerUrl": "API サーバー URL",
|
|
@@ -1360,6 +1364,8 @@
|
|
|
1360
1364
|
"engineLabel": "エンジン",
|
|
1361
1365
|
"customHandlerTitle": "remote-custom ハンドラー",
|
|
1362
1366
|
"customTypeLabel": "カスタムタイプ",
|
|
1367
|
+
"customBackendLabel": "バックエンド",
|
|
1368
|
+
"customBackendHelp": "このカスタム環境をプロビジョニングする登録済みバックエンド。",
|
|
1363
1369
|
"customConnected": "ハンドラーが接続されました。",
|
|
1364
1370
|
"saved": "ハンドラーを保存しました",
|
|
1365
1371
|
"removed": "ハンドラーを削除しました",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "Nie wykryto manifestów Kubernetes ani pliku Compose.",
|
|
447
447
|
"applied": "Zaproponowano konfigurację {type}. Przejrzyj i dostosuj pola poniżej.",
|
|
448
448
|
"overlayTitle": "Tymczasowy overlay",
|
|
449
|
+
"serviceDirTitle": "Folder wdrożenia usługi",
|
|
450
|
+
"manifestRootTitle": "Lokalizacja manifestu",
|
|
451
|
+
"composeServiceTitle": "Usługa Compose",
|
|
449
452
|
"urlSource": "Sugerowane źródło adresu URL środowiska: {source}. Zarządza tym handler przestrzeni roboczej; ustaw to tam.",
|
|
450
453
|
"namespace": "Manifesty ustalają przestrzeń nazw \"{namespace}\"; zaleca się jej przestrzeganie w handlerze przestrzeni roboczej.",
|
|
451
454
|
"confidenceHigh": "Wykryto",
|
|
@@ -1753,6 +1756,7 @@
|
|
|
1753
1756
|
"remote-kubernetes": "Zdalny Kubernetes"
|
|
1754
1757
|
},
|
|
1755
1758
|
"kubernetesEngine": {
|
|
1759
|
+
"localK3sHint": "Wstępnie wypełnione dla lokalnego klastra k3s/k3d/kind na tym komputerze. Powiąż ServiceAccount z rolą, wygeneruj jego token poleceniem `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) i wklej go poniżej. Następnie wybierz sposób ustalania adresu URL środowiska i zmień URL serwera API, jeśli Twój klaster nasłuchuje na innym porcie.",
|
|
1756
1760
|
"label": "Etykieta połączenia",
|
|
1757
1761
|
"labelPlaceholder": "Klaster podglądu",
|
|
1758
1762
|
"apiServerUrl": "URL serwera API",
|
|
@@ -1809,6 +1813,8 @@
|
|
|
1809
1813
|
"engineLabel": "Silnik",
|
|
1810
1814
|
"customHandlerTitle": "Handler remote-custom",
|
|
1811
1815
|
"customTypeLabel": "Typ niestandardowy",
|
|
1816
|
+
"customBackendLabel": "Backend",
|
|
1817
|
+
"customBackendHelp": "Który zarejestrowany backend obsługuje to niestandardowe środowisko.",
|
|
1812
1818
|
"customConnected": "Handler połączony.",
|
|
1813
1819
|
"saved": "Handler zapisany",
|
|
1814
1820
|
"removed": "Handler usunięty",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "Kubernetes manifesti veya Compose dosyası algılanmadı.",
|
|
447
447
|
"applied": "{type} yapılandırması önerildi. Aşağıdaki alanları gözden geçirip ayarlayın.",
|
|
448
448
|
"overlayTitle": "Geçici overlay",
|
|
449
|
+
"serviceDirTitle": "Servis dağıtım klasörü",
|
|
450
|
+
"manifestRootTitle": "Manifest konumu",
|
|
451
|
+
"composeServiceTitle": "Compose servisi",
|
|
449
452
|
"urlSource": "Önerilen ortam URL kaynağı: {source}. Bunu çalışma alanı işleyicisi yönetir; oradan ayarlayın.",
|
|
450
453
|
"namespace": "Manifestler \"{namespace}\" ad alanını sabitliyor; çalışma alanı işleyicisinde buna uymanız önerilir.",
|
|
451
454
|
"confidenceHigh": "Algılandı",
|
|
@@ -1304,6 +1307,7 @@
|
|
|
1304
1307
|
"remote-kubernetes": "Uzak Kubernetes"
|
|
1305
1308
|
},
|
|
1306
1309
|
"kubernetesEngine": {
|
|
1310
|
+
"localK3sHint": "Bu makinedeki yerel bir k3s/k3d/kind kümesi için önceden dolduruldu. Bir ServiceAccount'u bir role bağlayın, `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) ile token'ını oluşturun ve aşağıya yapıştırın. Ardından ortam URL'sinin nasıl türetileceğini seçin ve kümeniz farklı bir bağlantı noktasını dinliyorsa API sunucu URL'sini düzenleyin.",
|
|
1307
1311
|
"label": "Bağlantı etiketi",
|
|
1308
1312
|
"labelPlaceholder": "Önizleme kümesi",
|
|
1309
1313
|
"apiServerUrl": "API sunucu URL'si",
|
|
@@ -1360,6 +1364,8 @@
|
|
|
1360
1364
|
"engineLabel": "Motor",
|
|
1361
1365
|
"customHandlerTitle": "remote-custom işleyici",
|
|
1362
1366
|
"customTypeLabel": "Özel tür",
|
|
1367
|
+
"customBackendLabel": "Arka uç",
|
|
1368
|
+
"customBackendHelp": "Bu özel ortamı hangi kayıtlı arka ucun sağlayacağı.",
|
|
1363
1369
|
"customConnected": "İşleyici bağlandı.",
|
|
1364
1370
|
"saved": "İşleyici kaydedildi",
|
|
1365
1371
|
"removed": "İşleyici kaldırıldı",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
"none": "Маніфести Kubernetes або файл Compose не виявлено.",
|
|
447
447
|
"applied": "Запропоновано конфігурацію {type}. Перегляньте та скоригуйте поля нижче.",
|
|
448
448
|
"overlayTitle": "Тимчасовий overlay",
|
|
449
|
+
"serviceDirTitle": "Тека розгортання сервісу",
|
|
450
|
+
"manifestRootTitle": "Розташування маніфесту",
|
|
451
|
+
"composeServiceTitle": "Сервіс Compose",
|
|
449
452
|
"urlSource": "Запропоноване джерело URL середовища: {source}. Цим керує обробник робочого простору; налаштуйте його там.",
|
|
450
453
|
"namespace": "Маніфести фіксують простір імен \"{namespace}\"; рекомендуємо дотримуватися його в обробнику робочого простору.",
|
|
451
454
|
"confidenceHigh": "Виявлено",
|
|
@@ -1753,6 +1756,7 @@
|
|
|
1753
1756
|
"remote-kubernetes": "Віддалений Kubernetes"
|
|
1754
1757
|
},
|
|
1755
1758
|
"kubernetesEngine": {
|
|
1759
|
+
"localK3sHint": "Попередньо заповнено для локального кластера k3s/k3d/kind на цьому комп'ютері. Прив'яжіть ServiceAccount до ролі, згенеруйте його токен командою `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) і вставте його нижче. Потім виберіть, як визначається URL середовища, і змініть URL сервера API, якщо ваш кластер слухає на іншому порту.",
|
|
1756
1760
|
"label": "Мітка з'єднання",
|
|
1757
1761
|
"labelPlaceholder": "Кластер попереднього перегляду",
|
|
1758
1762
|
"apiServerUrl": "URL сервера API",
|
|
@@ -1809,6 +1813,8 @@
|
|
|
1809
1813
|
"engineLabel": "Рушій",
|
|
1810
1814
|
"customHandlerTitle": "Обробник remote-custom",
|
|
1811
1815
|
"customTypeLabel": "Власний тип",
|
|
1816
|
+
"customBackendLabel": "Бекенд",
|
|
1817
|
+
"customBackendHelp": "Який зареєстрований бекенд забезпечує це користувацьке середовище.",
|
|
1812
1818
|
"customConnected": "Обробник підключено.",
|
|
1813
1819
|
"saved": "Обробник збережено",
|
|
1814
1820
|
"removed": "Обробник видалено",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "^3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.71.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|