@cat-factory/app 0.89.0 → 0.91.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/board/RecurringPipelineModal.vue +1 -0
- package/app/components/gates/GateResultView.vue +40 -2
- package/app/components/settings/KubernetesEnvironmentForm.vue +5 -3
- package/app/components/settings/ProviderConnectionTab.vue +115 -28
- package/app/utils/catalog.ts +27 -0
- package/i18n/locales/en.json +7 -1
- package/i18n/locales/es.json +7 -1
- package/i18n/locales/fr.json +7 -1
- package/i18n/locales/he.json +7 -1
- package/i18n/locales/ja.json +7 -1
- package/i18n/locales/pl.json +7 -1
- package/i18n/locales/tr.json +7 -1
- package/i18n/locales/uk.json +7 -1
- package/package.json +2 -2
- package/app/components/settings/KubernetesRunnerForm.vue +0 -252
|
@@ -86,6 +86,7 @@ const selectedPipelineLabel = computed(
|
|
|
86
86
|
const template = computed<ScheduleTemplate>(() => {
|
|
87
87
|
if (pipelineId.value === 'pl_tech_debt') return 'tech-debt'
|
|
88
88
|
if (pipelineId.value === 'pl_dep_update') return 'dep-update'
|
|
89
|
+
if (pipelineId.value === 'pl_bug_triage') return 'bug-triage'
|
|
89
90
|
return 'custom'
|
|
90
91
|
})
|
|
91
92
|
const isTechDebt = computed(() => template.value === 'tech-debt')
|
|
@@ -35,9 +35,16 @@ const gate = computed<GateStepState | null>(() => step.value?.gate ?? null)
|
|
|
35
35
|
|
|
36
36
|
const isCi = computed(() => step.value?.agentKind === 'ci')
|
|
37
37
|
const isHumanReview = computed(() => step.value?.agentKind === 'human-review')
|
|
38
|
+
const isDocQuality = computed(() => step.value?.agentKind === 'doc-quality')
|
|
38
39
|
const meta = computed(() => agentKindMeta(step.value?.agentKind ?? 'ci'))
|
|
39
40
|
const helperKind = computed(() =>
|
|
40
|
-
isHumanReview.value
|
|
41
|
+
isHumanReview.value
|
|
42
|
+
? 'fixer'
|
|
43
|
+
: isCi.value
|
|
44
|
+
? 'ci-fixer'
|
|
45
|
+
: isDocQuality.value
|
|
46
|
+
? 'doc-fixer'
|
|
47
|
+
: 'conflict-resolver',
|
|
41
48
|
)
|
|
42
49
|
const helperMeta = computed(() => agentKindMeta(helperKind.value))
|
|
43
50
|
|
|
@@ -46,7 +53,9 @@ const subtitle = computed(() =>
|
|
|
46
53
|
? t('gates.subtitle.humanReview')
|
|
47
54
|
: isCi.value
|
|
48
55
|
? t('gates.subtitle.ci')
|
|
49
|
-
:
|
|
56
|
+
: isDocQuality.value
|
|
57
|
+
? t('gates.subtitle.docQuality')
|
|
58
|
+
: t('gates.subtitle.conflicts'),
|
|
50
59
|
)
|
|
51
60
|
|
|
52
61
|
// Human-review: approval progress + the freeform "request a fix" control.
|
|
@@ -316,6 +325,35 @@ const conflictVerdict = computed(() => {
|
|
|
316
325
|
</p>
|
|
317
326
|
</template>
|
|
318
327
|
|
|
328
|
+
<!-- Doc quality: the deterministic structural findings the gate raised -->
|
|
329
|
+
<template v-else-if="isDocQuality">
|
|
330
|
+
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
331
|
+
{{ t('gates.docQuality.findings') }}
|
|
332
|
+
</h3>
|
|
333
|
+
<div
|
|
334
|
+
v-if="gate.lastFailureSummary"
|
|
335
|
+
class="relative rounded-md border border-slate-800 bg-slate-950/40 px-3 py-2"
|
|
336
|
+
>
|
|
337
|
+
<CopyButton :text="gate.lastFailureSummary" class="absolute end-1 top-1" />
|
|
338
|
+
<p class="whitespace-pre-wrap pe-8 text-[12px] leading-relaxed text-slate-300">
|
|
339
|
+
{{ gate.lastFailureSummary }}
|
|
340
|
+
</p>
|
|
341
|
+
</div>
|
|
342
|
+
<p v-else class="text-[13px] leading-relaxed text-slate-300">
|
|
343
|
+
{{ t('gates.docQuality.findingsFallback') }}
|
|
344
|
+
</p>
|
|
345
|
+
<a
|
|
346
|
+
v-if="prUrl"
|
|
347
|
+
:href="prUrl"
|
|
348
|
+
target="_blank"
|
|
349
|
+
rel="noopener"
|
|
350
|
+
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200 hover:underline"
|
|
351
|
+
>
|
|
352
|
+
{{ t('gates.docQuality.viewPr') }}
|
|
353
|
+
<UIcon name="i-lucide-external-link" class="h-3 w-3" />
|
|
354
|
+
</a>
|
|
355
|
+
</template>
|
|
356
|
+
|
|
319
357
|
<!-- Conflicts: verdict + the resolver's account of what it left -->
|
|
320
358
|
<template v-else>
|
|
321
359
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
// environment tab's backend-type selector (the other being the BYO HTTP manifest editor).
|
|
4
4
|
// It builds the discriminated `{ kind: 'kubernetes', kubernetes }` config plus the
|
|
5
5
|
// `apiToken` secret bundle and emits test/save to the parent tab (which calls the shared
|
|
6
|
-
// provider-connections store). The K8s env config
|
|
7
|
-
// (
|
|
8
|
-
//
|
|
6
|
+
// provider-connections store). The K8s env config has manifest-source + URL-derivation
|
|
7
|
+
// (discriminated-union) fields a flat descriptor form can't yet express, so — unlike the
|
|
8
|
+
// runner backends, which now self-describe via `configTemplate` and render through the
|
|
9
|
+
// generic form — the environment axis keeps this bespoke form until it, too, is descriptor-
|
|
10
|
+
// driven (see docs/initiatives/descriptor-driven-infra-forms.md).
|
|
9
11
|
import { computed, reactive, ref, watch } from 'vue'
|
|
10
12
|
import { KUBERNETES_ENV_TOKEN_SECRET_KEY } from '@cat-factory/contracts'
|
|
11
13
|
import type { ProviderConnection } from '~/types/providerConnections'
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// One tab of the Infrastructure window — the connect surface for a single provider kind
|
|
3
3
|
// (container agents → runner pool, or test environments → environment provider). Both
|
|
4
|
-
// self-describe via a ProviderDescriptor, so this renders
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// self-describe via a ProviderDescriptor, so this renders every backend WITHOUT hard-coding
|
|
5
|
+
// which optional kinds (Kubernetes, EKS, a custom kind) exist:
|
|
6
|
+
// - a NATIVE MANIFEST provider (ships a `manifestTemplate`) → the flat field form, whose
|
|
7
|
+
// values are overlaid back onto the manifest before saving.
|
|
8
|
+
// - a NATIVE CONFIG backend (ships a `configTemplate` — the runner backends Kubernetes/EKS
|
|
9
|
+
// and any custom native kind) → the SAME flat field form, whose values are overlaid onto
|
|
10
|
+
// the discriminated `{ kind, <payload> }` config the backend described. The SPA never
|
|
11
|
+
// names a backend; it reads the single payload key off the skeleton.
|
|
7
12
|
// - a MANIFEST-driven provider (no template) → the full JSON manifest editor
|
|
8
13
|
// (ProviderManifestEditor), which replaces the old "use the API" disclaimer.
|
|
9
14
|
import { computed, ref, toRaw, watch } from 'vue'
|
|
10
|
-
import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
15
|
+
import type { ProviderConfigField, ProviderConnectionKind } from '~/types/providerConnections'
|
|
11
16
|
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
12
17
|
import ProviderManifestEditor from '~/components/settings/ProviderManifestEditor.vue'
|
|
13
|
-
import KubernetesRunnerForm from '~/components/settings/KubernetesRunnerForm.vue'
|
|
14
18
|
import KubernetesEnvironmentForm from '~/components/settings/KubernetesEnvironmentForm.vue'
|
|
15
19
|
|
|
16
20
|
const props = defineProps<{
|
|
@@ -56,17 +60,28 @@ const testResult = ref<{ ok: boolean; message?: string } | null>(null)
|
|
|
56
60
|
const testing = ref(false)
|
|
57
61
|
const busy = ref(false)
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
// A native provider renders the friendly flat field form. Two flavours self-describe it:
|
|
64
|
+
// `manifestTemplate` (overlay onto a manifest) and `configTemplate` (overlay onto a
|
|
65
|
+
// discriminated backend config — the Kubernetes/EKS/custom runner backends).
|
|
66
|
+
const isNativeManifest = computed(() => !!descriptor.value?.manifestTemplate)
|
|
67
|
+
const isNativeConfig = computed(() => !!descriptor.value?.configTemplate)
|
|
68
|
+
const isNative = computed(() => isNativeManifest.value || isNativeConfig.value)
|
|
61
69
|
const secretFieldCount = computed(
|
|
62
70
|
() => (descriptor.value?.configFields ?? []).filter((f) => f.secret).length,
|
|
63
71
|
)
|
|
64
72
|
const hasSecretFields = computed(() => secretFieldCount.value > 0)
|
|
65
73
|
|
|
66
|
-
// Seed the flat-form draft from the
|
|
67
|
-
//
|
|
74
|
+
// Seed the flat-form draft from the CURRENT non-secret config so an edit starts populated.
|
|
75
|
+
// Secret fields are never prefilled. A `configTemplate` backend gets its flat values straight
|
|
76
|
+
// from `descriptor.values`; a `manifestTemplate` backend reads them off the saved manifest
|
|
77
|
+
// (baseUrl + providerConfig). On a fresh Kubernetes connect a `k3s` preset prefills local defaults.
|
|
68
78
|
function resetDraft() {
|
|
69
79
|
testResult.value = null
|
|
80
|
+
if (isNativeConfig.value) {
|
|
81
|
+
values.value = { ...descriptor.value?.values }
|
|
82
|
+
applyPresetDefaults()
|
|
83
|
+
return
|
|
84
|
+
}
|
|
70
85
|
const saved = descriptor.value?.savedManifest
|
|
71
86
|
const cfg = (saved?.providerConfig as Record<string, unknown> | undefined) ?? {}
|
|
72
87
|
const next: Record<string, string> = {}
|
|
@@ -82,6 +97,24 @@ function resetDraft() {
|
|
|
82
97
|
values.value = next
|
|
83
98
|
}
|
|
84
99
|
|
|
100
|
+
// The low-config `k3s` preset (an execution-axis radio the picker synthesises) prefills the
|
|
101
|
+
// local-cluster defaults into the generic Kubernetes runner form. Only on a FRESH connect —
|
|
102
|
+
// never clobbering a stored connection's values. Kept SPA-local because the preset is a UI
|
|
103
|
+
// affordance of the picker, not a backend concept.
|
|
104
|
+
function applyPresetDefaults() {
|
|
105
|
+
if (props.preset !== 'k3s' || connection.value) return
|
|
106
|
+
const seed: Record<string, string> = {
|
|
107
|
+
label: 'Local k3s',
|
|
108
|
+
apiServerUrl: 'https://127.0.0.1:6443',
|
|
109
|
+
namespace: 'cat-factory',
|
|
110
|
+
insecureSkipTlsVerify: 'true',
|
|
111
|
+
}
|
|
112
|
+
if (props.suggestedImage) seed.image = props.suggestedImage
|
|
113
|
+
for (const [k, val] of Object.entries(seed)) {
|
|
114
|
+
if (!(values.value[k] ?? '').trim()) values.value[k] = val
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
85
118
|
/** A flat-form field is satisfied when filled now, or already stored, or it has a default. */
|
|
86
119
|
function satisfied(key: string): boolean {
|
|
87
120
|
const f = descriptor.value?.configFields.find((cf) => cf.key === key)
|
|
@@ -124,6 +157,51 @@ function buildManifestPayload(): {
|
|
|
124
157
|
return { manifest, secrets, backendKind: props.backendKind }
|
|
125
158
|
}
|
|
126
159
|
|
|
160
|
+
/** Coerce a flat string form value to the JSON type the backend config expects for its field. */
|
|
161
|
+
function coerceFieldValue(field: ProviderConfigField, raw: string): unknown {
|
|
162
|
+
if (field.type === 'number') return Number(raw)
|
|
163
|
+
if (field.type === 'checkbox') return raw === 'true'
|
|
164
|
+
return raw
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Overlay the flat field values onto a NATIVE backend's discriminated `configTemplate`. The
|
|
169
|
+
* skeleton is `{ kind, <payload> }`, so every non-secret field is written to the single
|
|
170
|
+
* non-`kind` payload key (typed via the field's `type`), each secret to the write-only bundle,
|
|
171
|
+
* and a cleared field is dropped so it reverts to absent. Because the template is the STORED
|
|
172
|
+
* config on an edit, advanced API-only keys the flat form never renders are preserved.
|
|
173
|
+
*/
|
|
174
|
+
function buildConfigPayload(): {
|
|
175
|
+
config: Record<string, unknown>
|
|
176
|
+
secrets: Record<string, string>
|
|
177
|
+
} | null {
|
|
178
|
+
const template = descriptor.value?.configTemplate
|
|
179
|
+
if (!template) return null
|
|
180
|
+
const config: Record<string, unknown> = structuredClone(toRaw(template))
|
|
181
|
+
const payloadKey = Object.keys(config).find((k) => k !== 'kind')
|
|
182
|
+
if (!payloadKey) return null
|
|
183
|
+
const payload: Record<string, unknown> = {
|
|
184
|
+
...(config[payloadKey] as Record<string, unknown> | undefined),
|
|
185
|
+
}
|
|
186
|
+
const secrets: Record<string, string> = {}
|
|
187
|
+
for (const f of descriptor.value?.configFields ?? []) {
|
|
188
|
+
const raw = (values.value[f.key] ?? '').trim()
|
|
189
|
+
if (f.secret) {
|
|
190
|
+
if (raw) secrets[f.key] = raw
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
if (!raw) delete payload[f.key]
|
|
194
|
+
else payload[f.key] = coerceFieldValue(f, raw)
|
|
195
|
+
}
|
|
196
|
+
config[payloadKey] = payload
|
|
197
|
+
return { config, secrets }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The payload for the active native flavour (discriminated config or manifest overlay). */
|
|
201
|
+
function buildFlatPayload() {
|
|
202
|
+
return isNativeConfig.value ? buildConfigPayload() : buildManifestPayload()
|
|
203
|
+
}
|
|
204
|
+
|
|
127
205
|
function notifyError(title: string, e: unknown) {
|
|
128
206
|
toast.add({
|
|
129
207
|
title,
|
|
@@ -141,9 +219,9 @@ function toastSaved() {
|
|
|
141
219
|
})
|
|
142
220
|
}
|
|
143
221
|
|
|
144
|
-
// --- Native flat-form actions
|
|
222
|
+
// --- Native flat-form actions (both manifest-overlay and config-overlay flavours) ---
|
|
145
223
|
async function testNative() {
|
|
146
|
-
const payload =
|
|
224
|
+
const payload = buildFlatPayload()
|
|
147
225
|
if (!payload) return
|
|
148
226
|
testing.value = true
|
|
149
227
|
testResult.value = null
|
|
@@ -159,7 +237,7 @@ async function testNative() {
|
|
|
159
237
|
async function saveNative() {
|
|
160
238
|
busy.value = true
|
|
161
239
|
try {
|
|
162
|
-
const payload =
|
|
240
|
+
const payload = buildFlatPayload()
|
|
163
241
|
if (payload) await store.register(props.kind, payload)
|
|
164
242
|
emit('connected')
|
|
165
243
|
resetDraft()
|
|
@@ -332,23 +410,12 @@ function fieldHelp(key: string): string | undefined {
|
|
|
332
410
|
}}
|
|
333
411
|
</div>
|
|
334
412
|
|
|
335
|
-
<!-- Native Kubernetes
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
:preset="preset"
|
|
340
|
-
:suggested-image="suggestedImage"
|
|
341
|
-
:supports-test="descriptor.supportsTest"
|
|
342
|
-
:testing="testing"
|
|
343
|
-
:busy="busy"
|
|
344
|
-
:test-result="testResult"
|
|
345
|
-
@test="testConfig"
|
|
346
|
-
@save="saveConfig"
|
|
347
|
-
/>
|
|
348
|
-
|
|
349
|
-
<!-- Native Kubernetes ephemeral-environment backend (environment). -->
|
|
413
|
+
<!-- Native Kubernetes ephemeral-environment backend (environment). The runner-pool
|
|
414
|
+
Kubernetes/EKS backends now self-describe via `configTemplate` and render through the
|
|
415
|
+
generic flat form below — no per-kind component. The env axis keeps its bespoke form
|
|
416
|
+
until it, too, is descriptor-driven (see docs/initiatives/descriptor-driven-infra-forms.md). -->
|
|
350
417
|
<KubernetesEnvironmentForm
|
|
351
|
-
v-
|
|
418
|
+
v-if="kind === 'environment' && backendKind === 'kubernetes'"
|
|
352
419
|
:connection="connection"
|
|
353
420
|
:supports-test="descriptor.supportsTest"
|
|
354
421
|
:testing="testing"
|
|
@@ -396,6 +463,26 @@ function fieldHelp(key: string): string | undefined {
|
|
|
396
463
|
:items="(field.options ?? []).map((o) => ({ label: o.label, value: o.value }))"
|
|
397
464
|
:placeholder="field.default ?? field.placeholder"
|
|
398
465
|
/>
|
|
466
|
+
<USwitch
|
|
467
|
+
v-else-if="field.type === 'checkbox'"
|
|
468
|
+
:model-value="values[field.key] === 'true'"
|
|
469
|
+
@update:model-value="values[field.key] = $event ? 'true' : 'false'"
|
|
470
|
+
/>
|
|
471
|
+
<UTextarea
|
|
472
|
+
v-else-if="field.type === 'textarea'"
|
|
473
|
+
v-model="values[field.key]"
|
|
474
|
+
:rows="4"
|
|
475
|
+
class="w-full font-mono"
|
|
476
|
+
:placeholder="field.default ?? field.placeholder"
|
|
477
|
+
/>
|
|
478
|
+
<UInput
|
|
479
|
+
v-else-if="field.type === 'number'"
|
|
480
|
+
:model-value="values[field.key] ?? ''"
|
|
481
|
+
type="number"
|
|
482
|
+
class="font-mono"
|
|
483
|
+
:placeholder="field.default ?? field.placeholder"
|
|
484
|
+
@update:model-value="values[field.key] = String($event ?? '')"
|
|
485
|
+
/>
|
|
399
486
|
<UInput
|
|
400
487
|
v-else
|
|
401
488
|
v-model="values[field.key]"
|
package/app/utils/catalog.ts
CHANGED
|
@@ -410,6 +410,19 @@ export const SYSTEM_AGENT_META: Record<string, AgentArchetype> = {
|
|
|
410
410
|
description:
|
|
411
411
|
'Files a tracker ticket (GitHub issue / Jira) from the analysis before work starts.',
|
|
412
412
|
},
|
|
413
|
+
// The inbound dual of `tracker`: a one-shot engine step that PULLS one matching open issue
|
|
414
|
+
// from the schedule's configured tracker board, marks it in-progress with a "taken by
|
|
415
|
+
// cat-factory" comment, and reseeds the recurring block from it. Runs no model; seeded only
|
|
416
|
+
// into the recurring bug-triage pipeline, so it is a display-metadata system kind (like
|
|
417
|
+
// `tracker`), not a palette archetype.
|
|
418
|
+
'bug-intake': {
|
|
419
|
+
kind: 'bug-intake',
|
|
420
|
+
label: 'Bug Intake',
|
|
421
|
+
icon: 'i-lucide-inbox',
|
|
422
|
+
color: '#fb923c',
|
|
423
|
+
description:
|
|
424
|
+
'Pulls one matching open issue from the configured tracker board, marks it in-progress, and seeds the run from it.',
|
|
425
|
+
},
|
|
413
426
|
conflicts: {
|
|
414
427
|
kind: 'conflicts',
|
|
415
428
|
label: 'Conflicts Gate',
|
|
@@ -437,6 +450,20 @@ export const SYSTEM_AGENT_META: Record<string, AgentArchetype> = {
|
|
|
437
450
|
// of the generic prose step-detail panel. Shared with the conflicts gate.
|
|
438
451
|
resultView: 'gate',
|
|
439
452
|
},
|
|
453
|
+
// The forward document pipelines' structural gate. Seed-only (no `category`, like ci /
|
|
454
|
+
// conflicts) — it's part of `pl_document(_quick)`, not a standing palette block — but it
|
|
455
|
+
// needs display metadata so timelines/saved pipelines render it. Its helper `doc-fixer`
|
|
456
|
+
// is a registered kind, so it arrives via the workspace snapshot's `customAgentKinds`.
|
|
457
|
+
'doc-quality': {
|
|
458
|
+
kind: 'doc-quality',
|
|
459
|
+
label: 'Doc Quality Gate',
|
|
460
|
+
icon: 'i-lucide-file-check-2',
|
|
461
|
+
color: '#818cf8',
|
|
462
|
+
description:
|
|
463
|
+
'Checks the drafted document for required sections, placeholders, links and heading structure, looping the doc fixer on problems.',
|
|
464
|
+
// Opens the dedicated gate window (verdict, attempts, the document findings).
|
|
465
|
+
resultView: 'gate',
|
|
466
|
+
},
|
|
440
467
|
'ci-fixer': {
|
|
441
468
|
kind: 'ci-fixer',
|
|
442
469
|
label: 'CI Fixer',
|
package/i18n/locales/en.json
CHANGED
|
@@ -3030,7 +3030,8 @@
|
|
|
3030
3030
|
"subtitle": {
|
|
3031
3031
|
"humanReview": "Waits for a human code review on the PR, looping the fixer on comments",
|
|
3032
3032
|
"ci": "Gates the PR on green CI, looping the CI fixer on failure",
|
|
3033
|
-
"conflicts": "Gates the PR on a clean merge, looping the resolver on conflicts"
|
|
3033
|
+
"conflicts": "Gates the PR on a clean merge, looping the resolver on conflicts",
|
|
3034
|
+
"docQuality": "Checks the drafted document's structure, looping the doc fixer on problems"
|
|
3034
3035
|
},
|
|
3035
3036
|
"status": {
|
|
3036
3037
|
"passed": "Passed",
|
|
@@ -3071,6 +3072,11 @@
|
|
|
3071
3072
|
"mergeability": "Mergeability",
|
|
3072
3073
|
"viewPr": "View pull request on GitHub"
|
|
3073
3074
|
},
|
|
3075
|
+
"docQuality": {
|
|
3076
|
+
"findings": "Document issues",
|
|
3077
|
+
"findingsFallback": "The document passed the structural checks.",
|
|
3078
|
+
"viewPr": "View pull request on GitHub"
|
|
3079
|
+
},
|
|
3074
3080
|
"attemptsHeading": "{helper} attempts",
|
|
3075
3081
|
"attempt": "Attempt {number}",
|
|
3076
3082
|
"attemptInstructions": "Handed to {helper}",
|
package/i18n/locales/es.json
CHANGED
|
@@ -2938,7 +2938,8 @@
|
|
|
2938
2938
|
"subtitle": {
|
|
2939
2939
|
"humanReview": "Espera una revisión de código humana en el PR, repitiendo el corrector ante los comentarios",
|
|
2940
2940
|
"ci": "Bloquea el PR hasta que la CI esté verde, repitiendo el corrector de CI ante los fallos",
|
|
2941
|
-
"conflicts": "Bloquea el PR hasta una fusión limpia, repitiendo el resolutor ante los conflictos"
|
|
2941
|
+
"conflicts": "Bloquea el PR hasta una fusión limpia, repitiendo el resolutor ante los conflictos",
|
|
2942
|
+
"docQuality": "Comprueba la estructura del documento redactado, repitiendo el corrector de documentos ante los problemas"
|
|
2942
2943
|
},
|
|
2943
2944
|
"status": {
|
|
2944
2945
|
"passed": "Aprobado",
|
|
@@ -2979,6 +2980,11 @@
|
|
|
2979
2980
|
"mergeability": "Fusionabilidad",
|
|
2980
2981
|
"viewPr": "Ver la pull request en GitHub"
|
|
2981
2982
|
},
|
|
2983
|
+
"docQuality": {
|
|
2984
|
+
"findings": "Problemas del documento",
|
|
2985
|
+
"findingsFallback": "El documento superó las comprobaciones estructurales.",
|
|
2986
|
+
"viewPr": "Ver la pull request en GitHub"
|
|
2987
|
+
},
|
|
2982
2988
|
"attemptsHeading": "Intentos de {helper}",
|
|
2983
2989
|
"attempt": "Intento {number}",
|
|
2984
2990
|
"attemptInstructions": "Entregado a {helper}",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -2938,7 +2938,8 @@
|
|
|
2938
2938
|
"subtitle": {
|
|
2939
2939
|
"humanReview": "Attend une revue de code humaine sur la PR, en relançant le correcteur à chaque commentaire",
|
|
2940
2940
|
"ci": "Bloque la PR jusqu'à une CI verte, en relançant le correcteur de CI en cas d'échec",
|
|
2941
|
-
"conflicts": "Bloque la PR jusqu'à une fusion propre, en relançant le résolveur en cas de conflits"
|
|
2941
|
+
"conflicts": "Bloque la PR jusqu'à une fusion propre, en relançant le résolveur en cas de conflits",
|
|
2942
|
+
"docQuality": "Vérifie la structure du document rédigé, en relançant le correcteur de documents en cas de problème"
|
|
2942
2943
|
},
|
|
2943
2944
|
"status": {
|
|
2944
2945
|
"passed": "Réussi",
|
|
@@ -2979,6 +2980,11 @@
|
|
|
2979
2980
|
"mergeability": "Fusionnabilité",
|
|
2980
2981
|
"viewPr": "Voir la pull request sur GitHub"
|
|
2981
2982
|
},
|
|
2983
|
+
"docQuality": {
|
|
2984
|
+
"findings": "Problèmes du document",
|
|
2985
|
+
"findingsFallback": "Le document a réussi les vérifications structurelles.",
|
|
2986
|
+
"viewPr": "Voir la pull request sur GitHub"
|
|
2987
|
+
},
|
|
2982
2988
|
"attemptsHeading": "Tentatives du {helper}",
|
|
2983
2989
|
"attempt": "Tentative {number}",
|
|
2984
2990
|
"attemptInstructions": "Transmis à {helper}",
|
package/i18n/locales/he.json
CHANGED
|
@@ -2943,7 +2943,8 @@
|
|
|
2943
2943
|
"subtitle": {
|
|
2944
2944
|
"humanReview": "ממתין לסקירת קוד אנושית על ה-PR, ומפעיל בלולאה את המתקן על הערות",
|
|
2945
2945
|
"ci": "מגדר את ה-PR על CI ירוק, ומפעיל בלולאה את מתקן ה-CI בכישלון",
|
|
2946
|
-
"conflicts": "מגדר את ה-PR על מיזוג נקי, ומפעיל בלולאה את הפותר בהתנגשויות"
|
|
2946
|
+
"conflicts": "מגדר את ה-PR על מיזוג נקי, ומפעיל בלולאה את הפותר בהתנגשויות",
|
|
2947
|
+
"docQuality": "בודק את מבנה המסמך שנוסח, ומפעיל בלולאה את מתקן המסמכים בבעיות"
|
|
2947
2948
|
},
|
|
2948
2949
|
"status": {
|
|
2949
2950
|
"passed": "עבר",
|
|
@@ -2984,6 +2985,11 @@
|
|
|
2984
2985
|
"mergeability": "יכולת מיזוג",
|
|
2985
2986
|
"viewPr": "הצג את בקשת המשיכה ב-GitHub"
|
|
2986
2987
|
},
|
|
2988
|
+
"docQuality": {
|
|
2989
|
+
"findings": "בעיות במסמך",
|
|
2990
|
+
"findingsFallback": "המסמך עבר את בדיקות המבנה.",
|
|
2991
|
+
"viewPr": "הצג את בקשת המשיכה ב-GitHub"
|
|
2992
|
+
},
|
|
2987
2993
|
"attemptsHeading": "ניסיונות {helper}",
|
|
2988
2994
|
"attempt": "ניסיון {number}",
|
|
2989
2995
|
"attemptInstructions": "נמסר ל־{helper}",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -2947,7 +2947,8 @@
|
|
|
2947
2947
|
"subtitle": {
|
|
2948
2948
|
"humanReview": "PR の人によるコードレビューを待ち、コメントに応じて fixer をループします",
|
|
2949
2949
|
"ci": "CI がグリーンになるまで PR をゲートし、失敗時に CI fixer をループします",
|
|
2950
|
-
"conflicts": "クリーンなマージになるまで PR をゲートし、コンフリクト時に resolver をループします"
|
|
2950
|
+
"conflicts": "クリーンなマージになるまで PR をゲートし、コンフリクト時に resolver をループします",
|
|
2951
|
+
"docQuality": "作成された文書の構造をチェックし、問題があれば doc fixer をループします"
|
|
2951
2952
|
},
|
|
2952
2953
|
"status": {
|
|
2953
2954
|
"passed": "合格",
|
|
@@ -2988,6 +2989,11 @@
|
|
|
2988
2989
|
"mergeability": "マージ可能性",
|
|
2989
2990
|
"viewPr": "GitHub でプルリクエストを表示"
|
|
2990
2991
|
},
|
|
2992
|
+
"docQuality": {
|
|
2993
|
+
"findings": "文書の問題",
|
|
2994
|
+
"findingsFallback": "文書は構造チェックに合格しました。",
|
|
2995
|
+
"viewPr": "GitHub でプルリクエストを表示"
|
|
2996
|
+
},
|
|
2991
2997
|
"attemptsHeading": "{helper} の試行",
|
|
2992
2998
|
"attempt": "試行 {number}",
|
|
2993
2999
|
"attemptInstructions": "{helper} への指示",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -2938,7 +2938,8 @@
|
|
|
2938
2938
|
"subtitle": {
|
|
2939
2939
|
"humanReview": "Czeka na recenzję kodu przez człowieka na PR, ponawiając korektora po komentarzach",
|
|
2940
2940
|
"ci": "Blokuje PR do zielonego CI, ponawiając korektora CI przy niepowodzeniu",
|
|
2941
|
-
"conflicts": "Blokuje PR do czystego scalenia, ponawiając rozwiązywacza przy konfliktach"
|
|
2941
|
+
"conflicts": "Blokuje PR do czystego scalenia, ponawiając rozwiązywacza przy konfliktach",
|
|
2942
|
+
"docQuality": "Sprawdza strukturę przygotowanego dokumentu, ponawiając korektora dokumentów przy problemach"
|
|
2942
2943
|
},
|
|
2943
2944
|
"status": {
|
|
2944
2945
|
"passed": "Zaliczono",
|
|
@@ -2979,6 +2980,11 @@
|
|
|
2979
2980
|
"mergeability": "Możliwość scalenia",
|
|
2980
2981
|
"viewPr": "Zobacz pull request na GitHub"
|
|
2981
2982
|
},
|
|
2983
|
+
"docQuality": {
|
|
2984
|
+
"findings": "Problemy dokumentu",
|
|
2985
|
+
"findingsFallback": "Dokument przeszedł kontrole strukturalne.",
|
|
2986
|
+
"viewPr": "Zobacz pull request na GitHub"
|
|
2987
|
+
},
|
|
2982
2988
|
"attemptsHeading": "Próby: {helper}",
|
|
2983
2989
|
"attempt": "Próba {number}",
|
|
2984
2990
|
"attemptInstructions": "Przekazano do: {helper}",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -2947,7 +2947,8 @@
|
|
|
2947
2947
|
"subtitle": {
|
|
2948
2948
|
"humanReview": "PR üzerinde bir insan kod incelemesini bekler, yorumlar üzerine düzelticiyi döngüye alır",
|
|
2949
2949
|
"ci": "PR'ı yeşil CI üzerinde kapı altına alır, başarısızlıkta CI düzelticiyi döngüye alır",
|
|
2950
|
-
"conflicts": "PR'ı temiz bir birleştirme üzerinde kapı altına alır, çakışmalarda çözücüyü döngüye alır"
|
|
2950
|
+
"conflicts": "PR'ı temiz bir birleştirme üzerinde kapı altına alır, çakışmalarda çözücüyü döngüye alır",
|
|
2951
|
+
"docQuality": "Hazırlanan belgenin yapısını denetler, sorunlarda belge düzelticiyi döngüye alır"
|
|
2951
2952
|
},
|
|
2952
2953
|
"status": {
|
|
2953
2954
|
"passed": "Geçti",
|
|
@@ -2988,6 +2989,11 @@
|
|
|
2988
2989
|
"mergeability": "Birleştirilebilirlik",
|
|
2989
2990
|
"viewPr": "Çekme isteğini GitHub'da görüntüle"
|
|
2990
2991
|
},
|
|
2992
|
+
"docQuality": {
|
|
2993
|
+
"findings": "Belge sorunları",
|
|
2994
|
+
"findingsFallback": "Belge yapısal denetimleri geçti.",
|
|
2995
|
+
"viewPr": "Çekme isteğini GitHub'da görüntüle"
|
|
2996
|
+
},
|
|
2991
2997
|
"attemptsHeading": "{helper} denemeleri",
|
|
2992
2998
|
"attempt": "Deneme {number}",
|
|
2993
2999
|
"attemptInstructions": "{helper}'a iletildi",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -2938,7 +2938,8 @@
|
|
|
2938
2938
|
"subtitle": {
|
|
2939
2939
|
"humanReview": "Чекає на огляд коду людиною в PR, повторюючи виправлювача після коментарів",
|
|
2940
2940
|
"ci": "Блокує PR до зеленого CI, повторюючи виправлювача CI у разі помилки",
|
|
2941
|
-
"conflicts": "Блокує PR до чистого злиття, повторюючи розв'язувача у разі конфліктів"
|
|
2941
|
+
"conflicts": "Блокує PR до чистого злиття, повторюючи розв'язувача у разі конфліктів",
|
|
2942
|
+
"docQuality": "Перевіряє структуру підготовленого документа, повторюючи виправлювача документів у разі проблем"
|
|
2942
2943
|
},
|
|
2943
2944
|
"status": {
|
|
2944
2945
|
"passed": "Пройдено",
|
|
@@ -2979,6 +2980,11 @@
|
|
|
2979
2980
|
"mergeability": "Можливість злиття",
|
|
2980
2981
|
"viewPr": "Переглянути pull request на GitHub"
|
|
2981
2982
|
},
|
|
2983
|
+
"docQuality": {
|
|
2984
|
+
"findings": "Проблеми документа",
|
|
2985
|
+
"findingsFallback": "Документ пройшов структурні перевірки.",
|
|
2986
|
+
"viewPr": "Переглянути pull request на GitHub"
|
|
2987
|
+
},
|
|
2982
2988
|
"attemptsHeading": "Спроби: {helper}",
|
|
2983
2989
|
"attempt": "Спроба {number}",
|
|
2984
2990
|
"attemptInstructions": "Передано: {helper}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.91.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.98.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// The Kubernetes "agent runner backend" connect form — one option of the runner-pool tab's
|
|
3
|
-
// backend-type selector (the other being the manifest pool). It builds the discriminated
|
|
4
|
-
// `{ kind: 'kubernetes', kubernetes }` config + the `apiToken` secret bundle and emits
|
|
5
|
-
// test/save to the parent tab (which calls the shared provider-connections store).
|
|
6
|
-
import { computed, reactive, ref, watch } from 'vue'
|
|
7
|
-
import { KUBERNETES_RUNNER_TOKEN_SECRET_KEY } from '@cat-factory/contracts'
|
|
8
|
-
import type { ProviderConnection } from '~/types/providerConnections'
|
|
9
|
-
|
|
10
|
-
const props = defineProps<{
|
|
11
|
-
connection: ProviderConnection | null
|
|
12
|
-
/** A low-config preset to seed the form with (today: local k3s). */
|
|
13
|
-
preset?: 'k3s'
|
|
14
|
-
/** The deployment's executor image, used to prefill the k3s preset's image field. */
|
|
15
|
-
suggestedImage?: string
|
|
16
|
-
supportsTest: boolean
|
|
17
|
-
testing: boolean
|
|
18
|
-
busy: boolean
|
|
19
|
-
testResult: { ok: boolean; message?: string } | null
|
|
20
|
-
}>()
|
|
21
|
-
|
|
22
|
-
const emit = defineEmits<{
|
|
23
|
-
test: [payload: { config: Record<string, unknown>; secrets: Record<string, string> }]
|
|
24
|
-
save: [payload: { config: Record<string, unknown>; secrets: Record<string, string> }]
|
|
25
|
-
}>()
|
|
26
|
-
|
|
27
|
-
const { t } = useI18n()
|
|
28
|
-
|
|
29
|
-
const form = reactive({
|
|
30
|
-
label: '',
|
|
31
|
-
apiServerUrl: '',
|
|
32
|
-
namespace: '',
|
|
33
|
-
image: '',
|
|
34
|
-
imageUi: '',
|
|
35
|
-
caCertPem: '',
|
|
36
|
-
harnessPort: '',
|
|
37
|
-
insecureSkipTlsVerify: false,
|
|
38
|
-
})
|
|
39
|
-
const apiToken = ref('')
|
|
40
|
-
|
|
41
|
-
// A registered k8s connection exposes its non-secret config, so prefill every non-secret
|
|
42
|
-
// field from it (never the token — secrets are write-only and re-entered on update). This
|
|
43
|
-
// lets an edit change one field without re-typing the whole form.
|
|
44
|
-
watch(
|
|
45
|
-
() => props.connection,
|
|
46
|
-
(c) => {
|
|
47
|
-
if (c?.kind !== 'kubernetes') return
|
|
48
|
-
form.label = c.label
|
|
49
|
-
form.apiServerUrl = c.baseUrl
|
|
50
|
-
const k =
|
|
51
|
-
c.config && (c.config as { kind?: string }).kind === 'kubernetes'
|
|
52
|
-
? (c.config as { kubernetes: Record<string, unknown> }).kubernetes
|
|
53
|
-
: undefined
|
|
54
|
-
if (k) {
|
|
55
|
-
form.namespace = typeof k.namespace === 'string' ? k.namespace : ''
|
|
56
|
-
form.image = typeof k.image === 'string' ? k.image : ''
|
|
57
|
-
form.imageUi = typeof k.imageUi === 'string' ? k.imageUi : ''
|
|
58
|
-
form.caCertPem = typeof k.caCertPem === 'string' ? k.caCertPem : ''
|
|
59
|
-
form.harnessPort = typeof k.harnessPort === 'number' ? String(k.harnessPort) : ''
|
|
60
|
-
form.insecureSkipTlsVerify = k.insecureSkipTlsVerify === true
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
{ immediate: true },
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// Low-config k3s preset: seed the local-cluster defaults so the operator only pastes a
|
|
67
|
-
// ServiceAccount token (and an image, unless the deployment surfaced one). Only seeds a
|
|
68
|
-
// fresh form — never clobbers an existing connection's config on edit.
|
|
69
|
-
watch(
|
|
70
|
-
() => props.preset,
|
|
71
|
-
(preset) => {
|
|
72
|
-
if (preset !== 'k3s' || props.connection) return
|
|
73
|
-
form.label = 'Local k3s'
|
|
74
|
-
form.apiServerUrl = 'https://127.0.0.1:6443'
|
|
75
|
-
form.namespace = 'cat-factory'
|
|
76
|
-
form.insecureSkipTlsVerify = true
|
|
77
|
-
if (props.suggestedImage) form.image = props.suggestedImage
|
|
78
|
-
},
|
|
79
|
-
{ immediate: true },
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
const canSave = computed(
|
|
83
|
-
() =>
|
|
84
|
-
!!form.label.trim() &&
|
|
85
|
-
!!form.apiServerUrl.trim() &&
|
|
86
|
-
!!form.namespace.trim() &&
|
|
87
|
-
!!form.image.trim() &&
|
|
88
|
-
!!apiToken.value.trim(),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
// Why the Connect button is disabled, surfaced as a red hint next to it so a mandatory-field gap
|
|
92
|
-
// is visible rather than a dead button. Lists the empty required fields by their on-screen label.
|
|
93
|
-
const connectBlockedReason = computed(() => {
|
|
94
|
-
if (canSave.value) return ''
|
|
95
|
-
const missing: string[] = []
|
|
96
|
-
if (!form.label.trim()) missing.push(t('settings.providerConnection.kubernetes.label'))
|
|
97
|
-
if (!form.apiServerUrl.trim())
|
|
98
|
-
missing.push(t('settings.providerConnection.kubernetes.apiServerUrl'))
|
|
99
|
-
if (!form.namespace.trim()) missing.push(t('settings.providerConnection.kubernetes.namespace'))
|
|
100
|
-
if (!form.image.trim()) missing.push(t('settings.providerConnection.kubernetes.image'))
|
|
101
|
-
if (!apiToken.value.trim()) missing.push(t('settings.providerConnection.kubernetes.apiToken'))
|
|
102
|
-
return t('settings.providerConnection.form.missingFields', { fields: missing.join(', ') })
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
function buildPayload(): { config: Record<string, unknown>; secrets: Record<string, string> } {
|
|
106
|
-
const kubernetes: Record<string, unknown> = {
|
|
107
|
-
label: form.label.trim(),
|
|
108
|
-
apiServerUrl: form.apiServerUrl.trim(),
|
|
109
|
-
namespace: form.namespace.trim(),
|
|
110
|
-
image: form.image.trim(),
|
|
111
|
-
}
|
|
112
|
-
if (form.imageUi.trim()) kubernetes.imageUi = form.imageUi.trim()
|
|
113
|
-
if (form.caCertPem.trim()) kubernetes.caCertPem = form.caCertPem.trim()
|
|
114
|
-
if (form.insecureSkipTlsVerify) kubernetes.insecureSkipTlsVerify = true
|
|
115
|
-
const port = Number(form.harnessPort)
|
|
116
|
-
if (form.harnessPort.trim() && Number.isFinite(port)) kubernetes.harnessPort = port
|
|
117
|
-
return {
|
|
118
|
-
config: { kind: 'kubernetes', kubernetes },
|
|
119
|
-
secrets: { [KUBERNETES_RUNNER_TOKEN_SECRET_KEY]: apiToken.value.trim() },
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
</script>
|
|
123
|
-
|
|
124
|
-
<template>
|
|
125
|
-
<div class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3">
|
|
126
|
-
<p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
127
|
-
{{
|
|
128
|
-
connection?.kind === 'kubernetes'
|
|
129
|
-
? t('settings.providerConnection.form.updateConfiguration')
|
|
130
|
-
: t('settings.providerConnection.form.connect')
|
|
131
|
-
}}
|
|
132
|
-
</p>
|
|
133
|
-
|
|
134
|
-
<UFormField :label="t('settings.providerConnection.kubernetes.label')">
|
|
135
|
-
<UInput
|
|
136
|
-
v-model="form.label"
|
|
137
|
-
:placeholder="t('settings.providerConnection.kubernetes.labelPlaceholder')"
|
|
138
|
-
/>
|
|
139
|
-
</UFormField>
|
|
140
|
-
|
|
141
|
-
<UFormField
|
|
142
|
-
:label="t('settings.providerConnection.kubernetes.apiServerUrl')"
|
|
143
|
-
:help="t('settings.providerConnection.kubernetes.apiServerUrlHelp')"
|
|
144
|
-
>
|
|
145
|
-
<UInput v-model="form.apiServerUrl" class="font-mono" placeholder="https://10.0.0.1:6443" />
|
|
146
|
-
</UFormField>
|
|
147
|
-
|
|
148
|
-
<UFormField :label="t('settings.providerConnection.kubernetes.namespace')">
|
|
149
|
-
<UInput v-model="form.namespace" class="font-mono" placeholder="cat-factory" />
|
|
150
|
-
</UFormField>
|
|
151
|
-
|
|
152
|
-
<UFormField
|
|
153
|
-
:label="t('settings.providerConnection.kubernetes.image')"
|
|
154
|
-
:help="t('settings.providerConnection.kubernetes.imageHelp')"
|
|
155
|
-
>
|
|
156
|
-
<UInput
|
|
157
|
-
v-model="form.image"
|
|
158
|
-
class="font-mono"
|
|
159
|
-
placeholder="ghcr.io/acme/cat-factory-executor:latest"
|
|
160
|
-
/>
|
|
161
|
-
</UFormField>
|
|
162
|
-
|
|
163
|
-
<UFormField
|
|
164
|
-
:label="
|
|
165
|
-
t('settings.providerConnection.form.optionalLabel', {
|
|
166
|
-
label: t('settings.providerConnection.kubernetes.imageUi'),
|
|
167
|
-
})
|
|
168
|
-
"
|
|
169
|
-
>
|
|
170
|
-
<UInput v-model="form.imageUi" class="font-mono" />
|
|
171
|
-
</UFormField>
|
|
172
|
-
|
|
173
|
-
<UFormField
|
|
174
|
-
:label="t('settings.providerConnection.kubernetes.apiToken')"
|
|
175
|
-
:help="t('settings.providerConnection.kubernetes.apiTokenHelp')"
|
|
176
|
-
>
|
|
177
|
-
<UInput v-model="apiToken" type="password" class="font-mono" />
|
|
178
|
-
</UFormField>
|
|
179
|
-
|
|
180
|
-
<UFormField
|
|
181
|
-
:label="
|
|
182
|
-
t('settings.providerConnection.form.optionalLabel', {
|
|
183
|
-
label: t('settings.providerConnection.kubernetes.caCertPem'),
|
|
184
|
-
})
|
|
185
|
-
"
|
|
186
|
-
:help="t('settings.providerConnection.kubernetes.caCertPemHelp')"
|
|
187
|
-
>
|
|
188
|
-
<UTextarea
|
|
189
|
-
v-model="form.caCertPem"
|
|
190
|
-
:rows="3"
|
|
191
|
-
class="font-mono"
|
|
192
|
-
placeholder="-----BEGIN CERTIFICATE-----"
|
|
193
|
-
/>
|
|
194
|
-
</UFormField>
|
|
195
|
-
|
|
196
|
-
<UFormField :help="t('settings.providerConnection.kubernetes.insecureSkipTlsVerifyHelp')">
|
|
197
|
-
<UCheckbox
|
|
198
|
-
v-model="form.insecureSkipTlsVerify"
|
|
199
|
-
:label="t('settings.providerConnection.kubernetes.insecureSkipTlsVerify')"
|
|
200
|
-
/>
|
|
201
|
-
</UFormField>
|
|
202
|
-
|
|
203
|
-
<UFormField
|
|
204
|
-
:label="
|
|
205
|
-
t('settings.providerConnection.form.optionalLabel', {
|
|
206
|
-
label: t('settings.providerConnection.kubernetes.harnessPort'),
|
|
207
|
-
})
|
|
208
|
-
"
|
|
209
|
-
>
|
|
210
|
-
<UInput v-model="form.harnessPort" type="number" class="font-mono" placeholder="8080" />
|
|
211
|
-
</UFormField>
|
|
212
|
-
|
|
213
|
-
<div v-if="supportsTest" class="flex items-center gap-2">
|
|
214
|
-
<UButton
|
|
215
|
-
color="neutral"
|
|
216
|
-
variant="soft"
|
|
217
|
-
size="sm"
|
|
218
|
-
icon="i-lucide-plug-zap"
|
|
219
|
-
:loading="testing"
|
|
220
|
-
:disabled="!canSave"
|
|
221
|
-
@click="emit('test', buildPayload())"
|
|
222
|
-
>
|
|
223
|
-
{{ t('settings.providerConnection.test.button') }}
|
|
224
|
-
</UButton>
|
|
225
|
-
<span v-if="testResult && testResult.ok" class="text-xs text-emerald-400">
|
|
226
|
-
{{ testResult.message ?? t('settings.providerConnection.test.ok') }}
|
|
227
|
-
</span>
|
|
228
|
-
<span v-else-if="testResult" class="text-xs text-rose-400">
|
|
229
|
-
{{ testResult.message ?? t('settings.providerConnection.test.failed') }}
|
|
230
|
-
</span>
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
<div class="flex items-center justify-end gap-3">
|
|
234
|
-
<p v-if="connectBlockedReason" class="flex-1 text-left text-xs text-rose-400">
|
|
235
|
-
{{ connectBlockedReason }}
|
|
236
|
-
</p>
|
|
237
|
-
<UButton
|
|
238
|
-
color="primary"
|
|
239
|
-
size="sm"
|
|
240
|
-
:loading="busy"
|
|
241
|
-
:disabled="!canSave"
|
|
242
|
-
@click="emit('save', buildPayload())"
|
|
243
|
-
>
|
|
244
|
-
{{
|
|
245
|
-
connection?.kind === 'kubernetes'
|
|
246
|
-
? t('common.save')
|
|
247
|
-
: t('settings.providerConnection.form.connect')
|
|
248
|
-
}}
|
|
249
|
-
</UButton>
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
</template>
|