@cat-factory/app 0.48.1 → 0.48.3
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/documents/DocumentSourceConnectModal.vue +0 -15
- package/app/components/settings/KubernetesRunnerForm.vue +205 -0
- package/app/components/settings/ProviderConnectionTab.vue +76 -1
- package/app/composables/api/providerConnections.ts +24 -5
- package/app/types/providerConnections.ts +20 -4
- package/i18n/locales/en.json +22 -3
- package/i18n/locales/es.json +20 -1
- package/i18n/locales/fr.json +20 -1
- package/i18n/locales/pl.json +20 -1
- package/i18n/locales/uk.json +20 -1
- package/package.json +2 -2
|
@@ -19,10 +19,6 @@ const connection = computed(() =>
|
|
|
19
19
|
source.value ? documents.connectionFor(source.value) : undefined,
|
|
20
20
|
)
|
|
21
21
|
const connected = computed(() => connection.value !== undefined)
|
|
22
|
-
// A `credentialScope: 'user'` source (e.g. Claude Design) stores a PERSONAL credential
|
|
23
|
-
// keyed to the signed-in user and never shared with the workspace — surface that so a
|
|
24
|
-
// member understands they're connecting their own account, not the team's.
|
|
25
|
-
const isPersonal = computed(() => descriptor.value?.credentialScope === 'user')
|
|
26
22
|
|
|
27
23
|
const open = computed({
|
|
28
24
|
get: () => ui.documentConnect !== null,
|
|
@@ -99,17 +95,6 @@ async function disconnect() {
|
|
|
99
95
|
{{ t('documents.connect.intro', { source: descriptor.label }) }}
|
|
100
96
|
</p>
|
|
101
97
|
|
|
102
|
-
<p
|
|
103
|
-
v-if="isPersonal"
|
|
104
|
-
class="flex items-start gap-1.5 text-xs text-amber-400/90"
|
|
105
|
-
data-testid="document-source-personal-note"
|
|
106
|
-
>
|
|
107
|
-
<UIcon name="i-lucide-user" class="mt-0.5 size-3.5 shrink-0" />
|
|
108
|
-
<span>
|
|
109
|
-
{{ t('documents.connect.personalNote') }}
|
|
110
|
-
</span>
|
|
111
|
-
</p>
|
|
112
|
-
|
|
113
98
|
<div class="space-y-3">
|
|
114
99
|
<UFormField
|
|
115
100
|
v-for="field in descriptor.credentialFields"
|
|
@@ -0,0 +1,205 @@
|
|
|
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
|
+
supportsTest: boolean
|
|
13
|
+
testing: boolean
|
|
14
|
+
busy: boolean
|
|
15
|
+
testResult: { ok: boolean; message?: string } | null
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
test: [payload: { config: Record<string, unknown>; secrets: Record<string, string> }]
|
|
20
|
+
save: [payload: { config: Record<string, unknown>; secrets: Record<string, string> }]
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const { t } = useI18n()
|
|
24
|
+
|
|
25
|
+
const form = reactive({
|
|
26
|
+
label: '',
|
|
27
|
+
apiServerUrl: '',
|
|
28
|
+
namespace: '',
|
|
29
|
+
image: '',
|
|
30
|
+
imageUi: '',
|
|
31
|
+
caCertPem: '',
|
|
32
|
+
harnessPort: '',
|
|
33
|
+
})
|
|
34
|
+
const apiToken = ref('')
|
|
35
|
+
|
|
36
|
+
// A registered k8s connection exposes its non-secret config, so prefill every non-secret
|
|
37
|
+
// field from it (never the token — secrets are write-only and re-entered on update). This
|
|
38
|
+
// lets an edit change one field without re-typing the whole form.
|
|
39
|
+
watch(
|
|
40
|
+
() => props.connection,
|
|
41
|
+
(c) => {
|
|
42
|
+
if (c?.kind !== 'kubernetes') return
|
|
43
|
+
form.label = c.label
|
|
44
|
+
form.apiServerUrl = c.baseUrl
|
|
45
|
+
const k =
|
|
46
|
+
c.config && (c.config as { kind?: string }).kind === 'kubernetes'
|
|
47
|
+
? (c.config as { kubernetes: Record<string, unknown> }).kubernetes
|
|
48
|
+
: undefined
|
|
49
|
+
if (k) {
|
|
50
|
+
form.namespace = typeof k.namespace === 'string' ? k.namespace : ''
|
|
51
|
+
form.image = typeof k.image === 'string' ? k.image : ''
|
|
52
|
+
form.imageUi = typeof k.imageUi === 'string' ? k.imageUi : ''
|
|
53
|
+
form.caCertPem = typeof k.caCertPem === 'string' ? k.caCertPem : ''
|
|
54
|
+
form.harnessPort = typeof k.harnessPort === 'number' ? String(k.harnessPort) : ''
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{ immediate: true },
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const canSave = computed(
|
|
61
|
+
() =>
|
|
62
|
+
!!form.label.trim() &&
|
|
63
|
+
!!form.apiServerUrl.trim() &&
|
|
64
|
+
!!form.namespace.trim() &&
|
|
65
|
+
!!form.image.trim() &&
|
|
66
|
+
!!apiToken.value.trim(),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
function buildPayload(): { config: Record<string, unknown>; secrets: Record<string, string> } {
|
|
70
|
+
const kubernetes: Record<string, unknown> = {
|
|
71
|
+
label: form.label.trim(),
|
|
72
|
+
apiServerUrl: form.apiServerUrl.trim(),
|
|
73
|
+
namespace: form.namespace.trim(),
|
|
74
|
+
image: form.image.trim(),
|
|
75
|
+
}
|
|
76
|
+
if (form.imageUi.trim()) kubernetes.imageUi = form.imageUi.trim()
|
|
77
|
+
if (form.caCertPem.trim()) kubernetes.caCertPem = form.caCertPem.trim()
|
|
78
|
+
const port = Number(form.harnessPort)
|
|
79
|
+
if (form.harnessPort.trim() && Number.isFinite(port)) kubernetes.harnessPort = port
|
|
80
|
+
return {
|
|
81
|
+
config: { kind: 'kubernetes', kubernetes },
|
|
82
|
+
secrets: { [KUBERNETES_RUNNER_TOKEN_SECRET_KEY]: apiToken.value.trim() },
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<div class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3">
|
|
89
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
90
|
+
{{
|
|
91
|
+
connection?.kind === 'kubernetes'
|
|
92
|
+
? t('settings.providerConnection.form.updateConfiguration')
|
|
93
|
+
: t('settings.providerConnection.form.connect')
|
|
94
|
+
}}
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
<UFormField :label="t('settings.providerConnection.kubernetes.label')">
|
|
98
|
+
<UInput
|
|
99
|
+
v-model="form.label"
|
|
100
|
+
:placeholder="t('settings.providerConnection.kubernetes.labelPlaceholder')"
|
|
101
|
+
/>
|
|
102
|
+
</UFormField>
|
|
103
|
+
|
|
104
|
+
<UFormField
|
|
105
|
+
:label="t('settings.providerConnection.kubernetes.apiServerUrl')"
|
|
106
|
+
:help="t('settings.providerConnection.kubernetes.apiServerUrlHelp')"
|
|
107
|
+
>
|
|
108
|
+
<UInput v-model="form.apiServerUrl" class="font-mono" placeholder="https://10.0.0.1:6443" />
|
|
109
|
+
</UFormField>
|
|
110
|
+
|
|
111
|
+
<UFormField :label="t('settings.providerConnection.kubernetes.namespace')">
|
|
112
|
+
<UInput v-model="form.namespace" class="font-mono" placeholder="cat-factory" />
|
|
113
|
+
</UFormField>
|
|
114
|
+
|
|
115
|
+
<UFormField
|
|
116
|
+
:label="t('settings.providerConnection.kubernetes.image')"
|
|
117
|
+
:help="t('settings.providerConnection.kubernetes.imageHelp')"
|
|
118
|
+
>
|
|
119
|
+
<UInput
|
|
120
|
+
v-model="form.image"
|
|
121
|
+
class="font-mono"
|
|
122
|
+
placeholder="ghcr.io/acme/cat-factory-executor:latest"
|
|
123
|
+
/>
|
|
124
|
+
</UFormField>
|
|
125
|
+
|
|
126
|
+
<UFormField
|
|
127
|
+
:label="
|
|
128
|
+
t('settings.providerConnection.form.optionalLabel', {
|
|
129
|
+
label: t('settings.providerConnection.kubernetes.imageUi'),
|
|
130
|
+
})
|
|
131
|
+
"
|
|
132
|
+
>
|
|
133
|
+
<UInput v-model="form.imageUi" class="font-mono" />
|
|
134
|
+
</UFormField>
|
|
135
|
+
|
|
136
|
+
<UFormField
|
|
137
|
+
:label="t('settings.providerConnection.kubernetes.apiToken')"
|
|
138
|
+
:help="t('settings.providerConnection.kubernetes.apiTokenHelp')"
|
|
139
|
+
>
|
|
140
|
+
<UInput v-model="apiToken" type="password" class="font-mono" />
|
|
141
|
+
</UFormField>
|
|
142
|
+
|
|
143
|
+
<UFormField
|
|
144
|
+
:label="
|
|
145
|
+
t('settings.providerConnection.form.optionalLabel', {
|
|
146
|
+
label: t('settings.providerConnection.kubernetes.caCertPem'),
|
|
147
|
+
})
|
|
148
|
+
"
|
|
149
|
+
:help="t('settings.providerConnection.kubernetes.caCertPemHelp')"
|
|
150
|
+
>
|
|
151
|
+
<UTextarea
|
|
152
|
+
v-model="form.caCertPem"
|
|
153
|
+
:rows="3"
|
|
154
|
+
class="font-mono"
|
|
155
|
+
placeholder="-----BEGIN CERTIFICATE-----"
|
|
156
|
+
/>
|
|
157
|
+
</UFormField>
|
|
158
|
+
|
|
159
|
+
<UFormField
|
|
160
|
+
:label="
|
|
161
|
+
t('settings.providerConnection.form.optionalLabel', {
|
|
162
|
+
label: t('settings.providerConnection.kubernetes.harnessPort'),
|
|
163
|
+
})
|
|
164
|
+
"
|
|
165
|
+
>
|
|
166
|
+
<UInput v-model="form.harnessPort" type="number" class="font-mono" placeholder="8080" />
|
|
167
|
+
</UFormField>
|
|
168
|
+
|
|
169
|
+
<div v-if="supportsTest" class="flex items-center gap-2">
|
|
170
|
+
<UButton
|
|
171
|
+
color="neutral"
|
|
172
|
+
variant="soft"
|
|
173
|
+
size="sm"
|
|
174
|
+
icon="i-lucide-plug-zap"
|
|
175
|
+
:loading="testing"
|
|
176
|
+
:disabled="!canSave"
|
|
177
|
+
@click="emit('test', buildPayload())"
|
|
178
|
+
>
|
|
179
|
+
{{ t('settings.providerConnection.test.button') }}
|
|
180
|
+
</UButton>
|
|
181
|
+
<span v-if="testResult && testResult.ok" class="text-xs text-emerald-400">
|
|
182
|
+
{{ testResult.message ?? t('settings.providerConnection.test.ok') }}
|
|
183
|
+
</span>
|
|
184
|
+
<span v-else-if="testResult" class="text-xs text-rose-400">
|
|
185
|
+
{{ testResult.message ?? t('settings.providerConnection.test.failed') }}
|
|
186
|
+
</span>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="flex justify-end">
|
|
190
|
+
<UButton
|
|
191
|
+
color="primary"
|
|
192
|
+
size="sm"
|
|
193
|
+
:loading="busy"
|
|
194
|
+
:disabled="!canSave"
|
|
195
|
+
@click="emit('save', buildPayload())"
|
|
196
|
+
>
|
|
197
|
+
{{
|
|
198
|
+
connection?.kind === 'kubernetes'
|
|
199
|
+
? t('common.save')
|
|
200
|
+
: t('settings.providerConnection.form.connect')
|
|
201
|
+
}}
|
|
202
|
+
</UButton>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</template>
|
|
@@ -10,6 +10,7 @@ import { computed, ref, toRaw, watch } from 'vue'
|
|
|
10
10
|
import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
11
11
|
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
12
12
|
import ProviderManifestEditor from '~/components/settings/ProviderManifestEditor.vue'
|
|
13
|
+
import KubernetesRunnerForm from '~/components/settings/KubernetesRunnerForm.vue'
|
|
13
14
|
|
|
14
15
|
const props = defineProps<{ kind: ProviderConnectionKind }>()
|
|
15
16
|
|
|
@@ -184,6 +185,57 @@ async function saveManifest(payload: {
|
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
// --- Runner-backend selector (runner-pool only) -------------------------------------
|
|
189
|
+
// The runner-pool tab can configure either the manifest pool OR a native Kubernetes
|
|
190
|
+
// cluster; environments are manifest-only. Defaults to the saved connection's kind.
|
|
191
|
+
const RUNNER_BACKEND_KINDS = ['manifest', 'kubernetes'] as const
|
|
192
|
+
type RunnerBackendKind = (typeof RUNNER_BACKEND_KINDS)[number]
|
|
193
|
+
const backendKind = ref<RunnerBackendKind>('manifest')
|
|
194
|
+
const showBackendSelector = computed(() => props.kind === 'runner-pool')
|
|
195
|
+
const backendKindItems = computed(() =>
|
|
196
|
+
RUNNER_BACKEND_KINDS.map((k) => ({
|
|
197
|
+
label: t(`settings.providerConnection.backend.${k}`),
|
|
198
|
+
value: k,
|
|
199
|
+
})),
|
|
200
|
+
)
|
|
201
|
+
watch(
|
|
202
|
+
() => connection.value,
|
|
203
|
+
(c) => {
|
|
204
|
+
if (c?.kind === 'kubernetes' || c?.kind === 'manifest') backendKind.value = c.kind
|
|
205
|
+
},
|
|
206
|
+
{ immediate: true },
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
async function testConfig(payload: {
|
|
210
|
+
config: Record<string, unknown>
|
|
211
|
+
secrets: Record<string, string>
|
|
212
|
+
}) {
|
|
213
|
+
testing.value = true
|
|
214
|
+
testResult.value = null
|
|
215
|
+
try {
|
|
216
|
+
testResult.value = await store.test(props.kind, payload)
|
|
217
|
+
} catch (e) {
|
|
218
|
+
testResult.value = { ok: false, message: e instanceof Error ? e.message : String(e) }
|
|
219
|
+
} finally {
|
|
220
|
+
testing.value = false
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function saveConfig(payload: {
|
|
225
|
+
config: Record<string, unknown>
|
|
226
|
+
secrets: Record<string, string>
|
|
227
|
+
}) {
|
|
228
|
+
busy.value = true
|
|
229
|
+
try {
|
|
230
|
+
await store.register(props.kind, payload)
|
|
231
|
+
toastSaved()
|
|
232
|
+
} catch (e) {
|
|
233
|
+
notifyError(t('settings.providerConnection.toast.saveFailed'), e)
|
|
234
|
+
} finally {
|
|
235
|
+
busy.value = false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
187
239
|
async function remove() {
|
|
188
240
|
busy.value = true
|
|
189
241
|
try {
|
|
@@ -265,8 +317,31 @@ function fieldHelp(key: string): string | undefined {
|
|
|
265
317
|
}}
|
|
266
318
|
</div>
|
|
267
319
|
|
|
320
|
+
<!-- Runner-backend selector: the manifest pool or a native Kubernetes cluster. -->
|
|
321
|
+
<UFormField
|
|
322
|
+
v-if="showBackendSelector"
|
|
323
|
+
:label="t('settings.providerConnection.backend.selectorLabel')"
|
|
324
|
+
>
|
|
325
|
+
<USelect v-model="backendKind" :items="backendKindItems" />
|
|
326
|
+
</UFormField>
|
|
327
|
+
|
|
328
|
+
<!-- Native Kubernetes runner backend. -->
|
|
329
|
+
<KubernetesRunnerForm
|
|
330
|
+
v-if="showBackendSelector && backendKind === 'kubernetes'"
|
|
331
|
+
:connection="connection"
|
|
332
|
+
:supports-test="descriptor.supportsTest"
|
|
333
|
+
:testing="testing"
|
|
334
|
+
:busy="busy"
|
|
335
|
+
:test-result="testResult"
|
|
336
|
+
@test="testConfig"
|
|
337
|
+
@save="saveConfig"
|
|
338
|
+
/>
|
|
339
|
+
|
|
268
340
|
<!-- NATIVE provider: the friendly, descriptor-driven flat field form. -->
|
|
269
|
-
<div
|
|
341
|
+
<div
|
|
342
|
+
v-else-if="isNative"
|
|
343
|
+
class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3"
|
|
344
|
+
>
|
|
270
345
|
<p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
271
346
|
{{
|
|
272
347
|
connection
|
|
@@ -58,9 +58,10 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
|
|
|
58
58
|
send(CONTRACTS[kind].get, { pathPrefix: ws(workspaceId) }),
|
|
59
59
|
|
|
60
60
|
// The connect form builds the manifest dynamically from a server-provided scaffold, so
|
|
61
|
-
// the FE input keeps `manifest` opaque (`Record<string, unknown>`); narrow on
|
|
62
|
-
// and cast to the matching per-kind contract input at this single boundary (the
|
|
63
|
-
// re-validates
|
|
61
|
+
// the FE input keeps `manifest`/`config` opaque (`Record<string, unknown>`); narrow on
|
|
62
|
+
// the kind and cast to the matching per-kind contract input at this single boundary (the
|
|
63
|
+
// backend re-validates against the precise contract on receipt). The runner-pool provider
|
|
64
|
+
// takes a discriminated `config`; a bare `manifest` is wrapped into the manifest backend.
|
|
64
65
|
registerProviderConnection: (
|
|
65
66
|
workspaceId: string,
|
|
66
67
|
kind: ProviderConnectionKind,
|
|
@@ -73,7 +74,10 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
|
|
|
73
74
|
})
|
|
74
75
|
: send(CONTRACTS['runner-pool'].register, {
|
|
75
76
|
pathPrefix: ws(workspaceId),
|
|
76
|
-
body:
|
|
77
|
+
body: {
|
|
78
|
+
config: runnerBackendConfig(body),
|
|
79
|
+
secrets: body.secrets,
|
|
80
|
+
} as RegisterRunnerPoolInput,
|
|
77
81
|
}),
|
|
78
82
|
|
|
79
83
|
updateProviderSecrets: (
|
|
@@ -94,10 +98,25 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
|
|
|
94
98
|
})
|
|
95
99
|
: send(CONTRACTS['runner-pool'].test, {
|
|
96
100
|
pathPrefix: ws(workspaceId),
|
|
97
|
-
body:
|
|
101
|
+
body: {
|
|
102
|
+
...(body.manifest || body.config ? { config: runnerBackendConfig(body) } : {}),
|
|
103
|
+
...(body.secrets ? { secrets: body.secrets } : {}),
|
|
104
|
+
} as TestRunnerPoolConnectionInput,
|
|
98
105
|
}),
|
|
99
106
|
|
|
100
107
|
deleteProviderConnection: (workspaceId: string, kind: ProviderConnectionKind) =>
|
|
101
108
|
send(CONTRACTS[kind].unregister, { pathPrefix: ws(workspaceId) }),
|
|
102
109
|
}
|
|
103
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the discriminated runner-backend config from a connect-form payload: an
|
|
114
|
+
* explicit `config` (the Kubernetes form) wins; otherwise a bare `manifest` (the manifest
|
|
115
|
+
* editor) is wrapped into the manifest backend kind.
|
|
116
|
+
*/
|
|
117
|
+
function runnerBackendConfig(
|
|
118
|
+
body: RegisterProviderInput | TestProviderInput,
|
|
119
|
+
): Record<string, unknown> {
|
|
120
|
+
if (body.config) return body.config
|
|
121
|
+
return { kind: 'manifest', manifest: body.manifest ?? {} }
|
|
122
|
+
}
|
|
@@ -19,12 +19,20 @@ export type ProviderConnectionKind = 'environment' | 'runner-pool'
|
|
|
19
19
|
|
|
20
20
|
/** A workspace's provider binding, as exposed to clients (never secret values). */
|
|
21
21
|
export interface ProviderConnection {
|
|
22
|
+
/** The runner-backend kind for a runner-pool connection (`manifest` | `kubernetes`). */
|
|
23
|
+
kind?: string
|
|
22
24
|
providerId: string
|
|
23
25
|
label: string
|
|
24
26
|
baseUrl: string
|
|
25
27
|
connectedAt: number
|
|
26
28
|
/** Which secret/config keys are stored (names only), so the UI shows completeness. */
|
|
27
29
|
secretKeys: string[]
|
|
30
|
+
/**
|
|
31
|
+
* The stored discriminated runner-backend config, sans secrets, so the connect form
|
|
32
|
+
* can prefill its non-secret fields on edit. Shape mirrors the backend
|
|
33
|
+
* `RunnerBackendConfig` ({ kind: 'manifest' | 'kubernetes', … }); kept opaque here.
|
|
34
|
+
*/
|
|
35
|
+
config?: Record<string, unknown>
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
// The connect form builds the manifest dynamically from a server-provided scaffold
|
|
@@ -33,15 +41,23 @@ export interface ProviderConnection {
|
|
|
33
41
|
// per-provider manifest contract on receipt; the composable casts to the contract input
|
|
34
42
|
// type at the single `send` boundary.
|
|
35
43
|
|
|
36
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* The assembled register payload. The environment provider sends a full `manifest`.
|
|
46
|
+
* The runner-pool ("agent runner backend") provider sends a discriminated `config`
|
|
47
|
+
* ({ kind: 'manifest' | 'kubernetes', … }); for back-compat of the manifest editor it
|
|
48
|
+
* may instead send a bare `manifest`, which the composable wraps into the manifest
|
|
49
|
+
* backend config. The write-only secret bundle rides alongside.
|
|
50
|
+
*/
|
|
37
51
|
export interface RegisterProviderInput {
|
|
38
|
-
manifest
|
|
52
|
+
manifest?: Record<string, unknown>
|
|
53
|
+
/** The discriminated runner-backend config (manifest pool or kubernetes). */
|
|
54
|
+
config?: Record<string, unknown>
|
|
39
55
|
secrets: Record<string, string>
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
/** The test/probe payload (manifest-driven
|
|
58
|
+
/** The test/probe payload (manifest-driven, native, or a discriminated runner config). */
|
|
43
59
|
export interface TestProviderInput {
|
|
44
60
|
manifest?: Record<string, unknown>
|
|
45
|
-
config?: Record<string,
|
|
61
|
+
config?: Record<string, unknown>
|
|
46
62
|
secrets?: Record<string, string>
|
|
47
63
|
}
|
package/i18n/locales/en.json
CHANGED
|
@@ -1272,10 +1272,30 @@
|
|
|
1272
1272
|
"blurb": "Where the Tester agent runs against a live preview environment. Configure the per-workspace settings and credentials your provider needs."
|
|
1273
1273
|
},
|
|
1274
1274
|
"runner-pool": {
|
|
1275
|
-
"title": "
|
|
1276
|
-
"blurb": "Where the coding agents run when not using Cloudflare Containers.
|
|
1275
|
+
"title": "Agent runner backend",
|
|
1276
|
+
"blurb": "Where the coding agents run when not using Cloudflare Containers. Choose a self-hosted runner pool (your own scheduler) or a Kubernetes cluster, then configure its endpoint and credentials."
|
|
1277
1277
|
}
|
|
1278
1278
|
},
|
|
1279
|
+
"backend": {
|
|
1280
|
+
"selectorLabel": "Runner backend",
|
|
1281
|
+
"manifest": "Self-hosted pool (manifest)",
|
|
1282
|
+
"kubernetes": "Kubernetes"
|
|
1283
|
+
},
|
|
1284
|
+
"kubernetes": {
|
|
1285
|
+
"label": "Name",
|
|
1286
|
+
"labelPlaceholder": "Production cluster",
|
|
1287
|
+
"apiServerUrl": "API server URL",
|
|
1288
|
+
"apiServerUrlHelp": "The kube-apiserver root, e.g. https://10.0.0.1:6443. The orchestrator reaches each run's pod through the apiserver, so only this endpoint must be reachable.",
|
|
1289
|
+
"namespace": "Namespace",
|
|
1290
|
+
"image": "Executor image",
|
|
1291
|
+
"imageHelp": "The executor-harness image each per-run pod runs.",
|
|
1292
|
+
"imageUi": "UI-tester image",
|
|
1293
|
+
"apiToken": "ServiceAccount token",
|
|
1294
|
+
"apiTokenHelp": "A bearer token with RBAC to create, get and delete pods and pods/proxy in the namespace. Stored encrypted; never shown again.",
|
|
1295
|
+
"caCertPem": "Cluster CA certificate (PEM)",
|
|
1296
|
+
"caCertPemHelp": "Paste the cluster CA bundle so the apiserver's TLS certificate verifies. Omit only for a publicly-trusted CA.",
|
|
1297
|
+
"harnessPort": "Harness port"
|
|
1298
|
+
},
|
|
1279
1299
|
"manifestEditor": {
|
|
1280
1300
|
"title": "Provider manifest",
|
|
1281
1301
|
"jsonLabel": "Manifest (JSON)",
|
|
@@ -2080,7 +2100,6 @@
|
|
|
2080
2100
|
"title": "Connect source",
|
|
2081
2101
|
"sourceFallback": "Source",
|
|
2082
2102
|
"intro": "Connect {source} to import requirements, RFCs and PRDs, then spawn board structure or attach them to tasks as agent context.",
|
|
2083
|
-
"personalNote": "Personal connection — this credential authenticates as you and is stored only for your account, never shared with the rest of the workspace.",
|
|
2084
2103
|
"disconnect": "Disconnect",
|
|
2085
2104
|
"connect": "Connect",
|
|
2086
2105
|
"update": "Update connection",
|
package/i18n/locales/es.json
CHANGED
|
@@ -1283,6 +1283,26 @@
|
|
|
1283
1283
|
"saveFailed": "No se pudo guardar la conexión",
|
|
1284
1284
|
"removed": "Conexión eliminada",
|
|
1285
1285
|
"removeFailed": "No se pudo eliminar la conexión"
|
|
1286
|
+
},
|
|
1287
|
+
"backend": {
|
|
1288
|
+
"selectorLabel": "Backend de ejecución",
|
|
1289
|
+
"manifest": "Pool autohospedado (manifiesto)",
|
|
1290
|
+
"kubernetes": "Kubernetes"
|
|
1291
|
+
},
|
|
1292
|
+
"kubernetes": {
|
|
1293
|
+
"label": "Nombre",
|
|
1294
|
+
"labelPlaceholder": "Clúster de producción",
|
|
1295
|
+
"apiServerUrl": "URL del API server",
|
|
1296
|
+
"apiServerUrlHelp": "La raíz del kube-apiserver, p. ej. https://10.0.0.1:6443. El orquestador llega al pod de cada ejecución a través del apiserver, así que solo este endpoint debe ser accesible.",
|
|
1297
|
+
"namespace": "Namespace",
|
|
1298
|
+
"image": "Imagen del ejecutor",
|
|
1299
|
+
"imageHelp": "La imagen de executor-harness que ejecuta cada pod por ejecución.",
|
|
1300
|
+
"imageUi": "Imagen de tester de UI",
|
|
1301
|
+
"apiToken": "Token de ServiceAccount",
|
|
1302
|
+
"apiTokenHelp": "Un token bearer con permisos RBAC para crear, obtener y eliminar pods y pods/proxy en el namespace. Se almacena cifrado; no se vuelve a mostrar.",
|
|
1303
|
+
"caCertPem": "Certificado CA del clúster (PEM)",
|
|
1304
|
+
"caCertPemHelp": "Pega el bundle CA del clúster para que el certificado TLS del apiserver se verifique. Omítelo solo para una CA de confianza pública.",
|
|
1305
|
+
"harnessPort": "Puerto del harness"
|
|
1286
1306
|
}
|
|
1287
1307
|
},
|
|
1288
1308
|
"serviceFragmentDefaults": {
|
|
@@ -2026,7 +2046,6 @@
|
|
|
2026
2046
|
"title": "Conectar fuente",
|
|
2027
2047
|
"sourceFallback": "Fuente",
|
|
2028
2048
|
"intro": "Conecta {source} para importar requisitos, RFC y PRD, y luego generar la estructura del tablero o adjuntarlos a las tareas como contexto para los agentes.",
|
|
2029
|
-
"personalNote": "Conexión personal: esta credencial te autentica a ti y se almacena solo para tu cuenta, nunca se comparte con el resto del espacio de trabajo.",
|
|
2030
2049
|
"disconnect": "Desconectar",
|
|
2031
2050
|
"connect": "Conectar",
|
|
2032
2051
|
"update": "Actualizar conexión",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -1283,6 +1283,26 @@
|
|
|
1283
1283
|
"saveFailed": "Impossible d'enregistrer la connexion",
|
|
1284
1284
|
"removed": "Connexion supprimée",
|
|
1285
1285
|
"removeFailed": "Impossible de supprimer la connexion"
|
|
1286
|
+
},
|
|
1287
|
+
"backend": {
|
|
1288
|
+
"selectorLabel": "Backend d'exécution",
|
|
1289
|
+
"manifest": "Pool auto-hébergé (manifeste)",
|
|
1290
|
+
"kubernetes": "Kubernetes"
|
|
1291
|
+
},
|
|
1292
|
+
"kubernetes": {
|
|
1293
|
+
"label": "Nom",
|
|
1294
|
+
"labelPlaceholder": "Cluster de production",
|
|
1295
|
+
"apiServerUrl": "URL du serveur d'API",
|
|
1296
|
+
"apiServerUrlHelp": "La racine du kube-apiserver, p. ex. https://10.0.0.1:6443. L'orchestrateur atteint le pod de chaque exécution via l'apiserver, donc seul ce point d'accès doit être accessible.",
|
|
1297
|
+
"namespace": "Namespace",
|
|
1298
|
+
"image": "Image de l'exécuteur",
|
|
1299
|
+
"imageHelp": "L'image executor-harness exécutée par chaque pod d'exécution.",
|
|
1300
|
+
"imageUi": "Image testeur d'UI",
|
|
1301
|
+
"apiToken": "Jeton de ServiceAccount",
|
|
1302
|
+
"apiTokenHelp": "Un jeton bearer avec les droits RBAC pour créer, lire et supprimer les pods et pods/proxy dans le namespace. Stocké chiffré ; jamais réaffiché.",
|
|
1303
|
+
"caCertPem": "Certificat CA du cluster (PEM)",
|
|
1304
|
+
"caCertPemHelp": "Collez le bundle CA du cluster pour que le certificat TLS de l'apiserver soit vérifié. À omettre uniquement pour une CA publiquement approuvée.",
|
|
1305
|
+
"harnessPort": "Port du harness"
|
|
1286
1306
|
}
|
|
1287
1307
|
},
|
|
1288
1308
|
"serviceFragmentDefaults": {
|
|
@@ -2026,7 +2046,6 @@
|
|
|
2026
2046
|
"title": "Connecter une source",
|
|
2027
2047
|
"sourceFallback": "Source",
|
|
2028
2048
|
"intro": "Connectez {source} pour importer des exigences, des RFC et des PRD, puis générer la structure du tableau ou les joindre aux tâches comme contexte pour les agents.",
|
|
2029
|
-
"personalNote": "Connexion personnelle : cet identifiant vous authentifie et n'est stocké que pour votre compte, jamais partagé avec le reste de l'espace de travail.",
|
|
2030
2049
|
"disconnect": "Déconnecter",
|
|
2031
2050
|
"connect": "Connecter",
|
|
2032
2051
|
"update": "Mettre à jour la connexion",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -1283,6 +1283,26 @@
|
|
|
1283
1283
|
"saveFailed": "Nie udało się zapisać połączenia",
|
|
1284
1284
|
"removed": "Połączenie usunięte",
|
|
1285
1285
|
"removeFailed": "Nie udało się usunąć połączenia"
|
|
1286
|
+
},
|
|
1287
|
+
"backend": {
|
|
1288
|
+
"selectorLabel": "Backend wykonawczy",
|
|
1289
|
+
"manifest": "Własny pool (manifest)",
|
|
1290
|
+
"kubernetes": "Kubernetes"
|
|
1291
|
+
},
|
|
1292
|
+
"kubernetes": {
|
|
1293
|
+
"label": "Nazwa",
|
|
1294
|
+
"labelPlaceholder": "Klaster produkcyjny",
|
|
1295
|
+
"apiServerUrl": "URL serwera API",
|
|
1296
|
+
"apiServerUrlHelp": "Główny adres kube-apiserver, np. https://10.0.0.1:6443. Orkiestrator łączy się z podem każdego uruchomienia przez apiserver, więc tylko ten endpoint musi być osiągalny.",
|
|
1297
|
+
"namespace": "Namespace",
|
|
1298
|
+
"image": "Obraz wykonawcy",
|
|
1299
|
+
"imageHelp": "Obraz executor-harness uruchamiany przez każdy pod uruchomienia.",
|
|
1300
|
+
"imageUi": "Obraz testera UI",
|
|
1301
|
+
"apiToken": "Token ServiceAccount",
|
|
1302
|
+
"apiTokenHelp": "Token bearer z uprawnieniami RBAC do tworzenia, odczytu i usuwania podów oraz pods/proxy w namespace. Przechowywany w postaci zaszyfrowanej; nie jest ponownie pokazywany.",
|
|
1303
|
+
"caCertPem": "Certyfikat CA klastra (PEM)",
|
|
1304
|
+
"caCertPemHelp": "Wklej pakiet CA klastra, aby certyfikat TLS apiservera został zweryfikowany. Pomiń tylko dla publicznie zaufanego CA.",
|
|
1305
|
+
"harnessPort": "Port harnessa"
|
|
1286
1306
|
}
|
|
1287
1307
|
},
|
|
1288
1308
|
"serviceFragmentDefaults": {
|
|
@@ -2026,7 +2046,6 @@
|
|
|
2026
2046
|
"title": "Połącz źródło",
|
|
2027
2047
|
"sourceFallback": "Źródło",
|
|
2028
2048
|
"intro": "Połącz {source}, aby importować wymagania, dokumenty RFC i PRD, a następnie utworzyć strukturę tablicy lub dołączyć je do zadań jako kontekst dla agentów.",
|
|
2029
|
-
"personalNote": "Połączenie osobiste: te poświadczenia uwierzytelniają Ciebie i są przechowywane tylko dla Twojego konta, nigdy nie są udostępniane reszcie obszaru roboczego.",
|
|
2030
2049
|
"disconnect": "Rozłącz",
|
|
2031
2050
|
"connect": "Połącz",
|
|
2032
2051
|
"update": "Zaktualizuj połączenie",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -1283,6 +1283,26 @@
|
|
|
1283
1283
|
"saveFailed": "Не вдалося зберегти підключення",
|
|
1284
1284
|
"removed": "Підключення видалено",
|
|
1285
1285
|
"removeFailed": "Не вдалося видалити підключення"
|
|
1286
|
+
},
|
|
1287
|
+
"backend": {
|
|
1288
|
+
"selectorLabel": "Бекенд виконавця",
|
|
1289
|
+
"manifest": "Власний пул (маніфест)",
|
|
1290
|
+
"kubernetes": "Kubernetes"
|
|
1291
|
+
},
|
|
1292
|
+
"kubernetes": {
|
|
1293
|
+
"label": "Назва",
|
|
1294
|
+
"labelPlaceholder": "Робочий кластер",
|
|
1295
|
+
"apiServerUrl": "URL сервера API",
|
|
1296
|
+
"apiServerUrlHelp": "Кореневий адрес kube-apiserver, напр. https://10.0.0.1:6443. Оркестратор звертається до пода кожного запуску через apiserver, тож лише цей endpoint має бути доступним.",
|
|
1297
|
+
"namespace": "Простір імен",
|
|
1298
|
+
"image": "Образ виконавця",
|
|
1299
|
+
"imageHelp": "Образ executor-harness, який запускає кожен под запуску.",
|
|
1300
|
+
"imageUi": "Образ UI-тестувальника",
|
|
1301
|
+
"apiToken": "Токен ServiceAccount",
|
|
1302
|
+
"apiTokenHelp": "Bearer-токен з правами RBAC на створення, читання та видалення подів і pods/proxy у просторі імен. Зберігається зашифрованим; більше не показується.",
|
|
1303
|
+
"caCertPem": "Сертифікат CA кластера (PEM)",
|
|
1304
|
+
"caCertPemHelp": "Вставте CA-набір кластера, щоб TLS-сертифікат apiserver проходив перевірку. Пропустіть лише для публічно довіреного CA.",
|
|
1305
|
+
"harnessPort": "Порт harness"
|
|
1286
1306
|
}
|
|
1287
1307
|
},
|
|
1288
1308
|
"serviceFragmentDefaults": {
|
|
@@ -2026,7 +2046,6 @@
|
|
|
2026
2046
|
"title": "Підключити джерело",
|
|
2027
2047
|
"sourceFallback": "Джерело",
|
|
2028
2048
|
"intro": "Підключіть {source}, щоб імпортувати вимоги, RFC та PRD, а потім створити структуру дошки або долучити їх до завдань як контекст для агентів.",
|
|
2029
|
-
"personalNote": "Особисте підключення: ці облікові дані автентифікують саме вас і зберігаються лише для вашого облікового запису, ніколи не передаються решті робочого простору.",
|
|
2030
2049
|
"disconnect": "Відключити",
|
|
2031
2050
|
"connect": "Підключити",
|
|
2032
2051
|
"update": "Оновити підключення",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.3",
|
|
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.49.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|