@cat-factory/app 0.110.4 → 0.111.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/InspectorPanel.vue +4 -0
- package/app/components/panels/inspector/ServiceTestSecrets.vue +264 -0
- package/app/composables/api/testSecrets.ts +36 -0
- package/app/composables/useApi.ts +2 -0
- package/app/stores/testSecrets.ts +89 -0
- package/app/types/testSecrets.ts +15 -0
- package/i18n/locales/de.json +21 -0
- package/i18n/locales/en.json +21 -0
- package/i18n/locales/es.json +21 -0
- package/i18n/locales/fr.json +21 -0
- package/i18n/locales/he.json +21 -0
- package/i18n/locales/it.json +21 -0
- package/i18n/locales/ja.json +21 -0
- package/i18n/locales/pl.json +21 -0
- package/i18n/locales/tr.json +21 -0
- package/i18n/locales/uk.json +21 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import TaskAgentConfig from '~/components/panels/inspector/TaskAgentConfig.vue'
|
|
|
8
8
|
import ServiceTestConfig from '~/components/panels/inspector/ServiceTestConfig.vue'
|
|
9
9
|
import ServiceFragments from '~/components/panels/inspector/ServiceFragments.vue'
|
|
10
10
|
import ServiceReleaseHealthConfig from '~/components/panels/inspector/ServiceReleaseHealthConfig.vue'
|
|
11
|
+
import ServiceTestSecrets from '~/components/panels/inspector/ServiceTestSecrets.vue'
|
|
11
12
|
import FrontendConfig from '~/components/panels/inspector/FrontendConfig.vue'
|
|
12
13
|
import ServiceConnections from '~/components/panels/inspector/ServiceConnections.vue'
|
|
13
14
|
import ContainerSummary from '~/components/panels/inspector/ContainerSummary.vue'
|
|
@@ -496,6 +497,9 @@ const showOriginalDescription = ref(false)
|
|
|
496
497
|
<!-- service (frame): test infra + provisioning configuration -->
|
|
497
498
|
<ServiceTestConfig v-if="isFrame" :key="`test-config-${block.id}`" :block="block" />
|
|
498
499
|
|
|
500
|
+
<!-- service (frame): SENSITIVE test credentials (sealed, injected out of band) -->
|
|
501
|
+
<ServiceTestSecrets v-if="isFrame" :key="`test-secrets-${block.id}`" :block="block" />
|
|
502
|
+
|
|
499
503
|
<!-- service (frame): best-practice fragments for code-aware agents -->
|
|
500
504
|
<ServiceFragments v-if="isFrame" :key="`fragments-${block.id}`" :block="block" />
|
|
501
505
|
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, reactive, ref, watch } from 'vue'
|
|
3
|
+
import type { Block } from '~/types/domain'
|
|
4
|
+
import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
|
|
5
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
6
|
+
|
|
7
|
+
// Per-service (frame) SENSITIVE test credentials: a genuinely secret token a Tester needs
|
|
8
|
+
// to exercise a third-party integration (e.g. a Stripe API key). Unlike the non-sensitive
|
|
9
|
+
// pools, these are SEALED at rest and injected into the Tester container OUT OF BAND — never
|
|
10
|
+
// rendered into a prompt or the telemetry snapshot. Keyed by THIS frame's block id.
|
|
11
|
+
//
|
|
12
|
+
// The backend stores the WHOLE set and values are write-only (never read back), so this is a
|
|
13
|
+
// full-set replace editor: saving persists exactly the rows below and drops anything omitted.
|
|
14
|
+
// The list prefills from the configured keys/descriptions; every value must be (re-)entered,
|
|
15
|
+
// and Save stays disabled until each row has one — so an existing secret can never be blanked
|
|
16
|
+
// by accident. Hidden entirely when the backend store is unconfigured (no ENCRYPTION_KEY).
|
|
17
|
+
const props = defineProps<{ block: Block }>()
|
|
18
|
+
|
|
19
|
+
const store = useTestSecretsStore()
|
|
20
|
+
const toast = useToast()
|
|
21
|
+
const { t } = useI18n()
|
|
22
|
+
const { confirmAction, toastDone } = useConfirmAction()
|
|
23
|
+
|
|
24
|
+
const busy = ref(false)
|
|
25
|
+
|
|
26
|
+
interface DraftRow {
|
|
27
|
+
key: string
|
|
28
|
+
description: string
|
|
29
|
+
value: string
|
|
30
|
+
}
|
|
31
|
+
const draft = reactive<{ rows: DraftRow[] }>({ rows: [] })
|
|
32
|
+
|
|
33
|
+
const configured = computed(() => store.entriesForBlock(props.block.id))
|
|
34
|
+
const available = computed(() => store.available !== false)
|
|
35
|
+
|
|
36
|
+
const blankRow = (): DraftRow => ({ key: '', description: '', value: '' })
|
|
37
|
+
|
|
38
|
+
// Load this frame's configured refs once, then (re)hydrate the editor from them. Runs again
|
|
39
|
+
// after a save/clear (the store refs change) so the just-typed secret values don't linger in
|
|
40
|
+
// the form — the persisted set is re-shown with empty value fields to re-enter.
|
|
41
|
+
onMounted(() => {
|
|
42
|
+
store.ensureLoaded(props.block.id).catch(() => {})
|
|
43
|
+
})
|
|
44
|
+
watch(
|
|
45
|
+
configured,
|
|
46
|
+
(entries) => {
|
|
47
|
+
draft.rows = entries.length
|
|
48
|
+
? entries.map((e) => ({ key: e.key, description: e.description, value: '' }))
|
|
49
|
+
: [blankRow()]
|
|
50
|
+
},
|
|
51
|
+
{ immediate: true },
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// A valid POSIX env-var name (mirrors the contract's testSecretKeySchema regex + max length).
|
|
55
|
+
// The reserved/toolchain-name rejection lives server-side and surfaces as a save error — we
|
|
56
|
+
// don't duplicate the harness's reserved-name list here.
|
|
57
|
+
const KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
58
|
+
const KEY_MAX = 128
|
|
59
|
+
function keyValid(key: string): boolean {
|
|
60
|
+
const k = key.trim()
|
|
61
|
+
return KEY_RE.test(k) && k.length <= KEY_MAX
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Keys that appear more than once (trimmed) — flagged inline and block saving.
|
|
65
|
+
const duplicateKeys = computed(() => {
|
|
66
|
+
const seen = new Map<string, number>()
|
|
67
|
+
for (const r of draft.rows) {
|
|
68
|
+
const k = r.key.trim()
|
|
69
|
+
if (k) seen.set(k, (seen.get(k) ?? 0) + 1)
|
|
70
|
+
}
|
|
71
|
+
return new Set([...seen.entries()].filter(([, n]) => n > 1).map(([k]) => k))
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
function rowComplete(r: DraftRow): boolean {
|
|
75
|
+
return keyValid(r.key) && r.value.trim().length > 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const canSave = computed(
|
|
79
|
+
() =>
|
|
80
|
+
!busy.value &&
|
|
81
|
+
draft.rows.length > 0 &&
|
|
82
|
+
draft.rows.every(rowComplete) &&
|
|
83
|
+
duplicateKeys.value.size === 0,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
function addRow() {
|
|
87
|
+
draft.rows.push(blankRow())
|
|
88
|
+
}
|
|
89
|
+
function removeRow(index: number) {
|
|
90
|
+
draft.rows.splice(index, 1)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function notifyError(title: string, e: unknown) {
|
|
94
|
+
toast.add({
|
|
95
|
+
title,
|
|
96
|
+
description: e instanceof Error ? e.message : String(e),
|
|
97
|
+
icon: 'i-lucide-triangle-alert',
|
|
98
|
+
color: 'error',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function save() {
|
|
103
|
+
busy.value = true
|
|
104
|
+
try {
|
|
105
|
+
await store.save(props.block.id, {
|
|
106
|
+
entries: draft.rows.map((r) => ({
|
|
107
|
+
key: r.key.trim(),
|
|
108
|
+
description: r.description.trim(),
|
|
109
|
+
value: r.value,
|
|
110
|
+
})),
|
|
111
|
+
})
|
|
112
|
+
toast.add({
|
|
113
|
+
title: t('inspector.testSecrets.savedToast'),
|
|
114
|
+
icon: 'i-lucide-check',
|
|
115
|
+
color: 'success',
|
|
116
|
+
})
|
|
117
|
+
} catch (e) {
|
|
118
|
+
notifyError(t('inspector.testSecrets.saveFailed'), e)
|
|
119
|
+
} finally {
|
|
120
|
+
busy.value = false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function clearAll() {
|
|
125
|
+
const noun = t('inspector.testSecrets.configNoun')
|
|
126
|
+
if (!(await confirmAction('clear', noun))) return
|
|
127
|
+
busy.value = true
|
|
128
|
+
try {
|
|
129
|
+
await store.clear(props.block.id)
|
|
130
|
+
toastDone('clear', noun)
|
|
131
|
+
} catch (e) {
|
|
132
|
+
notifyError(t('inspector.testSecrets.clearFailed'), e)
|
|
133
|
+
} finally {
|
|
134
|
+
busy.value = false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<template>
|
|
140
|
+
<InspectorSection
|
|
141
|
+
v-if="available"
|
|
142
|
+
:title="t('inspector.testSecrets.title')"
|
|
143
|
+
:hint="t('inspector.testSecrets.sectionHint')"
|
|
144
|
+
:count="configured.length"
|
|
145
|
+
warning
|
|
146
|
+
default-open
|
|
147
|
+
data-testid="service-test-secrets"
|
|
148
|
+
>
|
|
149
|
+
<template #actions>
|
|
150
|
+
<UButton
|
|
151
|
+
v-if="configured.length"
|
|
152
|
+
color="error"
|
|
153
|
+
variant="ghost"
|
|
154
|
+
size="xs"
|
|
155
|
+
icon="i-lucide-trash-2"
|
|
156
|
+
:loading="busy"
|
|
157
|
+
data-testid="test-secrets-clear"
|
|
158
|
+
@click="clearAll"
|
|
159
|
+
>
|
|
160
|
+
{{ t('inspector.testSecrets.clear') }}
|
|
161
|
+
</UButton>
|
|
162
|
+
</template>
|
|
163
|
+
|
|
164
|
+
<!-- These are REAL secrets: an unmistakable sensitivity + replace-all warning. -->
|
|
165
|
+
<div
|
|
166
|
+
class="flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-2.5 py-2 text-[11px] leading-snug text-amber-200"
|
|
167
|
+
>
|
|
168
|
+
<UIcon name="i-lucide-shield-alert" class="mt-0.5 h-4 w-4 shrink-0 text-amber-400" />
|
|
169
|
+
<span>{{ t('inspector.testSecrets.warning') }}</span>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<p class="text-[11px] leading-snug text-slate-500">
|
|
173
|
+
{{ t('inspector.testSecrets.replaceNote') }}
|
|
174
|
+
</p>
|
|
175
|
+
|
|
176
|
+
<div class="space-y-3">
|
|
177
|
+
<div
|
|
178
|
+
v-for="(row, index) in draft.rows"
|
|
179
|
+
:key="index"
|
|
180
|
+
class="space-y-2 rounded-md border border-slate-800 p-2.5"
|
|
181
|
+
:data-testid="`test-secret-row-${index}`"
|
|
182
|
+
>
|
|
183
|
+
<div class="flex items-start gap-2">
|
|
184
|
+
<UFormField
|
|
185
|
+
:label="t('inspector.testSecrets.key')"
|
|
186
|
+
:error="
|
|
187
|
+
row.key.trim() && !keyValid(row.key)
|
|
188
|
+
? t('inspector.testSecrets.keyInvalid')
|
|
189
|
+
: undefined
|
|
190
|
+
"
|
|
191
|
+
class="flex-1"
|
|
192
|
+
>
|
|
193
|
+
<UInput
|
|
194
|
+
v-model="row.key"
|
|
195
|
+
placeholder="STRIPE_API_KEY"
|
|
196
|
+
size="sm"
|
|
197
|
+
class="w-full font-mono"
|
|
198
|
+
:data-testid="`test-secret-key-${index}`"
|
|
199
|
+
/>
|
|
200
|
+
</UFormField>
|
|
201
|
+
<UButton
|
|
202
|
+
color="error"
|
|
203
|
+
variant="ghost"
|
|
204
|
+
size="xs"
|
|
205
|
+
icon="i-lucide-x"
|
|
206
|
+
class="mt-5 shrink-0"
|
|
207
|
+
:aria-label="t('inspector.testSecrets.removeRow')"
|
|
208
|
+
:data-testid="`test-secret-remove-${index}`"
|
|
209
|
+
@click="removeRow(index)"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<UFormField :label="t('inspector.testSecrets.description')">
|
|
214
|
+
<UInput
|
|
215
|
+
v-model="row.description"
|
|
216
|
+
:placeholder="t('inspector.testSecrets.descriptionPlaceholder')"
|
|
217
|
+
size="sm"
|
|
218
|
+
class="w-full"
|
|
219
|
+
:data-testid="`test-secret-description-${index}`"
|
|
220
|
+
/>
|
|
221
|
+
</UFormField>
|
|
222
|
+
|
|
223
|
+
<UFormField :label="t('inspector.testSecrets.value')">
|
|
224
|
+
<SecretInput
|
|
225
|
+
v-model="row.value"
|
|
226
|
+
:placeholder="t('inspector.testSecrets.valuePlaceholder')"
|
|
227
|
+
size="sm"
|
|
228
|
+
class="w-full"
|
|
229
|
+
:data-testid="`test-secret-value-${index}`"
|
|
230
|
+
/>
|
|
231
|
+
</UFormField>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<p v-if="duplicateKeys.size" class="text-[11px] text-error-400">
|
|
235
|
+
{{ t('inspector.testSecrets.duplicateKey') }}
|
|
236
|
+
</p>
|
|
237
|
+
|
|
238
|
+
<div class="flex items-center justify-between gap-2">
|
|
239
|
+
<UButton
|
|
240
|
+
color="neutral"
|
|
241
|
+
variant="soft"
|
|
242
|
+
size="xs"
|
|
243
|
+
icon="i-lucide-plus"
|
|
244
|
+
data-testid="test-secret-add"
|
|
245
|
+
@click="addRow"
|
|
246
|
+
>
|
|
247
|
+
{{ t('inspector.testSecrets.addRow') }}
|
|
248
|
+
</UButton>
|
|
249
|
+
<UButton
|
|
250
|
+
color="primary"
|
|
251
|
+
variant="soft"
|
|
252
|
+
size="xs"
|
|
253
|
+
icon="i-lucide-save"
|
|
254
|
+
:loading="busy"
|
|
255
|
+
:disabled="!canSave"
|
|
256
|
+
data-testid="test-secrets-save"
|
|
257
|
+
@click="save"
|
|
258
|
+
>
|
|
259
|
+
{{ t('inspector.testSecrets.save') }}
|
|
260
|
+
</UButton>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</InspectorSection>
|
|
264
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deleteServiceTestSecretsContract,
|
|
3
|
+
getServiceTestSecretsContract,
|
|
4
|
+
setServiceTestSecretsContract,
|
|
5
|
+
} from '@cat-factory/contracts'
|
|
6
|
+
import type { UpsertServiceTestSecretsInput } from '~/types/testSecrets'
|
|
7
|
+
import type { ApiContext } from './context'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sensitive per-service test secrets (SEALED, write-only). The GET view returns only the
|
|
11
|
+
* configured keys + descriptions; the PUT replaces the whole set (values write-only) and
|
|
12
|
+
* an empty set clears it. Keyed by the service-frame block. See TestSecretsController.
|
|
13
|
+
*/
|
|
14
|
+
export function testSecretsApi({ send, ws }: ApiContext) {
|
|
15
|
+
return {
|
|
16
|
+
getServiceTestSecrets: (workspaceId: string, blockId: string) =>
|
|
17
|
+
send(getServiceTestSecretsContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
|
|
18
|
+
|
|
19
|
+
setServiceTestSecrets: (
|
|
20
|
+
workspaceId: string,
|
|
21
|
+
blockId: string,
|
|
22
|
+
body: UpsertServiceTestSecretsInput,
|
|
23
|
+
) =>
|
|
24
|
+
send(setServiceTestSecretsContract, {
|
|
25
|
+
pathPrefix: ws(workspaceId),
|
|
26
|
+
pathParams: { blockId },
|
|
27
|
+
body,
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
deleteServiceTestSecrets: (workspaceId: string, blockId: string) =>
|
|
31
|
+
send(deleteServiceTestSecretsContract, {
|
|
32
|
+
pathPrefix: ws(workspaceId),
|
|
33
|
+
pathParams: { blockId },
|
|
34
|
+
}),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -35,6 +35,7 @@ import { reviewsApi } from './api/reviews'
|
|
|
35
35
|
import { slackApi } from './api/slack'
|
|
36
36
|
import { specApi } from './api/spec'
|
|
37
37
|
import { tasksApi } from './api/tasks'
|
|
38
|
+
import { testSecretsApi } from './api/testSecrets'
|
|
38
39
|
import { userSecretsApi } from './api/userSecrets'
|
|
39
40
|
import { userSettingsApi } from './api/userSettings'
|
|
40
41
|
import { workspacesApi } from './api/workspaces'
|
|
@@ -123,6 +124,7 @@ export function useApi() {
|
|
|
123
124
|
...docInterviewApi(ctx),
|
|
124
125
|
...provisioningLogsApi(ctx),
|
|
125
126
|
...releaseHealthApi(ctx),
|
|
127
|
+
...testSecretsApi(ctx),
|
|
126
128
|
...packageRegistriesApi(ctx),
|
|
127
129
|
...previewApi(ctx),
|
|
128
130
|
...environmentsApi(ctx),
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { TestSecretRef, UpsertServiceTestSecretsInput } from '~/types/testSecrets'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
import { apiErrorStatus } from '~/composables/api/errors'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A service frame's SENSITIVE test secrets (a third-party token a Tester needs to
|
|
9
|
+
* exercise an integration). Sealed on the backend and delivered to the container out of
|
|
10
|
+
* band; the store only ever holds the non-secret refs (key + description) — values are
|
|
11
|
+
* write-only and never read back. Loaded on demand per service frame (the inspector
|
|
12
|
+
* panel), not from the snapshot, since the secrets never leave the server.
|
|
13
|
+
*/
|
|
14
|
+
export const useTestSecretsStore = defineStore('testSecrets', () => {
|
|
15
|
+
const api = useApi()
|
|
16
|
+
|
|
17
|
+
// Per service-frame block id → the configured secret refs (key + description).
|
|
18
|
+
const byBlock = ref<Record<string, TestSecretRef[]>>({})
|
|
19
|
+
const loading = ref(false)
|
|
20
|
+
// Mirrors the backend's opt-in gate (the controller 503s when ENCRYPTION_KEY is absent):
|
|
21
|
+
// `null` until first probed, then `true`/`false`. The inspector panel hides itself when
|
|
22
|
+
// this is false, so a deployment with no sealed-secret store doesn't surface a dead control.
|
|
23
|
+
const available = ref<boolean | null>(null)
|
|
24
|
+
const inFlight = new Map<string, Promise<void>>()
|
|
25
|
+
|
|
26
|
+
/** Force a refresh of one block's configured secret refs (used after a save/clear). */
|
|
27
|
+
async function load(blockId: string) {
|
|
28
|
+
const ws = useWorkspaceStore()
|
|
29
|
+
loading.value = true
|
|
30
|
+
try {
|
|
31
|
+
const view = await api.getServiceTestSecrets(ws.requireId(), blockId)
|
|
32
|
+
byBlock.value[blockId] = view.entries
|
|
33
|
+
available.value = true
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (apiErrorStatus(err) === 503) {
|
|
36
|
+
// A definitive 503 means the store is unconfigured (no encryption key on the
|
|
37
|
+
// backend): hide the UI entry point and stop probing.
|
|
38
|
+
available.value = false
|
|
39
|
+
byBlock.value[blockId] = []
|
|
40
|
+
}
|
|
41
|
+
// Any other failure (transient 5xx / network) is left untouched: it must not hide an
|
|
42
|
+
// already-available panel nor cache a false "unavailable". `available` stays `null`
|
|
43
|
+
// when never probed, so `ensureLoaded` remains retryable on the next open.
|
|
44
|
+
} finally {
|
|
45
|
+
loading.value = false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load one block's refs once and share the result, coalescing concurrent callers for the
|
|
51
|
+
* SAME block. `load()` forces a refresh.
|
|
52
|
+
*/
|
|
53
|
+
async function ensureLoaded(blockId: string) {
|
|
54
|
+
// Store known-unconfigured (a definitive 503) is a deployment-level fact — don't re-probe
|
|
55
|
+
// per service frame; the panel is hidden anyway.
|
|
56
|
+
if (available.value === false) return
|
|
57
|
+
if (byBlock.value[blockId] !== undefined) return
|
|
58
|
+
if (!inFlight.has(blockId)) {
|
|
59
|
+
inFlight.set(
|
|
60
|
+
blockId,
|
|
61
|
+
load(blockId).finally(() => inFlight.delete(blockId)),
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
return inFlight.get(blockId)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The configured refs for a block (empty until loaded). */
|
|
68
|
+
function entriesForBlock(blockId: string): TestSecretRef[] {
|
|
69
|
+
return byBlock.value[blockId] ?? []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Replace a service frame's full secret set (values write-only); empty set clears it. */
|
|
73
|
+
async function save(blockId: string, input: UpsertServiceTestSecretsInput) {
|
|
74
|
+
const ws = useWorkspaceStore()
|
|
75
|
+
const view = await api.setServiceTestSecrets(ws.requireId(), blockId, input)
|
|
76
|
+
byBlock.value[blockId] = view.entries
|
|
77
|
+
available.value = true
|
|
78
|
+
return view
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Remove all of a service frame's secrets. */
|
|
82
|
+
async function clear(blockId: string) {
|
|
83
|
+
const ws = useWorkspaceStore()
|
|
84
|
+
await api.deleteServiceTestSecrets(ws.requireId(), blockId)
|
|
85
|
+
byBlock.value[blockId] = []
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { byBlock, loading, available, load, ensureLoaded, entriesForBlock, save, clear }
|
|
89
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Sensitive per-service test-secret shapes (SEALED, write-only). A Tester needs these
|
|
2
|
+
// to exercise a third-party integration (e.g. a Stripe API key). Sealed at rest on the
|
|
3
|
+
// backend and injected into the Tester container OUT OF BAND — never rendered into a
|
|
4
|
+
// prompt or the telemetry snapshot. The view returns only the configured keys +
|
|
5
|
+
// descriptions (`TestSecretRef`); values are write-only and never read back.
|
|
6
|
+
//
|
|
7
|
+
// All wire shapes are sourced from @cat-factory/contracts (single source of truth).
|
|
8
|
+
// See docs/initiatives/tester-environment-access.md (Slice C).
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
TestSecretRef,
|
|
12
|
+
TestSecretEntry,
|
|
13
|
+
ServiceTestSecretsView,
|
|
14
|
+
UpsertServiceTestSecretsInput,
|
|
15
|
+
} from '@cat-factory/contracts'
|
package/i18n/locales/de.json
CHANGED
|
@@ -973,6 +973,27 @@
|
|
|
973
973
|
"clearFailed": "Zuordnung konnte nicht geleert werden",
|
|
974
974
|
"configNoun": "die Release-Health-Konfiguration"
|
|
975
975
|
},
|
|
976
|
+
"testSecrets": {
|
|
977
|
+
"title": "Test-Anmeldedaten (sensibel)",
|
|
978
|
+
"sectionHint": "Sensible Anmeldedaten, die der Tester benötigt, um ein von diesem Dienst genutztes Drittsystem anzusprechen, z. B. den API-Schlüssel eines Zahlungsanbieters. Sie werden verschlüsselt gespeichert und dem Tester als Umgebungsvariablen übergeben; sie erscheinen nie in Prompts oder Protokollen.",
|
|
979
|
+
"warning": "Dies sind echte, sensible Geheimnisse. Sie werden verschlüsselt gespeichert und dem Tester als Umgebungsvariablen übergeben, niemals in einem Prompt oder der Telemetrie des Laufs. Gib keine Produktionsdaten ein, die du nicht rotieren kannst.",
|
|
980
|
+
"replaceNote": "Beim Speichern wird der gesamte Satz für diesen Dienst ersetzt. Nimm jede Anmeldung auf, die du behalten möchtest, und gib ihren Wert erneut ein; alles Ausgelassene wird entfernt.",
|
|
981
|
+
"key": "Variablenname",
|
|
982
|
+
"keyInvalid": "Verwende Buchstaben, Ziffern und Unterstriche und beginne nicht mit einer Ziffer.",
|
|
983
|
+
"description": "Beschreibung",
|
|
984
|
+
"descriptionPlaceholder": "Wofür diese Anmeldung dient",
|
|
985
|
+
"value": "Wert",
|
|
986
|
+
"valuePlaceholder": "Geheimer Wert",
|
|
987
|
+
"addRow": "Anmeldung hinzufügen",
|
|
988
|
+
"removeRow": "Anmeldung entfernen",
|
|
989
|
+
"save": "Anmeldedaten speichern",
|
|
990
|
+
"clear": "Alle löschen",
|
|
991
|
+
"savedToast": "Test-Anmeldedaten gespeichert",
|
|
992
|
+
"saveFailed": "Test-Anmeldedaten konnten nicht gespeichert werden",
|
|
993
|
+
"clearFailed": "Test-Anmeldedaten konnten nicht gelöscht werden",
|
|
994
|
+
"configNoun": "die sensiblen Test-Anmeldedaten",
|
|
995
|
+
"duplicateKey": "Jeder Variablenname muss eindeutig sein."
|
|
996
|
+
},
|
|
976
997
|
"testConfig": {
|
|
977
998
|
"title": "Testinfrastruktur",
|
|
978
999
|
"hint": "Wie eine Testumgebung für diesen Service aufgesetzt wird, wenn eine Pipeline ihn ausführen muss: keine Infrastruktur, eine Docker-Compose-Datei, Kubernetes-Manifeste oder ein benutzerdefinierter Manifesttyp.",
|
package/i18n/locales/en.json
CHANGED
|
@@ -723,6 +723,27 @@
|
|
|
723
723
|
"clearFailed": "Could not clear the mapping",
|
|
724
724
|
"configNoun": "the release health configuration"
|
|
725
725
|
},
|
|
726
|
+
"testSecrets": {
|
|
727
|
+
"title": "Test credentials (sensitive)",
|
|
728
|
+
"sectionHint": "Sensitive credentials the Tester needs to exercise a third-party system this service depends on, e.g. a payment provider's API key. They are encrypted at rest and injected into the Tester's environment as variables; they are never shown in prompts or logs.",
|
|
729
|
+
"warning": "These are real, sensitive secrets. They are encrypted at rest and passed to the Tester as environment variables, never put in a prompt or the run's telemetry. Don't enter production credentials you can't rotate.",
|
|
730
|
+
"replaceNote": "Saving replaces the entire set for this service. Include every credential you want to keep and re-enter its value; anything left out is removed.",
|
|
731
|
+
"key": "Variable name",
|
|
732
|
+
"keyInvalid": "Use letters, digits and underscores, and don't start with a digit.",
|
|
733
|
+
"description": "Description",
|
|
734
|
+
"descriptionPlaceholder": "What this credential is for",
|
|
735
|
+
"value": "Value",
|
|
736
|
+
"valuePlaceholder": "Secret value",
|
|
737
|
+
"addRow": "Add credential",
|
|
738
|
+
"removeRow": "Remove credential",
|
|
739
|
+
"save": "Save credentials",
|
|
740
|
+
"clear": "Clear all",
|
|
741
|
+
"savedToast": "Test credentials saved",
|
|
742
|
+
"saveFailed": "Could not save the test credentials",
|
|
743
|
+
"clearFailed": "Could not clear the test credentials",
|
|
744
|
+
"configNoun": "the sensitive test credentials",
|
|
745
|
+
"duplicateKey": "Each variable name must be unique."
|
|
746
|
+
},
|
|
726
747
|
"testConfig": {
|
|
727
748
|
"title": "Test infrastructure",
|
|
728
749
|
"hint": "How a test environment is stood up for this service when a pipeline needs to run it: no infrastructure, a Docker Compose file, Kubernetes manifests, or a custom manifest type.",
|
package/i18n/locales/es.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "No se pudo limpiar la asignación",
|
|
670
670
|
"configNoun": "la configuración de salud de la versión"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "Credenciales de prueba (sensibles)",
|
|
674
|
+
"sectionHint": "Credenciales sensibles que el Tester necesita para usar un sistema de terceros del que depende este servicio, p. ej. la clave de API de una pasarela de pago. Se almacenan cifradas y se inyectan en el entorno del Tester como variables; nunca se muestran en prompts ni en registros.",
|
|
675
|
+
"warning": "Son secretos reales y sensibles. Se almacenan cifrados y se pasan al Tester como variables de entorno, nunca en un prompt ni en la telemetría de la ejecución. No introduzcas credenciales de producción que no puedas rotar.",
|
|
676
|
+
"replaceNote": "Al guardar se reemplaza el conjunto completo de este servicio. Incluye cada credencial que quieras conservar y vuelve a introducir su valor; lo que se omita se elimina.",
|
|
677
|
+
"key": "Nombre de variable",
|
|
678
|
+
"keyInvalid": "Usa letras, dígitos y guiones bajos, y no empieces por un dígito.",
|
|
679
|
+
"description": "Descripción",
|
|
680
|
+
"descriptionPlaceholder": "Para qué sirve esta credencial",
|
|
681
|
+
"value": "Valor",
|
|
682
|
+
"valuePlaceholder": "Valor secreto",
|
|
683
|
+
"addRow": "Añadir credencial",
|
|
684
|
+
"removeRow": "Eliminar credencial",
|
|
685
|
+
"save": "Guardar credenciales",
|
|
686
|
+
"clear": "Borrar todo",
|
|
687
|
+
"savedToast": "Credenciales de prueba guardadas",
|
|
688
|
+
"saveFailed": "No se pudieron guardar las credenciales de prueba",
|
|
689
|
+
"clearFailed": "No se pudieron borrar las credenciales de prueba",
|
|
690
|
+
"configNoun": "las credenciales de prueba sensibles",
|
|
691
|
+
"duplicateKey": "Cada nombre de variable debe ser único."
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "Infraestructura de pruebas",
|
|
674
695
|
"hint": "Cómo se levanta un entorno de prueba para este servicio cuando un pipeline necesita ejecutarlo: sin infraestructura, un archivo de Docker Compose, manifiestos de Kubernetes o un tipo de manifiesto personalizado.",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "Impossible d'effacer le mappage",
|
|
670
670
|
"configNoun": "la configuration de santé de la version"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "Identifiants de test (sensibles)",
|
|
674
|
+
"sectionHint": "Identifiants sensibles dont le Testeur a besoin pour solliciter un système tiers utilisé par ce service, par exemple la clé d'API d'un prestataire de paiement. Ils sont chiffrés au repos et injectés dans l'environnement du Testeur sous forme de variables ; ils n'apparaissent jamais dans les prompts ni les journaux.",
|
|
675
|
+
"warning": "Ce sont de vrais secrets sensibles. Ils sont chiffrés au repos et transmis au Testeur comme variables d'environnement, jamais dans un prompt ni dans la télémétrie de l'exécution. N'entrez pas d'identifiants de production que vous ne pouvez pas renouveler.",
|
|
676
|
+
"replaceNote": "L'enregistrement remplace l'ensemble complet pour ce service. Incluez chaque identifiant à conserver et saisissez à nouveau sa valeur ; tout ce qui est omis est supprimé.",
|
|
677
|
+
"key": "Nom de variable",
|
|
678
|
+
"keyInvalid": "Utilisez des lettres, des chiffres et des tirets bas, et ne commencez pas par un chiffre.",
|
|
679
|
+
"description": "Description",
|
|
680
|
+
"descriptionPlaceholder": "À quoi sert cet identifiant",
|
|
681
|
+
"value": "Valeur",
|
|
682
|
+
"valuePlaceholder": "Valeur secrète",
|
|
683
|
+
"addRow": "Ajouter un identifiant",
|
|
684
|
+
"removeRow": "Supprimer l'identifiant",
|
|
685
|
+
"save": "Enregistrer les identifiants",
|
|
686
|
+
"clear": "Tout effacer",
|
|
687
|
+
"savedToast": "Identifiants de test enregistrés",
|
|
688
|
+
"saveFailed": "Impossible d'enregistrer les identifiants de test",
|
|
689
|
+
"clearFailed": "Impossible d'effacer les identifiants de test",
|
|
690
|
+
"configNoun": "les identifiants de test sensibles",
|
|
691
|
+
"duplicateKey": "Chaque nom de variable doit être unique."
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "Infrastructure de test",
|
|
674
695
|
"hint": "Comment un environnement de test est mis en place pour ce service quand un pipeline doit l'exécuter : sans infrastructure, un fichier Docker Compose, des manifestes Kubernetes ou un type de manifeste personnalisé.",
|
package/i18n/locales/he.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "לא ניתן לנקות את המיפוי",
|
|
670
670
|
"configNoun": "תצורת בריאות הגרסה"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "פרטי גישה לבדיקה (רגישים)",
|
|
674
|
+
"sectionHint": "פרטי גישה רגישים שהבודק זקוק להם כדי לתקשר עם מערכת צד-שלישי שהשירות הזה תלוי בה, למשל מפתח API של ספק תשלומים. הם מאוחסנים מוצפנים ומוזרקים לסביבת הבודק כמשתני סביבה; הם לעולם אינם מוצגים בהנחיות או ביומנים.",
|
|
675
|
+
"warning": "אלה סודות אמיתיים ורגישים. הם מאוחסנים מוצפנים ומועברים לבודק כמשתני סביבה, לעולם לא בהנחיה או בטלמטריה של הריצה. אל תזינו פרטי גישה של סביבת ייצור שאינכם יכולים להחליף.",
|
|
676
|
+
"replaceNote": "שמירה מחליפה את כל הקבוצה של השירות הזה. כללו כל פרט גישה שברצונכם לשמור והזינו מחדש את ערכו; כל מה שיושמט יימחק.",
|
|
677
|
+
"key": "שם משתנה",
|
|
678
|
+
"keyInvalid": "השתמשו באותיות, ספרות וקו תחתון, ואל תתחילו בספרה.",
|
|
679
|
+
"description": "תיאור",
|
|
680
|
+
"descriptionPlaceholder": "למה משמש פרט הגישה הזה",
|
|
681
|
+
"value": "ערך",
|
|
682
|
+
"valuePlaceholder": "ערך סודי",
|
|
683
|
+
"addRow": "הוספת פרט גישה",
|
|
684
|
+
"removeRow": "הסרת פרט גישה",
|
|
685
|
+
"save": "שמירת פרטי הגישה",
|
|
686
|
+
"clear": "ניקוי הכול",
|
|
687
|
+
"savedToast": "פרטי הגישה לבדיקה נשמרו",
|
|
688
|
+
"saveFailed": "לא ניתן לשמור את פרטי הגישה לבדיקה",
|
|
689
|
+
"clearFailed": "לא ניתן לנקות את פרטי הגישה לבדיקה",
|
|
690
|
+
"configNoun": "פרטי הגישה הרגישים לבדיקה",
|
|
691
|
+
"duplicateKey": "כל שם משתנה חייב להיות ייחודי."
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "תשתית בדיקות",
|
|
674
695
|
"hint": "כיצד מוקמת סביבת בדיקה לשירות זה כאשר פייפליין צריך להריץ אותו: ללא תשתית, קובץ Docker Compose, מניפסטים של Kubernetes או סוג מניפסט מותאם אישית.",
|
package/i18n/locales/it.json
CHANGED
|
@@ -973,6 +973,27 @@
|
|
|
973
973
|
"clearFailed": "Impossibile cancellare il mapping",
|
|
974
974
|
"configNoun": "la configurazione dello stato di rilascio"
|
|
975
975
|
},
|
|
976
|
+
"testSecrets": {
|
|
977
|
+
"title": "Credenziali di test (sensibili)",
|
|
978
|
+
"sectionHint": "Credenziali sensibili di cui il Tester ha bisogno per interagire con un sistema di terze parti da cui dipende questo servizio, ad esempio la chiave API di un fornitore di pagamenti. Sono cifrate a riposo e iniettate nell'ambiente del Tester come variabili; non compaiono mai nei prompt né nei log.",
|
|
979
|
+
"warning": "Sono segreti reali e sensibili. Vengono cifrati a riposo e passati al Tester come variabili d'ambiente, mai in un prompt o nella telemetria dell'esecuzione. Non inserire credenziali di produzione che non puoi ruotare.",
|
|
980
|
+
"replaceNote": "Il salvataggio sostituisce l'intero insieme per questo servizio. Includi ogni credenziale che vuoi mantenere e reinserisci il suo valore; tutto ciò che viene omesso viene rimosso.",
|
|
981
|
+
"key": "Nome variabile",
|
|
982
|
+
"keyInvalid": "Usa lettere, cifre e trattini bassi e non iniziare con una cifra.",
|
|
983
|
+
"description": "Descrizione",
|
|
984
|
+
"descriptionPlaceholder": "A cosa serve questa credenziale",
|
|
985
|
+
"value": "Valore",
|
|
986
|
+
"valuePlaceholder": "Valore segreto",
|
|
987
|
+
"addRow": "Aggiungi credenziale",
|
|
988
|
+
"removeRow": "Rimuovi credenziale",
|
|
989
|
+
"save": "Salva credenziali",
|
|
990
|
+
"clear": "Cancella tutto",
|
|
991
|
+
"savedToast": "Credenziali di test salvate",
|
|
992
|
+
"saveFailed": "Impossibile salvare le credenziali di test",
|
|
993
|
+
"clearFailed": "Impossibile cancellare le credenziali di test",
|
|
994
|
+
"configNoun": "le credenziali di test sensibili",
|
|
995
|
+
"duplicateKey": "Ogni nome di variabile deve essere univoco."
|
|
996
|
+
},
|
|
976
997
|
"testConfig": {
|
|
977
998
|
"title": "Infrastruttura di test",
|
|
978
999
|
"hint": "Come viene predisposto un ambiente di test per questo servizio quando una pipeline deve eseguirlo: nessuna infrastruttura, un file Docker Compose, manifest Kubernetes, o un tipo di manifest personalizzato.",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "マッピングをクリアできませんでした",
|
|
670
670
|
"configNoun": "リリースヘルス設定"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "テスト用認証情報(機密)",
|
|
674
|
+
"sectionHint": "このサービスが依存するサードパーティのシステム(例:決済プロバイダーの API キー)をテスターが操作するために必要な、機密性の高い認証情報です。保存時に暗号化され、環境変数としてテスターの環境に注入されます。プロンプトやログには決して表示されません。",
|
|
675
|
+
"warning": "これらは実際の機密情報です。保存時に暗号化され、環境変数としてテスターに渡されます。プロンプトや実行のテレメトリーには一切含まれません。ローテーションできない本番用の認証情報は入力しないでください。",
|
|
676
|
+
"replaceNote": "保存すると、このサービスのセット全体が置き換えられます。残しておきたい認証情報はすべて含め、その値を再入力してください。省略したものは削除されます。",
|
|
677
|
+
"key": "変数名",
|
|
678
|
+
"keyInvalid": "英字・数字・アンダースコアを使用し、数字で始めないでください。",
|
|
679
|
+
"description": "説明",
|
|
680
|
+
"descriptionPlaceholder": "この認証情報の用途",
|
|
681
|
+
"value": "値",
|
|
682
|
+
"valuePlaceholder": "秘密の値",
|
|
683
|
+
"addRow": "認証情報を追加",
|
|
684
|
+
"removeRow": "認証情報を削除",
|
|
685
|
+
"save": "認証情報を保存",
|
|
686
|
+
"clear": "すべて消去",
|
|
687
|
+
"savedToast": "テスト用認証情報を保存しました",
|
|
688
|
+
"saveFailed": "テスト用認証情報を保存できませんでした",
|
|
689
|
+
"clearFailed": "テスト用認証情報を消去できませんでした",
|
|
690
|
+
"configNoun": "機密のテスト用認証情報",
|
|
691
|
+
"duplicateKey": "変数名はそれぞれ一意である必要があります。"
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "テストインフラ",
|
|
674
695
|
"hint": "パイプラインがこのサービスを実行する必要があるときに、テスト環境をどう立ち上げるか: インフラなし、Docker Compose ファイル、Kubernetes マニフェスト、またはカスタムマニフェストタイプ。",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "Nie udało się wyczyścić mapowania",
|
|
670
670
|
"configNoun": "konfigurację kondycji wydania"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "Poświadczenia testowe (wrażliwe)",
|
|
674
|
+
"sectionHint": "Wrażliwe poświadczenia potrzebne Testerowi do korzystania z zewnętrznego systemu, od którego zależy ta usługa, np. klucz API dostawcy płatności. Są przechowywane w postaci zaszyfrowanej i wstrzykiwane do środowiska Testera jako zmienne; nigdy nie pojawiają się w promptach ani w logach.",
|
|
675
|
+
"warning": "To prawdziwe, wrażliwe sekrety. Są przechowywane w postaci zaszyfrowanej i przekazywane Testerowi jako zmienne środowiskowe, nigdy w promptcie ani w telemetrii przebiegu. Nie wprowadzaj poświadczeń produkcyjnych, których nie możesz zmienić.",
|
|
676
|
+
"replaceNote": "Zapis zastępuje cały zestaw dla tej usługi. Uwzględnij każde poświadczenie, które chcesz zachować, i ponownie wprowadź jego wartość; wszystko pominięte zostanie usunięte.",
|
|
677
|
+
"key": "Nazwa zmiennej",
|
|
678
|
+
"keyInvalid": "Używaj liter, cyfr i podkreśleń i nie zaczynaj od cyfry.",
|
|
679
|
+
"description": "Opis",
|
|
680
|
+
"descriptionPlaceholder": "Do czego służy to poświadczenie",
|
|
681
|
+
"value": "Wartość",
|
|
682
|
+
"valuePlaceholder": "Tajna wartość",
|
|
683
|
+
"addRow": "Dodaj poświadczenie",
|
|
684
|
+
"removeRow": "Usuń poświadczenie",
|
|
685
|
+
"save": "Zapisz poświadczenia",
|
|
686
|
+
"clear": "Wyczyść wszystko",
|
|
687
|
+
"savedToast": "Zapisano poświadczenia testowe",
|
|
688
|
+
"saveFailed": "Nie udało się zapisać poświadczeń testowych",
|
|
689
|
+
"clearFailed": "Nie udało się wyczyścić poświadczeń testowych",
|
|
690
|
+
"configNoun": "wrażliwe poświadczenia testowe",
|
|
691
|
+
"duplicateKey": "Każda nazwa zmiennej musi być unikalna."
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "Infrastruktura testowa",
|
|
674
695
|
"hint": "Jak stawiane jest środowisko testowe dla tej usługi, gdy potok musi ją uruchomić: bez infrastruktury, plik Docker Compose, manifesty Kubernetes lub niestandardowy typ manifestu.",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "Eşleme temizlenemedi",
|
|
670
670
|
"configNoun": "sürüm sağlığı yapılandırması"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "Test kimlik bilgileri (hassas)",
|
|
674
|
+
"sectionHint": "Test Aracısının, bu hizmetin bağlı olduğu bir üçüncü taraf sistemi kullanması için gereken hassas kimlik bilgileri; örneğin bir ödeme sağlayıcısının API anahtarı. Bekleme sırasında şifrelenir ve Test Aracısının ortamına değişken olarak enjekte edilir; istemlerde veya günlüklerde asla gösterilmez.",
|
|
675
|
+
"warning": "Bunlar gerçek, hassas sırlardır. Bekleme sırasında şifrelenir ve Test Aracısına ortam değişkenleri olarak iletilir; asla bir isteme ya da çalıştırma telemetrisine konmaz. Değiştiremeyeceğiniz üretim kimlik bilgilerini girmeyin.",
|
|
676
|
+
"replaceNote": "Kaydetmek, bu hizmete ait kümenin tamamını değiştirir. Tutmak istediğiniz her kimlik bilgisini ekleyin ve değerini yeniden girin; dışarıda bırakılan her şey kaldırılır.",
|
|
677
|
+
"key": "Değişken adı",
|
|
678
|
+
"keyInvalid": "Harf, rakam ve alt çizgi kullanın ve rakamla başlamayın.",
|
|
679
|
+
"description": "Açıklama",
|
|
680
|
+
"descriptionPlaceholder": "Bu kimlik bilgisi ne için",
|
|
681
|
+
"value": "Değer",
|
|
682
|
+
"valuePlaceholder": "Gizli değer",
|
|
683
|
+
"addRow": "Kimlik bilgisi ekle",
|
|
684
|
+
"removeRow": "Kimlik bilgisini kaldır",
|
|
685
|
+
"save": "Kimlik bilgilerini kaydet",
|
|
686
|
+
"clear": "Tümünü temizle",
|
|
687
|
+
"savedToast": "Test kimlik bilgileri kaydedildi",
|
|
688
|
+
"saveFailed": "Test kimlik bilgileri kaydedilemedi",
|
|
689
|
+
"clearFailed": "Test kimlik bilgileri temizlenemedi",
|
|
690
|
+
"configNoun": "hassas test kimlik bilgileri",
|
|
691
|
+
"duplicateKey": "Her değişken adı benzersiz olmalıdır."
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "Test altyapısı",
|
|
674
695
|
"hint": "Bir pipeline bu servisi çalıştırmak istediğinde test ortamının nasıl kurulacağı: altyapısız, bir Docker Compose dosyası, Kubernetes manifestoları veya özel bir manifest türü.",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -669,6 +669,27 @@
|
|
|
669
669
|
"clearFailed": "Не вдалося очистити зіставлення",
|
|
670
670
|
"configNoun": "конфігурацію стану випуску"
|
|
671
671
|
},
|
|
672
|
+
"testSecrets": {
|
|
673
|
+
"title": "Тестові облікові дані (конфіденційні)",
|
|
674
|
+
"sectionHint": "Конфіденційні облікові дані, потрібні Тестувальнику для взаємодії зі сторонньою системою, від якої залежить цей сервіс, наприклад ключ API платіжного провайдера. Вони зберігаються в зашифрованому вигляді й додаються в середовище Тестувальника як змінні; вони ніколи не показуються в підказках чи журналах.",
|
|
675
|
+
"warning": "Це справжні конфіденційні секрети. Вони зберігаються зашифрованими й передаються Тестувальнику як змінні середовища, ніколи в підказці чи телеметрії запуску. Не вводьте робочі облікові дані, які ви не можете змінити.",
|
|
676
|
+
"replaceNote": "Збереження замінює весь набір для цього сервісу. Додайте кожні облікові дані, які хочете зберегти, і введіть їхнє значення знову; усе пропущене буде видалено.",
|
|
677
|
+
"key": "Назва змінної",
|
|
678
|
+
"keyInvalid": "Використовуйте літери, цифри та підкреслення й не починайте з цифри.",
|
|
679
|
+
"description": "Опис",
|
|
680
|
+
"descriptionPlaceholder": "Для чого ці облікові дані",
|
|
681
|
+
"value": "Значення",
|
|
682
|
+
"valuePlaceholder": "Секретне значення",
|
|
683
|
+
"addRow": "Додати облікові дані",
|
|
684
|
+
"removeRow": "Видалити облікові дані",
|
|
685
|
+
"save": "Зберегти облікові дані",
|
|
686
|
+
"clear": "Очистити все",
|
|
687
|
+
"savedToast": "Тестові облікові дані збережено",
|
|
688
|
+
"saveFailed": "Не вдалося зберегти тестові облікові дані",
|
|
689
|
+
"clearFailed": "Не вдалося очистити тестові облікові дані",
|
|
690
|
+
"configNoun": "конфіденційні тестові облікові дані",
|
|
691
|
+
"duplicateKey": "Кожна назва змінної має бути унікальною."
|
|
692
|
+
},
|
|
672
693
|
"testConfig": {
|
|
673
694
|
"title": "Тестова інфраструктура",
|
|
674
695
|
"hint": "Як розгортається тестове середовище для цього сервісу, коли конвеєру потрібно його запустити: без інфраструктури, файл Docker Compose, маніфести Kubernetes або власний тип маніфесту.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.111.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",
|