@cat-factory/app 0.208.1 → 0.209.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/settings/CapabilityCredentialsPanel.vue +262 -0
- package/app/components/settings/InfrastructureWindow.logic.spec.ts +28 -3
- package/app/components/settings/InfrastructureWindow.logic.ts +15 -1
- package/app/components/settings/InfrastructureWindow.vue +23 -0
- package/app/composables/api/capabilityCredentials.ts +37 -0
- package/app/composables/useApi.ts +2 -0
- package/app/stores/capabilityCredentials.spec.ts +163 -0
- package/app/stores/capabilityCredentials.ts +102 -0
- package/app/types/capabilityCredentials.ts +12 -0
- package/app/types/providerConnections.ts +5 -1
- package/i18n/locales/de.json +34 -0
- package/i18n/locales/en.json +34 -0
- package/i18n/locales/es.json +34 -0
- package/i18n/locales/fr.json +34 -0
- package/i18n/locales/he.json +34 -0
- package/i18n/locales/it.json +34 -0
- package/i18n/locales/ja.json +34 -0
- package/i18n/locales/pl.json +34 -0
- package/i18n/locales/tr.json +34 -0
- package/i18n/locales/uk.json +34 -0
- package/package.json +2 -2
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Capability credentials — the sealed, per-workspace values behind the secrets a registered tool
|
|
3
|
+
// server (MCP) or generative binary integration declares BY NAME. It renders as a CHECKLIST, not
|
|
4
|
+
// a blank key-value form: which keys exist is a property of the deployment's CODE, so the panel
|
|
5
|
+
// projects the declarations and the operator fills them in. A blank form would mean reading the
|
|
6
|
+
// deployment's source to learn what to type, with a typo producing a sealed row nothing asks for.
|
|
7
|
+
//
|
|
8
|
+
// Values are write-only and saved ONE KEY AT A TIME. The whole-set write is unusable here: this
|
|
9
|
+
// client never receives the values, so replacing the set would delete every credential the
|
|
10
|
+
// operator did not retype in this sitting.
|
|
11
|
+
//
|
|
12
|
+
// Renders inside the Infrastructure window's "Capability credentials" tab: what an agent's tools
|
|
13
|
+
// authenticate as is part of where agents RUN. `secrets.manage`-gated end to end (the READ
|
|
14
|
+
// included — the view names the deployment's credential keys), so the tab is HIDDEN, never
|
|
15
|
+
// disabled, for anyone without it.
|
|
16
|
+
import { computed, onMounted, reactive, ref } from 'vue'
|
|
17
|
+
import type { CapabilityCredentialStatus } from '~/types/capabilityCredentials'
|
|
18
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
19
|
+
|
|
20
|
+
const { t, d } = useI18n()
|
|
21
|
+
const store = useCapabilityCredentialsStore()
|
|
22
|
+
const toast = useToast()
|
|
23
|
+
const { confirmAction, toastDone } = useConfirmAction()
|
|
24
|
+
|
|
25
|
+
// Which declaring capability wants a key. An exhaustive Record over the wire union, so a new
|
|
26
|
+
// subject fails to compile until it has translated copy — the sanctioned guard for an enum-keyed
|
|
27
|
+
// lookup the typed-message-key check cannot see.
|
|
28
|
+
type CredentialSubject = CapabilityCredentialStatus['declaredBy'][number]['subject']
|
|
29
|
+
const SUBJECT_LABELS = computed<Record<CredentialSubject, string>>(() => ({
|
|
30
|
+
'tool-server': t('settings.capabilityCredentials.subject.toolServer'),
|
|
31
|
+
'binary-generator': t('settings.capabilityCredentials.subject.binaryGenerator'),
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
// Draft values, keyed by credential key. Never prefilled: nothing here was ever read back, and a
|
|
35
|
+
// masked placeholder standing in for a stored value would make "unchanged" and "retyped" look
|
|
36
|
+
// identical at the save button.
|
|
37
|
+
const drafts = reactive<Record<string, string>>({})
|
|
38
|
+
// Which row's WHICH button is in flight. The action is part of the state because save and delete
|
|
39
|
+
// sit beside each other on one row: a shared per-key flag would spin the delete button for the
|
|
40
|
+
// save the user just clicked, which reads as a delete in progress.
|
|
41
|
+
const busy = ref<{ key: string; action: 'save' | 'remove' } | null>(null)
|
|
42
|
+
// Failures present through the shared status-class funnel (translated description up front, the
|
|
43
|
+
// raw backend prose + requestId behind "Show details"), never raw `e.message` as the description.
|
|
44
|
+
const { present } = usePipelineErrorToast()
|
|
45
|
+
|
|
46
|
+
const view = computed(() => store.view)
|
|
47
|
+
|
|
48
|
+
function isBusy(key: string, action: 'save' | 'remove') {
|
|
49
|
+
return busy.value?.key === key && busy.value.action === action
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The tab this renders in only exists once the window's probe resolved `available === true`, so
|
|
53
|
+
// `ensureLoaded()` here would early-return every time and this error branch would be dead code.
|
|
54
|
+
// Read outright instead: the window owns the PROBE (a failure there means no tab), the panel owns
|
|
55
|
+
// the DATA (a failure here means the reader is looking at a list we could not fetch, and must be
|
|
56
|
+
// told). It also drops the staleness `ensureLoaded` carried, so reopening the tab after the
|
|
57
|
+
// deployment registered a new capability shows the new key.
|
|
58
|
+
onMounted(async () => {
|
|
59
|
+
try {
|
|
60
|
+
await store.load()
|
|
61
|
+
} catch (e) {
|
|
62
|
+
present(e, 'settings.capabilityCredentials.toast.loadFailed')
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
async function saveKey(key: string) {
|
|
67
|
+
const value = (drafts[key] ?? '').trim()
|
|
68
|
+
if (!value) return
|
|
69
|
+
busy.value = { key, action: 'save' }
|
|
70
|
+
try {
|
|
71
|
+
await store.save(key, value)
|
|
72
|
+
drafts[key] = ''
|
|
73
|
+
toast.add({
|
|
74
|
+
title: t('settings.capabilityCredentials.toast.saved', { key }),
|
|
75
|
+
icon: 'i-lucide-check',
|
|
76
|
+
color: 'success',
|
|
77
|
+
})
|
|
78
|
+
} catch (e) {
|
|
79
|
+
present(e, 'settings.capabilityCredentials.toast.saveFailed')
|
|
80
|
+
} finally {
|
|
81
|
+
busy.value = null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function removeKey(key: string) {
|
|
86
|
+
const noun = t('settings.capabilityCredentials.credentialNoun', { key })
|
|
87
|
+
if (!(await confirmAction('remove', noun))) return
|
|
88
|
+
busy.value = { key, action: 'remove' }
|
|
89
|
+
try {
|
|
90
|
+
await store.remove(key)
|
|
91
|
+
toastDone('remove', noun)
|
|
92
|
+
} catch (e) {
|
|
93
|
+
present(e, 'settings.capabilityCredentials.toast.removeFailed')
|
|
94
|
+
} finally {
|
|
95
|
+
busy.value = null
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<div class="space-y-4" data-testid="capability-credentials-panel">
|
|
102
|
+
<p class="text-sm text-slate-400">
|
|
103
|
+
{{ t('settings.capabilityCredentials.intro') }}
|
|
104
|
+
</p>
|
|
105
|
+
|
|
106
|
+
<!-- The declaration read failed (the deployment's generative integrations could not be
|
|
107
|
+
reached), so this checklist may be SHORT and the orphan list is withheld. Said out loud
|
|
108
|
+
rather than rendered as a clean empty list: an outage and "nothing needs a credential"
|
|
109
|
+
are the same list and opposite facts. -->
|
|
110
|
+
<UAlert
|
|
111
|
+
v-if="view?.declarationsIncomplete"
|
|
112
|
+
color="warning"
|
|
113
|
+
variant="subtle"
|
|
114
|
+
icon="i-lucide-triangle-alert"
|
|
115
|
+
:title="t('settings.capabilityCredentials.incomplete.title')"
|
|
116
|
+
:description="t('settings.capabilityCredentials.incomplete.body')"
|
|
117
|
+
data-testid="capability-credentials-incomplete"
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
<section
|
|
121
|
+
v-for="entry in view?.declared ?? []"
|
|
122
|
+
:key="entry.key"
|
|
123
|
+
class="space-y-3 rounded-lg border border-slate-700 p-3"
|
|
124
|
+
:data-testid="`capability-credential-${entry.key}`"
|
|
125
|
+
>
|
|
126
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
127
|
+
<code class="font-mono text-sm font-medium">{{ entry.key }}</code>
|
|
128
|
+
<UBadge v-if="entry.required" color="warning" variant="soft" size="sm">
|
|
129
|
+
{{ t('settings.capabilityCredentials.required') }}
|
|
130
|
+
</UBadge>
|
|
131
|
+
<UBadge v-else color="neutral" variant="soft" size="sm">
|
|
132
|
+
{{ t('settings.capabilityCredentials.optional') }}
|
|
133
|
+
</UBadge>
|
|
134
|
+
<UBadge
|
|
135
|
+
v-if="entry.stored"
|
|
136
|
+
color="success"
|
|
137
|
+
variant="soft"
|
|
138
|
+
size="sm"
|
|
139
|
+
:data-testid="`capability-credential-stored-${entry.key}`"
|
|
140
|
+
>
|
|
141
|
+
{{ t('settings.capabilityCredentials.stored') }}
|
|
142
|
+
</UBadge>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Who wants the value, so an operator can tell what they are about to change. One key is
|
|
146
|
+
routinely wanted by more than one capability (two integrations behind one vendor
|
|
147
|
+
account), which is exactly when a rotation has consequences beyond the row it edits. -->
|
|
148
|
+
<ul class="space-y-1 text-xs text-slate-400">
|
|
149
|
+
<li v-for="declarer in entry.declaredBy" :key="`${declarer.subject}:${declarer.id}`">
|
|
150
|
+
<span class="text-slate-300">{{ declarer.label }}</span>
|
|
151
|
+
<span class="text-slate-500"> · {{ SUBJECT_LABELS[declarer.subject] }}</span>
|
|
152
|
+
<span v-if="declarer.usage" class="block text-slate-500">{{ declarer.usage }}</span>
|
|
153
|
+
</li>
|
|
154
|
+
</ul>
|
|
155
|
+
|
|
156
|
+
<p v-if="entry.stored && entry.updatedAt" class="text-[11px] text-slate-500">
|
|
157
|
+
{{
|
|
158
|
+
t('settings.capabilityCredentials.storedAt', {
|
|
159
|
+
date: d(new Date(entry.updatedAt), 'short'),
|
|
160
|
+
})
|
|
161
|
+
}}
|
|
162
|
+
</p>
|
|
163
|
+
<!-- An EMPTY row is not the same fact in both deployments, so it must not read the same
|
|
164
|
+
way. Behind the store the deployment's own environment may still answer for this key,
|
|
165
|
+
and calling that "missing" would send an operator hunting for a value that is already
|
|
166
|
+
resolving. -->
|
|
167
|
+
<p v-else-if="view?.environmentFallback" class="text-[11px] text-slate-500">
|
|
168
|
+
{{ t('settings.capabilityCredentials.notStoredWithFallback') }}
|
|
169
|
+
</p>
|
|
170
|
+
<p v-else class="text-[11px] text-amber-400">
|
|
171
|
+
{{ t('settings.capabilityCredentials.notStored') }}
|
|
172
|
+
</p>
|
|
173
|
+
|
|
174
|
+
<div class="flex items-end gap-2">
|
|
175
|
+
<UFormField
|
|
176
|
+
class="flex-1"
|
|
177
|
+
:label="
|
|
178
|
+
entry.stored
|
|
179
|
+
? t('settings.capabilityCredentials.replaceValue')
|
|
180
|
+
: t('settings.capabilityCredentials.setValue')
|
|
181
|
+
"
|
|
182
|
+
>
|
|
183
|
+
<SecretInput
|
|
184
|
+
v-model="drafts[entry.key]"
|
|
185
|
+
class="w-full"
|
|
186
|
+
:data-testid="`capability-credential-input-${entry.key}`"
|
|
187
|
+
@keyup.enter="saveKey(entry.key)"
|
|
188
|
+
/>
|
|
189
|
+
</UFormField>
|
|
190
|
+
<UButton
|
|
191
|
+
:loading="isBusy(entry.key, 'save')"
|
|
192
|
+
:disabled="!(drafts[entry.key] ?? '').trim() || isBusy(entry.key, 'remove')"
|
|
193
|
+
:data-testid="`capability-credential-save-${entry.key}`"
|
|
194
|
+
@click="saveKey(entry.key)"
|
|
195
|
+
>
|
|
196
|
+
{{ t('settings.capabilityCredentials.save') }}
|
|
197
|
+
</UButton>
|
|
198
|
+
<UButton
|
|
199
|
+
v-if="entry.stored"
|
|
200
|
+
color="error"
|
|
201
|
+
variant="ghost"
|
|
202
|
+
icon="i-lucide-trash-2"
|
|
203
|
+
:loading="isBusy(entry.key, 'remove')"
|
|
204
|
+
:disabled="isBusy(entry.key, 'save')"
|
|
205
|
+
:data-testid="`capability-credential-delete-${entry.key}`"
|
|
206
|
+
:aria-label="t('settings.capabilityCredentials.remove')"
|
|
207
|
+
@click="removeKey(entry.key)"
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
</section>
|
|
211
|
+
|
|
212
|
+
<p
|
|
213
|
+
v-if="view && !view.declared.length && !view.declarationsIncomplete"
|
|
214
|
+
class="text-sm text-slate-500"
|
|
215
|
+
>
|
|
216
|
+
{{ t('settings.capabilityCredentials.noneDeclared') }}
|
|
217
|
+
</p>
|
|
218
|
+
|
|
219
|
+
<!-- Stored keys nothing declares any more: a live secret nobody will ever ask for, which is
|
|
220
|
+
what a retired integration or a renamed variable leaves behind. Listed rather than
|
|
221
|
+
filtered, because only the operator can tell "delete this" from "the deployment
|
|
222
|
+
regressed". Withheld entirely while the declaration read is incomplete. -->
|
|
223
|
+
<section
|
|
224
|
+
v-if="view?.orphaned.length"
|
|
225
|
+
class="space-y-2 rounded-lg border border-amber-900/60 p-3"
|
|
226
|
+
data-testid="capability-credentials-orphaned"
|
|
227
|
+
>
|
|
228
|
+
<h3 class="text-sm font-semibold">
|
|
229
|
+
{{ t('settings.capabilityCredentials.orphaned.heading') }}
|
|
230
|
+
</h3>
|
|
231
|
+
<p class="text-xs text-slate-400">
|
|
232
|
+
{{ t('settings.capabilityCredentials.orphaned.body') }}
|
|
233
|
+
</p>
|
|
234
|
+
<div
|
|
235
|
+
v-for="orphan in view.orphaned"
|
|
236
|
+
:key="orphan.key"
|
|
237
|
+
class="flex items-center justify-between gap-2 rounded-md border border-slate-800 px-3 py-2"
|
|
238
|
+
>
|
|
239
|
+
<div class="min-w-0">
|
|
240
|
+
<code class="font-mono text-sm">{{ orphan.key }}</code>
|
|
241
|
+
<span class="block text-[11px] text-slate-500">
|
|
242
|
+
{{
|
|
243
|
+
t('settings.capabilityCredentials.storedAt', {
|
|
244
|
+
date: d(new Date(orphan.updatedAt), 'short'),
|
|
245
|
+
})
|
|
246
|
+
}}
|
|
247
|
+
</span>
|
|
248
|
+
</div>
|
|
249
|
+
<UButton
|
|
250
|
+
color="error"
|
|
251
|
+
variant="ghost"
|
|
252
|
+
icon="i-lucide-trash-2"
|
|
253
|
+
size="sm"
|
|
254
|
+
:loading="isBusy(orphan.key, 'remove')"
|
|
255
|
+
:data-testid="`capability-credential-delete-${orphan.key}`"
|
|
256
|
+
:aria-label="t('settings.capabilityCredentials.remove')"
|
|
257
|
+
@click="removeKey(orphan.key)"
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
</section>
|
|
261
|
+
</div>
|
|
262
|
+
</template>
|
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
repinInfrastructureTab,
|
|
6
6
|
} from './InfrastructureWindow.logic'
|
|
7
7
|
|
|
8
|
-
const NONE = {
|
|
8
|
+
const NONE = {
|
|
9
|
+
agents: false,
|
|
10
|
+
environments: false,
|
|
11
|
+
packageRegistries: false,
|
|
12
|
+
capabilityCredentials: false,
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
describe('infrastructureTabs', () => {
|
|
11
16
|
it('shows nothing when no probe reports a backend', () => {
|
|
@@ -28,10 +33,30 @@ describe('infrastructureTabs', () => {
|
|
|
28
33
|
expect(infrastructureTabs({ ...NONE, agents: true })).toEqual(['runner-pool'])
|
|
29
34
|
})
|
|
30
35
|
|
|
36
|
+
it('gates the capability-credentials tab on its own two-part probe', () => {
|
|
37
|
+
// Unlike its neighbours this one also gates on CONTENT: the panel is a checklist projected
|
|
38
|
+
// from the deployment's registered capabilities, so a build that registers none has no
|
|
39
|
+
// credential to type. The window folds that (and the `secrets.manage` check) into the flag.
|
|
40
|
+
expect(infrastructureTabs({ ...NONE, capabilityCredentials: true })).toEqual([
|
|
41
|
+
'capability-credentials',
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
31
45
|
it('orders tabs by the question they answer, not by which probe resolved', () => {
|
|
32
46
|
expect(
|
|
33
|
-
infrastructureTabs({
|
|
34
|
-
|
|
47
|
+
infrastructureTabs({
|
|
48
|
+
agents: true,
|
|
49
|
+
environments: true,
|
|
50
|
+
packageRegistries: true,
|
|
51
|
+
capabilityCredentials: true,
|
|
52
|
+
}),
|
|
53
|
+
).toEqual([
|
|
54
|
+
'runner-pool',
|
|
55
|
+
'environment',
|
|
56
|
+
'shared-stacks',
|
|
57
|
+
'package-registries',
|
|
58
|
+
'capability-credentials',
|
|
59
|
+
])
|
|
35
60
|
})
|
|
36
61
|
})
|
|
37
62
|
|
|
@@ -19,12 +19,25 @@ export interface InfrastructureTabAvailability {
|
|
|
19
19
|
environments: boolean
|
|
20
20
|
/** The package-registries module answered its probe affirmatively (it 503s unconfigured). */
|
|
21
21
|
packageRegistries: boolean
|
|
22
|
+
/**
|
|
23
|
+
* The capability-credential surface has something to show: its probe resolved (the module 503s
|
|
24
|
+
* with no encryption key, and 403s for a caller without `secrets.manage`) AND this deployment's
|
|
25
|
+
* registered capabilities declare a credential, or the workspace stored one nothing declares,
|
|
26
|
+
* or the declaration read failed.
|
|
27
|
+
*
|
|
28
|
+
* Unlike every other tab this one gates on CONTENT as well as availability, because the panel
|
|
29
|
+
* is a CHECKLIST projected from the deployment's code: a build that registers no tool server
|
|
30
|
+
* and no generative integration has no credential to type, so the tab would be a dead end. The
|
|
31
|
+
* failed-read case is deliberately kept IN — an unreadable list and an empty one are the same
|
|
32
|
+
* list and opposite facts, and only the panel can say which one this is.
|
|
33
|
+
*/
|
|
34
|
+
capabilityCredentials: boolean
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
/**
|
|
25
38
|
* The window's tabs, in display order. Order is the reading order of the questions they answer:
|
|
26
39
|
* where agent containers run, where test environments run, what those environments attach to,
|
|
27
|
-
*
|
|
40
|
+
* what a checkout may install from, and what the tools an agent reaches authenticate as.
|
|
28
41
|
*
|
|
29
42
|
* Shared stacks ride the test-environment probe because a stack is infra an environment attaches
|
|
30
43
|
* to — there is nothing to attach without an environment backend.
|
|
@@ -34,6 +47,7 @@ export function infrastructureTabs(available: InfrastructureTabAvailability): In
|
|
|
34
47
|
if (available.agents) tabs.push('runner-pool')
|
|
35
48
|
if (available.environments) tabs.push('environment', 'shared-stacks')
|
|
36
49
|
if (available.packageRegistries) tabs.push('package-registries')
|
|
50
|
+
if (available.capabilityCredentials) tabs.push('capability-credentials')
|
|
37
51
|
return tabs
|
|
38
52
|
}
|
|
39
53
|
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
// - "Package registries" — the private npm registries a checkout installs from (formerly an
|
|
16
16
|
// Integrations-hub row). What a container can resolve its dependencies from is part of the
|
|
17
17
|
// execution environment, not an optional external system a workspace links in.
|
|
18
|
+
// - "Capability credentials" — the sealed per-workspace values behind the secrets a registered
|
|
19
|
+
// tool server (MCP) or generative binary integration declares. What an agent's tools
|
|
20
|
+
// authenticate as belongs beside where those agents run, and it is `secrets.manage`-only, so
|
|
21
|
+
// the tab is HIDDEN rather than disabled for anyone without that permission.
|
|
18
22
|
// Local-specific affordances render inline, gated on `auth.localMode?.enabled`. A tab whose
|
|
19
23
|
// backend integration is disabled (503) simply doesn't render.
|
|
20
24
|
import { computed, ref, watch } from 'vue'
|
|
@@ -31,12 +35,15 @@ import LocalContainerPoolSettings from '~/components/settings/LocalContainerPool
|
|
|
31
35
|
import SharedStacksPanel from '~/components/settings/SharedStacksPanel.vue'
|
|
32
36
|
import ComposeEnvironmentSetupSection from '~/components/settings/ComposeEnvironmentSetupSection.vue'
|
|
33
37
|
import PackageRegistriesPanel from '~/components/settings/PackageRegistriesPanel.vue'
|
|
38
|
+
import CapabilityCredentialsPanel from '~/components/settings/CapabilityCredentialsPanel.vue'
|
|
34
39
|
|
|
35
40
|
const { t } = useI18n()
|
|
36
41
|
const ui = useUiStore()
|
|
37
42
|
const store = useProviderConnectionsStore()
|
|
38
43
|
const auth = useAuthStore()
|
|
39
44
|
const packageRegistries = usePackageRegistriesStore()
|
|
45
|
+
const capabilityCredentials = useCapabilityCredentialsStore()
|
|
46
|
+
const { canManageSecrets } = useWorkspaceAccess()
|
|
40
47
|
|
|
41
48
|
const open = computed({
|
|
42
49
|
get: () => ui.infrastructureOpen,
|
|
@@ -63,12 +70,14 @@ const TAB_LABELS = computed<Record<InfrastructureTab, string>>(() => ({
|
|
|
63
70
|
environment: t('settings.providerConnection.tabs.testEnvironments'),
|
|
64
71
|
'shared-stacks': t('settings.sharedStacks.tab'),
|
|
65
72
|
'package-registries': t('settings.packageRegistries.tab'),
|
|
73
|
+
'capability-credentials': t('settings.capabilityCredentials.tab'),
|
|
66
74
|
}))
|
|
67
75
|
const TAB_ICONS: Record<InfrastructureTab, string> = {
|
|
68
76
|
'runner-pool': 'i-lucide-server-cog',
|
|
69
77
|
environment: 'i-lucide-cloud',
|
|
70
78
|
'shared-stacks': 'i-lucide-layers',
|
|
71
79
|
'package-registries': 'i-lucide-package',
|
|
80
|
+
'capability-credentials': 'i-lucide-key-round',
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
// `slot` mirrors `value` — the template names one `<template #…>` per tab value.
|
|
@@ -79,6 +88,12 @@ const tabs = computed(() =>
|
|
|
79
88
|
// The module's own probe (the backend 503s with no encryption key), same gate as the
|
|
80
89
|
// Integrations-hub row this replaced — an unconfigured backend shows no dead tab.
|
|
81
90
|
packageRegistries: packageRegistries.available === true,
|
|
91
|
+
// Two gates, and neither implies the other. `canManageSecrets` hides the tab from a member
|
|
92
|
+
// who may not manage secrets (the view NAMES the deployment's credential keys, which is why
|
|
93
|
+
// the backend gates the read too), and `hasSurface` hides a tab with nothing in it — the
|
|
94
|
+
// panel is a checklist projected from the deployment's registered capabilities, so a build
|
|
95
|
+
// that registers none has no credential to type.
|
|
96
|
+
capabilityCredentials: canManageSecrets.value && capabilityCredentials.hasSurface,
|
|
82
97
|
}).map((value) => ({
|
|
83
98
|
value,
|
|
84
99
|
label: TAB_LABELS.value[value],
|
|
@@ -102,6 +117,11 @@ watch(
|
|
|
102
117
|
// Swallowed here on purpose: the PANEL reports a load failure, and it can only do that
|
|
103
118
|
// once the tab it lives in exists, so a probe failure has to leave the window itself alone.
|
|
104
119
|
void packageRegistries.ensureLoaded().catch(() => {})
|
|
120
|
+
// Same split as the registries probe: swallowed here (a failed probe means no tab, and the
|
|
121
|
+
// window must still open), reported by the panel, which can only do that once its tab exists.
|
|
122
|
+
// Not probed at all without the permission — the backend would refuse it, and asking would
|
|
123
|
+
// put a 403 in every member's console on every open.
|
|
124
|
+
if (canManageSecrets.value) void capabilityCredentials.ensureLoaded().catch(() => {})
|
|
105
125
|
activeTab.value = openInfrastructureTab(tabValues.value, ui.infrastructureTab)
|
|
106
126
|
},
|
|
107
127
|
{ immediate: true },
|
|
@@ -176,6 +196,9 @@ watch([tabs, () => store.loaded], () => {
|
|
|
176
196
|
<template #package-registries>
|
|
177
197
|
<PackageRegistriesPanel />
|
|
178
198
|
</template>
|
|
199
|
+
<template #capability-credentials>
|
|
200
|
+
<CapabilityCredentialsPanel />
|
|
201
|
+
</template>
|
|
179
202
|
</UTabs>
|
|
180
203
|
|
|
181
204
|
<p v-else class="px-1 py-6 text-center text-sm text-slate-500">
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deleteCapabilityCredentialContract,
|
|
3
|
+
getCapabilityCredentialsContract,
|
|
4
|
+
setCapabilityCredentialContract,
|
|
5
|
+
} from '@cat-factory/contracts'
|
|
6
|
+
import type { ApiContext } from './context'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Per-workspace capability credentials (SEALED, write-only). The GET view returns what this
|
|
10
|
+
* deployment's registered capabilities DECLARE joined to what this workspace has stored, never a
|
|
11
|
+
* value. Writes are PER KEY: the whole-set PUT exists for an API caller declaring a whole set at
|
|
12
|
+
* once, and this client could not use it — it never received the other values, so a set-replacing
|
|
13
|
+
* write here would delete every credential the operator did not retype.
|
|
14
|
+
*
|
|
15
|
+
* `secrets.manage`-gated end to end, the READ included: the view carries the credential key names
|
|
16
|
+
* the deployment's capabilities want, which the workspace snapshot deliberately omits.
|
|
17
|
+
* See CapabilityCredentialsController.
|
|
18
|
+
*/
|
|
19
|
+
export function capabilityCredentialsApi({ send, ws }: ApiContext) {
|
|
20
|
+
return {
|
|
21
|
+
getCapabilityCredentials: (workspaceId: string) =>
|
|
22
|
+
send(getCapabilityCredentialsContract, { pathPrefix: ws(workspaceId) }),
|
|
23
|
+
|
|
24
|
+
setCapabilityCredential: (workspaceId: string, key: string, value: string) =>
|
|
25
|
+
send(setCapabilityCredentialContract, {
|
|
26
|
+
pathPrefix: ws(workspaceId),
|
|
27
|
+
pathParams: { key },
|
|
28
|
+
body: { value },
|
|
29
|
+
}),
|
|
30
|
+
|
|
31
|
+
deleteCapabilityCredential: (workspaceId: string, key: string) =>
|
|
32
|
+
send(deleteCapabilityCredentialContract, {
|
|
33
|
+
pathPrefix: ws(workspaceId),
|
|
34
|
+
pathParams: { key },
|
|
35
|
+
}),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -31,6 +31,7 @@ import { localSettingsApi } from './api/localSettings'
|
|
|
31
31
|
import { modelsApi } from './api/models'
|
|
32
32
|
import { notificationsApi } from './api/notifications'
|
|
33
33
|
import { packageRegistriesApi } from './api/packageRegistries'
|
|
34
|
+
import { capabilityCredentialsApi } from './api/capabilityCredentials'
|
|
34
35
|
import { preflightsApi } from './api/preflights'
|
|
35
36
|
import { presetsApi } from './api/presets'
|
|
36
37
|
import { publicApiKeysApi } from './api/publicApiKeys'
|
|
@@ -151,6 +152,7 @@ export function useApi() {
|
|
|
151
152
|
...validationChecksApi(ctx),
|
|
152
153
|
...testSecretsApi(ctx),
|
|
153
154
|
...packageRegistriesApi(ctx),
|
|
155
|
+
...capabilityCredentialsApi(ctx),
|
|
154
156
|
...previewApi(ctx),
|
|
155
157
|
...environmentsApi(ctx),
|
|
156
158
|
...recurringApi(ctx),
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { useCapabilityCredentialsStore } from '~/stores/capabilityCredentials'
|
|
3
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
4
|
+
import type { CapabilityCredentialsView } from '~/types/capabilityCredentials'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Two behaviours carry this store, and both are about a list that is empty for more than one
|
|
8
|
+
* reason:
|
|
9
|
+
*
|
|
10
|
+
* - the probe. A 503 ("no encryption key on this deployment") and a 403 ("you may not manage
|
|
11
|
+
* secrets") are ANSWERS and resolve normally, hiding the tab; anything else propagates,
|
|
12
|
+
* because the panel is the surface that can tell a reader the list could not be fetched.
|
|
13
|
+
* - `hasSurface`. An empty checklist with a COMPLETE declaration read means this deployment
|
|
14
|
+
* registers no capability that wants a credential, so there is nothing to type; the same
|
|
15
|
+
* empty checklist with an INCOMPLETE read is an outage the panel has to state.
|
|
16
|
+
*/
|
|
17
|
+
function view(over: Partial<CapabilityCredentialsView> = {}): CapabilityCredentialsView {
|
|
18
|
+
return {
|
|
19
|
+
declared: [],
|
|
20
|
+
orphaned: [],
|
|
21
|
+
environmentFallback: true,
|
|
22
|
+
declarationsIncomplete: false,
|
|
23
|
+
...over,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function declared(key: string, stored = false) {
|
|
28
|
+
return {
|
|
29
|
+
key,
|
|
30
|
+
declaredBy: [{ subject: 'tool-server' as const, id: 'srv', label: 'Search' }],
|
|
31
|
+
required: true,
|
|
32
|
+
stored,
|
|
33
|
+
...(stored ? { updatedAt: 1000 } : {}),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('capabilityCredentials store', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
useWorkspaceStore().workspaceId = 'ws1'
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('load stores the view and marks the surface available', async () => {
|
|
43
|
+
vi.stubGlobal('useApi', () => ({
|
|
44
|
+
getCapabilityCredentials: () => Promise.resolve(view({ declared: [declared('SEARCH_KEY')] })),
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
const store = useCapabilityCredentialsStore()
|
|
48
|
+
await store.load()
|
|
49
|
+
|
|
50
|
+
expect(store.available).toBe(true)
|
|
51
|
+
expect(store.hasSurface).toBe(true)
|
|
52
|
+
expect(store.loading).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it.each([503, 403])(
|
|
56
|
+
'a definitive %i latches the surface unavailable without throwing',
|
|
57
|
+
async (statusCode) => {
|
|
58
|
+
// 503: the deployment has no encryption key. 403: this caller may not manage secrets, and
|
|
59
|
+
// the READ is gated too, because the view names the credential keys the deployment wants.
|
|
60
|
+
// Both hide the tab rather than disabling it.
|
|
61
|
+
vi.stubGlobal('useApi', () => ({
|
|
62
|
+
getCapabilityCredentials: () => Promise.reject({ statusCode }),
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
const store = useCapabilityCredentialsStore()
|
|
66
|
+
await expect(store.load()).resolves.toBeUndefined()
|
|
67
|
+
|
|
68
|
+
expect(store.available).toBe(false)
|
|
69
|
+
expect(store.view).toBeNull()
|
|
70
|
+
expect(store.hasSurface).toBe(false)
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
it('a transient failure propagates and leaves `available` null so the probe stays retryable', async () => {
|
|
75
|
+
vi.stubGlobal('useApi', () => ({
|
|
76
|
+
getCapabilityCredentials: () => Promise.reject({ statusCode: 500 }),
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
const store = useCapabilityCredentialsStore()
|
|
80
|
+
await expect(store.load()).rejects.toMatchObject({ statusCode: 500 })
|
|
81
|
+
|
|
82
|
+
expect(store.available).toBeNull()
|
|
83
|
+
expect(store.loading).toBe(false)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('offers no surface when nothing is declared and nothing is stored', async () => {
|
|
87
|
+
vi.stubGlobal('useApi', () => ({ getCapabilityCredentials: () => Promise.resolve(view()) }))
|
|
88
|
+
|
|
89
|
+
const store = useCapabilityCredentialsStore()
|
|
90
|
+
await store.load()
|
|
91
|
+
|
|
92
|
+
// The panel is a checklist projected from the deployment's registered capabilities. With
|
|
93
|
+
// none, there is no credential to type and the tab would be a dead end.
|
|
94
|
+
expect(store.available).toBe(true)
|
|
95
|
+
expect(store.hasSurface).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('keeps the surface when the declaration read failed, even with both lists empty', async () => {
|
|
99
|
+
vi.stubGlobal('useApi', () => ({
|
|
100
|
+
getCapabilityCredentials: () => Promise.resolve(view({ declarationsIncomplete: true })),
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
const store = useCapabilityCredentialsStore()
|
|
104
|
+
await store.load()
|
|
105
|
+
|
|
106
|
+
// An unreadable list and an empty one are the same list and opposite facts. Hiding the tab
|
|
107
|
+
// here would render someone else's outage as "this deployment needs no credentials".
|
|
108
|
+
expect(store.hasSurface).toBe(true)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('keeps the surface for an orphan nothing declares any more', async () => {
|
|
112
|
+
vi.stubGlobal('useApi', () => ({
|
|
113
|
+
getCapabilityCredentials: () =>
|
|
114
|
+
Promise.resolve(view({ orphaned: [{ key: 'OLD_KEY', updatedAt: 1000 }] })),
|
|
115
|
+
}))
|
|
116
|
+
|
|
117
|
+
const store = useCapabilityCredentialsStore()
|
|
118
|
+
await store.load()
|
|
119
|
+
|
|
120
|
+
// A live secret nobody will ever ask for. The tab is the only place it can be removed.
|
|
121
|
+
expect(store.hasSurface).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('saves ONE key and adopts the returned view', async () => {
|
|
125
|
+
const calls: { key: string; value: string }[] = []
|
|
126
|
+
vi.stubGlobal('useApi', () => ({
|
|
127
|
+
getCapabilityCredentials: () => Promise.resolve(view({ declared: [declared('SEARCH_KEY')] })),
|
|
128
|
+
setCapabilityCredential: (_ws: string, key: string, value: string) => {
|
|
129
|
+
calls.push({ key, value })
|
|
130
|
+
return Promise.resolve(view({ declared: [declared('SEARCH_KEY', true)] }))
|
|
131
|
+
},
|
|
132
|
+
}))
|
|
133
|
+
|
|
134
|
+
const store = useCapabilityCredentialsStore()
|
|
135
|
+
await store.load()
|
|
136
|
+
await store.save('SEARCH_KEY', 'sk-live')
|
|
137
|
+
|
|
138
|
+
// Per KEY, never a set-replacing write: this client never received the other values, so a
|
|
139
|
+
// whole-set save would delete every credential the operator did not retype.
|
|
140
|
+
expect(calls).toEqual([{ key: 'SEARCH_KEY', value: 'sk-live' }])
|
|
141
|
+
expect(store.view?.declared[0]?.stored).toBe(true)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('re-reads after a delete rather than patching the row out locally', async () => {
|
|
145
|
+
let stored = true
|
|
146
|
+
vi.stubGlobal('useApi', () => ({
|
|
147
|
+
getCapabilityCredentials: () =>
|
|
148
|
+
Promise.resolve(view({ declared: [declared('SEARCH_KEY', stored)] })),
|
|
149
|
+
deleteCapabilityCredential: () => {
|
|
150
|
+
stored = false
|
|
151
|
+
return Promise.resolve(undefined)
|
|
152
|
+
},
|
|
153
|
+
}))
|
|
154
|
+
|
|
155
|
+
const store = useCapabilityCredentialsStore()
|
|
156
|
+
await store.load()
|
|
157
|
+
await store.remove('SEARCH_KEY')
|
|
158
|
+
|
|
159
|
+
// The DELETE answers 204, and the declared half is deployment state this client does not
|
|
160
|
+
// own: a locally-patched row would drift from it the moment the deployment changed.
|
|
161
|
+
expect(store.view?.declared[0]?.stored).toBe(false)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { CapabilityCredentialsView } from '~/types/capabilityCredentials'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
import { apiErrorStatus } from '~/composables/api/errors'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The workspace's capability credentials: the sealed, tenant-scoped values behind the secrets a
|
|
9
|
+
* registered tool server (MCP) or generative binary integration declares BY NAME. Values are
|
|
10
|
+
* write-only — the store only ever holds the view, which pairs the deployment's DECLARATIONS with
|
|
11
|
+
* which of them this workspace has stored. Loaded on demand (the Infrastructure window's
|
|
12
|
+
* "Capability credentials" tab, whose existence gates on the probe below), not from the snapshot.
|
|
13
|
+
*
|
|
14
|
+
* Writes are PER KEY. A whole-set write exists on the API for a caller declaring a set at once,
|
|
15
|
+
* and this store cannot use it: it never receives the values, so replacing the set would delete
|
|
16
|
+
* every credential the operator did not retype in this sitting.
|
|
17
|
+
*/
|
|
18
|
+
export const useCapabilityCredentialsStore = defineStore('capabilityCredentials', () => {
|
|
19
|
+
const api = useApi()
|
|
20
|
+
|
|
21
|
+
const view = ref<CapabilityCredentialsView | null>(null)
|
|
22
|
+
const loading = ref(false)
|
|
23
|
+
// Mirrors the backend's two definitive refusals: the module 503s with no encryption key, and
|
|
24
|
+
// the whole surface (the READ included) is `secrets.manage`-gated, so a member without it gets
|
|
25
|
+
// a 403. `null` until first probed, then `true`/`false`. Both answers hide the tab rather than
|
|
26
|
+
// disabling it — a member who cannot manage secrets has no business learning which environment
|
|
27
|
+
// variables the deployment's capabilities want, which is the very content of this view.
|
|
28
|
+
const available = ref<boolean | null>(null)
|
|
29
|
+
let inFlight: Promise<void> | null = null
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether there is anything to show. The panel is a CHECKLIST projected from the deployment's
|
|
33
|
+
* registered capabilities, so with nothing declared, nothing orphaned and a complete read there
|
|
34
|
+
* is no credential to type and no tab worth rendering.
|
|
35
|
+
*
|
|
36
|
+
* `declarationsIncomplete` keeps the surface even when both lists are empty, because then the
|
|
37
|
+
* emptiness is an OUTAGE (`BinaryGeneratorSource` throws rather than answering an empty set)
|
|
38
|
+
* rather than an answer, and hiding the tab would render the outage as "this deployment needs
|
|
39
|
+
* no credentials".
|
|
40
|
+
*/
|
|
41
|
+
const hasSurface = computed(
|
|
42
|
+
() =>
|
|
43
|
+
view.value !== null &&
|
|
44
|
+
(view.value.declared.length > 0 ||
|
|
45
|
+
view.value.orphaned.length > 0 ||
|
|
46
|
+
view.value.declarationsIncomplete),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
/** Force a refresh of the view (used after a save/remove). */
|
|
50
|
+
async function load() {
|
|
51
|
+
const ws = useWorkspaceStore()
|
|
52
|
+
loading.value = true
|
|
53
|
+
try {
|
|
54
|
+
view.value = await api.getCapabilityCredentials(ws.requireId())
|
|
55
|
+
available.value = true
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const status = apiErrorStatus(err)
|
|
58
|
+
if (status === 503 || status === 403) {
|
|
59
|
+
// Definitive answers, not failures: the module is unconfigured, or this caller may not
|
|
60
|
+
// manage secrets. Hide the entry point and stop probing; resolve normally.
|
|
61
|
+
available.value = false
|
|
62
|
+
view.value = null
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
// Any other failure (transient 5xx / network) leaves the state untouched: it must not hide
|
|
66
|
+
// an already-available panel nor cache a false "unavailable", and `available` stays `null`
|
|
67
|
+
// when never probed so `ensureLoaded` remains retryable. The error PROPAGATES, because the
|
|
68
|
+
// panel is the one surface that can tell a reader it is looking at a list we could not
|
|
69
|
+
// fetch — the PROBE caller swallows instead (a failed probe means no tab, not a broken
|
|
70
|
+
// window). Same split as the package-registries store.
|
|
71
|
+
throw err
|
|
72
|
+
} finally {
|
|
73
|
+
loading.value = false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Load once and share the result (coalescing concurrent callers); `load()` refreshes. */
|
|
78
|
+
async function ensureLoaded() {
|
|
79
|
+
if (available.value !== null) return
|
|
80
|
+
if (!inFlight) inFlight = load().finally(() => (inFlight = null))
|
|
81
|
+
return inFlight
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Store ONE credential's value, leaving every other stored key as it is. */
|
|
85
|
+
async function save(key: string, value: string) {
|
|
86
|
+
const ws = useWorkspaceStore()
|
|
87
|
+
view.value = await api.setCapabilityCredential(ws.requireId(), key, value)
|
|
88
|
+
available.value = true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Remove ONE stored credential (a rotated key, or an orphan nothing declares any more). */
|
|
92
|
+
async function remove(key: string) {
|
|
93
|
+
const ws = useWorkspaceStore()
|
|
94
|
+
await api.deleteCapabilityCredential(ws.requireId(), key)
|
|
95
|
+
// The DELETE answers 204, so the view is re-read rather than patched locally: the declared
|
|
96
|
+
// half is deployment state this client does not own, and a locally-patched row would drift
|
|
97
|
+
// from it the moment the deployment changed.
|
|
98
|
+
await load()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { view, loading, available, hasSurface, load, ensureLoaded, save, remove }
|
|
102
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Per-workspace capability-credential shapes: the tenant-scoped home for the secrets a
|
|
2
|
+
// registered tool server (MCP) or generative binary integration declares BY NAME. Values are
|
|
3
|
+
// write-only — the view carries the keys the deployment DECLARES, which of them this workspace
|
|
4
|
+
// has stored, and which stored keys nothing declares any more.
|
|
5
|
+
//
|
|
6
|
+
// All wire shapes are sourced from @cat-factory/contracts (single source of truth).
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
CapabilityCredentialRef,
|
|
10
|
+
CapabilityCredentialStatus,
|
|
11
|
+
CapabilityCredentialsView,
|
|
12
|
+
} from '@cat-factory/contracts'
|
|
@@ -27,7 +27,11 @@ export type ProviderConnectionKind = 'environment' | 'runner-pool'
|
|
|
27
27
|
* pointer could open the window but never land the user on the tab it meant. Every tab
|
|
28
28
|
* `InfrastructureWindow.vue` can render must have a name here.
|
|
29
29
|
*/
|
|
30
|
-
export type InfrastructureTab =
|
|
30
|
+
export type InfrastructureTab =
|
|
31
|
+
| ProviderConnectionKind
|
|
32
|
+
| 'shared-stacks'
|
|
33
|
+
| 'package-registries'
|
|
34
|
+
| 'capability-credentials'
|
|
31
35
|
|
|
32
36
|
/** A workspace's provider binding, as exposed to clients (never secret values). */
|
|
33
37
|
export interface ProviderConnection {
|
package/i18n/locales/de.json
CHANGED
|
@@ -568,6 +568,40 @@
|
|
|
568
568
|
"removeFailed": "Der Registry-Eintrag konnte nicht entfernt werden"
|
|
569
569
|
}
|
|
570
570
|
},
|
|
571
|
+
"capabilityCredentials": {
|
|
572
|
+
"tab": "Zugangsdaten für Fähigkeiten",
|
|
573
|
+
"intro": "Die Secrets, die die Tool-Server und generativen Integrationen dieser Installation namentlich anfordern. Werte gelten nur für dieses Board, werden verschlüsselt gespeichert und direkt an den Prozess des Agenten übergeben: Sie erscheinen weder in einem Prompt noch in einem Log. Werte lassen sich nur schreiben, nie auslesen, ein gespeicherter Wert wird also durch Eingabe eines neuen ersetzt.",
|
|
574
|
+
"credentialNoun": "Zugangsdaten {key}",
|
|
575
|
+
"required": "Erforderlich",
|
|
576
|
+
"optional": "Optional",
|
|
577
|
+
"stored": "Gespeichert",
|
|
578
|
+
"storedAt": "Zuletzt gesetzt am {date}",
|
|
579
|
+
"notStoredWithFallback": "Für dieses Board ist nichts gespeichert. Diese Installation liest den Schlüssel auch aus ihrer eigenen Umgebung, die Fähigkeit funktioniert also möglicherweise trotzdem.",
|
|
580
|
+
"notStored": "Für dieses Board ist nichts gespeichert, und diese Installation hat keinen Rückfallwert aus der Umgebung. Die Fähigkeit kann sich daher nicht authentifizieren.",
|
|
581
|
+
"setValue": "Wert",
|
|
582
|
+
"replaceValue": "Gespeicherten Wert ersetzen",
|
|
583
|
+
"save": "Speichern",
|
|
584
|
+
"remove": "Zugangsdaten entfernen",
|
|
585
|
+
"noneDeclared": "Keine der registrierten Fähigkeiten dieser Installation fordert Zugangsdaten an.",
|
|
586
|
+
"subject": {
|
|
587
|
+
"toolServer": "Tool-Server",
|
|
588
|
+
"binaryGenerator": "Generative Integration"
|
|
589
|
+
},
|
|
590
|
+
"incomplete": {
|
|
591
|
+
"title": "Diese Liste ist möglicherweise unvollständig",
|
|
592
|
+
"body": "Die generativen Integrationen konnten nicht gelesen werden, daher fehlen unten womöglich Zugangsdaten, die eine von ihnen anfordert. Gespeicherte Schlüssel, die niemand anfordert, bleiben ausgeblendet, bis die Liste wieder gelesen werden kann."
|
|
593
|
+
},
|
|
594
|
+
"orphaned": {
|
|
595
|
+
"heading": "Gespeichert, aber nicht angefordert",
|
|
596
|
+
"body": "Nichts, was diese Installation registriert hat, fordert diese Schlüssel an. Genau das hinterlässt eine abgeschaltete Integration oder eine umbenannte Variable. Sie bleiben verschlüsselt gespeichert, bis du sie entfernst."
|
|
597
|
+
},
|
|
598
|
+
"toast": {
|
|
599
|
+
"loadFailed": "Zugangsdaten für Fähigkeiten konnten nicht geladen werden",
|
|
600
|
+
"saved": "{key} gespeichert",
|
|
601
|
+
"saveFailed": "Die Zugangsdaten konnten nicht gespeichert werden",
|
|
602
|
+
"removeFailed": "Die Zugangsdaten konnten nicht entfernt werden"
|
|
603
|
+
}
|
|
604
|
+
},
|
|
571
605
|
"apiTokens": {
|
|
572
606
|
"title": "API-Zugriffstokens",
|
|
573
607
|
"intro": "Erstelle Tokens, die externe Systeme der cat-factory-API vorlegen. Jedes Token authentifiziert sich als dieser Arbeitsbereich an den /api/v1-Endpunkten. Das Geheimnis wird nur einmal bei der Erstellung angezeigt und kann nicht wiederhergestellt werden. Speichere es daher sofort.",
|
package/i18n/locales/en.json
CHANGED
|
@@ -2941,6 +2941,40 @@
|
|
|
2941
2941
|
"removeFailed": "Could not remove the registry entry"
|
|
2942
2942
|
}
|
|
2943
2943
|
},
|
|
2944
|
+
"capabilityCredentials": {
|
|
2945
|
+
"tab": "Capability credentials",
|
|
2946
|
+
"intro": "The secrets this deployment's tool servers and generative integrations ask for by name. Values are stored for this board only, sealed at rest, and handed straight to the agent's process: they never reach a prompt or a log. Values are write-only, so a stored one is replaced by typing a new one and never read back.",
|
|
2947
|
+
"credentialNoun": "{key} credential",
|
|
2948
|
+
"required": "Required",
|
|
2949
|
+
"optional": "Optional",
|
|
2950
|
+
"stored": "Stored",
|
|
2951
|
+
"storedAt": "Last set {date}",
|
|
2952
|
+
"notStoredWithFallback": "Nothing stored for this board. This deployment also reads the key from its own environment, so the capability may still be working.",
|
|
2953
|
+
"notStored": "Nothing stored for this board, and this deployment has no environment fallback, so the capability cannot authenticate.",
|
|
2954
|
+
"setValue": "Value",
|
|
2955
|
+
"replaceValue": "Replace the stored value",
|
|
2956
|
+
"save": "Save",
|
|
2957
|
+
"remove": "Remove credential",
|
|
2958
|
+
"noneDeclared": "None of this deployment's registered capabilities asks for a credential.",
|
|
2959
|
+
"subject": {
|
|
2960
|
+
"toolServer": "Tool server",
|
|
2961
|
+
"binaryGenerator": "Generative integration"
|
|
2962
|
+
},
|
|
2963
|
+
"incomplete": {
|
|
2964
|
+
"title": "This list may be incomplete",
|
|
2965
|
+
"body": "The generative integrations could not be read, so a credential one of them asks for may be missing below. Stored keys that nothing asks for stay hidden until the list can be read again."
|
|
2966
|
+
},
|
|
2967
|
+
"orphaned": {
|
|
2968
|
+
"heading": "Stored but not asked for",
|
|
2969
|
+
"body": "Nothing this deployment has registered asks for these keys, which is what a retired integration or a renamed variable leaves behind. They stay sealed until you remove them."
|
|
2970
|
+
},
|
|
2971
|
+
"toast": {
|
|
2972
|
+
"loadFailed": "Could not load capability credentials",
|
|
2973
|
+
"saved": "Saved {key}",
|
|
2974
|
+
"saveFailed": "Could not save the credential",
|
|
2975
|
+
"removeFailed": "Could not remove the credential"
|
|
2976
|
+
}
|
|
2977
|
+
},
|
|
2944
2978
|
"apiTokens": {
|
|
2945
2979
|
"title": "API access tokens",
|
|
2946
2980
|
"intro": "Create tokens that external systems present to the cat-factory API. Each token authenticates as this workspace on the /api/v1 endpoints. The secret is shown only once, when you create it, and cannot be recovered, so store it right away.",
|
package/i18n/locales/es.json
CHANGED
|
@@ -2709,6 +2709,40 @@
|
|
|
2709
2709
|
"removeFailed": "No se pudo eliminar la entrada del registro"
|
|
2710
2710
|
}
|
|
2711
2711
|
},
|
|
2712
|
+
"capabilityCredentials": {
|
|
2713
|
+
"tab": "Credenciales de capacidades",
|
|
2714
|
+
"intro": "Los secretos que los servidores de herramientas y las integraciones generativas de esta instalación piden por nombre. Los valores se guardan solo para este tablero, cifrados en reposo, y se entregan directamente al proceso del agente: nunca llegan a un prompt ni a un registro. Los valores son de solo escritura, así que uno guardado se sustituye escribiendo otro y nunca se vuelve a leer.",
|
|
2715
|
+
"credentialNoun": "credencial {key}",
|
|
2716
|
+
"required": "Obligatoria",
|
|
2717
|
+
"optional": "Opcional",
|
|
2718
|
+
"stored": "Guardada",
|
|
2719
|
+
"storedAt": "Definida por última vez el {date}",
|
|
2720
|
+
"notStoredWithFallback": "No hay nada guardado para este tablero. Esta instalación también lee la clave de su propio entorno, así que la capacidad puede seguir funcionando.",
|
|
2721
|
+
"notStored": "No hay nada guardado para este tablero y esta instalación no tiene respaldo en el entorno, así que la capacidad no puede autenticarse.",
|
|
2722
|
+
"setValue": "Valor",
|
|
2723
|
+
"replaceValue": "Sustituir el valor guardado",
|
|
2724
|
+
"save": "Guardar",
|
|
2725
|
+
"remove": "Quitar credencial",
|
|
2726
|
+
"noneDeclared": "Ninguna de las capacidades registradas de esta instalación pide credenciales.",
|
|
2727
|
+
"subject": {
|
|
2728
|
+
"toolServer": "Servidor de herramientas",
|
|
2729
|
+
"binaryGenerator": "Integración generativa"
|
|
2730
|
+
},
|
|
2731
|
+
"incomplete": {
|
|
2732
|
+
"title": "Puede que esta lista esté incompleta",
|
|
2733
|
+
"body": "No se pudieron leer las integraciones generativas, así que abajo puede faltar alguna credencial que una de ellas pida. Las claves guardadas que nadie pide quedan ocultas hasta que la lista se pueda leer de nuevo."
|
|
2734
|
+
},
|
|
2735
|
+
"orphaned": {
|
|
2736
|
+
"heading": "Guardadas pero no solicitadas",
|
|
2737
|
+
"body": "Nada de lo que esta instalación tiene registrado pide estas claves, que es lo que deja una integración retirada o una variable renombrada. Seguirán cifradas hasta que las quites."
|
|
2738
|
+
},
|
|
2739
|
+
"toast": {
|
|
2740
|
+
"loadFailed": "No se pudieron cargar las credenciales de capacidades",
|
|
2741
|
+
"saved": "{key} guardada",
|
|
2742
|
+
"saveFailed": "No se pudo guardar la credencial",
|
|
2743
|
+
"removeFailed": "No se pudo quitar la credencial"
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2712
2746
|
"apiTokens": {
|
|
2713
2747
|
"title": "Tokens de acceso a la API",
|
|
2714
2748
|
"intro": "Crea tokens que los sistemas externos presentan a la API de cat-factory. Cada token se autentica como este espacio de trabajo en los endpoints /api/v1. El secreto se muestra una sola vez, al crearlo, y no se puede recuperar, así que guárdalo de inmediato.",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -2709,6 +2709,40 @@
|
|
|
2709
2709
|
"removeFailed": "Impossible de supprimer l'entrée du registre"
|
|
2710
2710
|
}
|
|
2711
2711
|
},
|
|
2712
|
+
"capabilityCredentials": {
|
|
2713
|
+
"tab": "Identifiants des capacités",
|
|
2714
|
+
"intro": "Les secrets que les serveurs d'outils et les intégrations génératives de ce déploiement réclament par leur nom. Les valeurs ne sont enregistrées que pour ce tableau, chiffrées au repos, et remises directement au processus de l'agent : elles n'apparaissent ni dans un prompt ni dans un journal. Les valeurs sont en écriture seule, une valeur enregistrée se remplace donc en en saisissant une nouvelle et n'est jamais relue.",
|
|
2715
|
+
"credentialNoun": "l'identifiant {key}",
|
|
2716
|
+
"required": "Obligatoire",
|
|
2717
|
+
"optional": "Facultatif",
|
|
2718
|
+
"stored": "Enregistré",
|
|
2719
|
+
"storedAt": "Défini le {date}",
|
|
2720
|
+
"notStoredWithFallback": "Rien n'est enregistré pour ce tableau. Ce déploiement lit aussi la clé dans son propre environnement, la capacité fonctionne donc peut-être encore.",
|
|
2721
|
+
"notStored": "Rien n'est enregistré pour ce tableau et ce déploiement n'a pas de valeur de repli dans l'environnement : la capacité ne peut pas s'authentifier.",
|
|
2722
|
+
"setValue": "Valeur",
|
|
2723
|
+
"replaceValue": "Remplacer la valeur enregistrée",
|
|
2724
|
+
"save": "Enregistrer",
|
|
2725
|
+
"remove": "Supprimer l'identifiant",
|
|
2726
|
+
"noneDeclared": "Aucune des capacités enregistrées de ce déploiement ne réclame d'identifiant.",
|
|
2727
|
+
"subject": {
|
|
2728
|
+
"toolServer": "Serveur d'outils",
|
|
2729
|
+
"binaryGenerator": "Intégration générative"
|
|
2730
|
+
},
|
|
2731
|
+
"incomplete": {
|
|
2732
|
+
"title": "Cette liste est peut-être incomplète",
|
|
2733
|
+
"body": "Les intégrations génératives n'ont pas pu être lues : un identifiant réclamé par l'une d'elles manque peut-être ci-dessous. Les clés enregistrées que personne ne réclame restent masquées tant que la liste ne peut pas être relue."
|
|
2734
|
+
},
|
|
2735
|
+
"orphaned": {
|
|
2736
|
+
"heading": "Enregistrées mais non réclamées",
|
|
2737
|
+
"body": "Rien de ce que ce déploiement a enregistré ne réclame ces clés, ce que laisse derrière elle une intégration retirée ou une variable renommée. Elles restent chiffrées jusqu'à ce que vous les supprimiez."
|
|
2738
|
+
},
|
|
2739
|
+
"toast": {
|
|
2740
|
+
"loadFailed": "Impossible de charger les identifiants des capacités",
|
|
2741
|
+
"saved": "{key} enregistré",
|
|
2742
|
+
"saveFailed": "Impossible d'enregistrer l'identifiant",
|
|
2743
|
+
"removeFailed": "Impossible de supprimer l'identifiant"
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2712
2746
|
"apiTokens": {
|
|
2713
2747
|
"title": "Jetons d'accès à l'API",
|
|
2714
2748
|
"intro": "Créez des jetons que les systèmes externes présentent à l'API cat-factory. Chaque jeton s'authentifie en tant que cet espace de travail sur les points de terminaison /api/v1. Le secret n'est affiché qu'une seule fois, à sa création, et ne peut pas être récupéré, alors conservez-le immédiatement.",
|
package/i18n/locales/he.json
CHANGED
|
@@ -2849,6 +2849,40 @@
|
|
|
2849
2849
|
"removeFailed": "הסרת רשומת המאגר נכשלה"
|
|
2850
2850
|
}
|
|
2851
2851
|
},
|
|
2852
|
+
"capabilityCredentials": {
|
|
2853
|
+
"tab": "אישורי גישה ליכולות",
|
|
2854
|
+
"intro": "הסודות ששרתי הכלים והאינטגרציות הגנרטיביות של הפריסה הזו מבקשים לפי שם. הערכים נשמרים ללוח הזה בלבד, מוצפנים במנוחה ומועברים ישירות לתהליך של הסוכן: הם לעולם לא מגיעים להנחיה או ליומן. הערכים ניתנים לכתיבה בלבד, ולכן ערך שמור מוחלף בהקלדת ערך חדש ולעולם אינו נקרא בחזרה.",
|
|
2855
|
+
"credentialNoun": "אישור הגישה {key}",
|
|
2856
|
+
"required": "נדרש",
|
|
2857
|
+
"optional": "לא חובה",
|
|
2858
|
+
"stored": "נשמר",
|
|
2859
|
+
"storedAt": "הוגדר לאחרונה ב-{date}",
|
|
2860
|
+
"notStoredWithFallback": "לא נשמר דבר עבור הלוח הזה. הפריסה הזו קוראת את המפתח גם מהסביבה שלה, ולכן ייתכן שהיכולת עדיין פועלת.",
|
|
2861
|
+
"notStored": "לא נשמר דבר עבור הלוח הזה, ולפריסה הזו אין נפילה לסביבה, ולכן היכולת אינה יכולה לבצע אימות.",
|
|
2862
|
+
"setValue": "ערך",
|
|
2863
|
+
"replaceValue": "החלפת הערך השמור",
|
|
2864
|
+
"save": "שמירה",
|
|
2865
|
+
"remove": "הסרת אישור הגישה",
|
|
2866
|
+
"noneDeclared": "אף אחת מהיכולות הרשומות בפריסה הזו אינה מבקשת אישור גישה.",
|
|
2867
|
+
"subject": {
|
|
2868
|
+
"toolServer": "שרת כלים",
|
|
2869
|
+
"binaryGenerator": "אינטגרציה גנרטיבית"
|
|
2870
|
+
},
|
|
2871
|
+
"incomplete": {
|
|
2872
|
+
"title": "ייתכן שהרשימה הזו חלקית",
|
|
2873
|
+
"body": "לא ניתן היה לקרוא את האינטגרציות הגנרטיביות, ולכן ייתכן שחסר למטה אישור גישה שאחת מהן מבקשת. מפתחות שמורים שאיש אינו מבקש נשארים מוסתרים עד שאפשר יהיה לקרוא את הרשימה שוב."
|
|
2874
|
+
},
|
|
2875
|
+
"orphaned": {
|
|
2876
|
+
"heading": "שמורים אך לא מבוקשים",
|
|
2877
|
+
"body": "שום דבר שנרשם בפריסה הזו אינו מבקש את המפתחות האלה, וזה בדיוק מה שמשאירה אחריה אינטגרציה שהוסרה או משתנה ששמו שונה. הם יישארו מוצפנים עד שתסירו אותם."
|
|
2878
|
+
},
|
|
2879
|
+
"toast": {
|
|
2880
|
+
"loadFailed": "לא ניתן היה לטעון את אישורי הגישה ליכולות",
|
|
2881
|
+
"saved": "{key} נשמר",
|
|
2882
|
+
"saveFailed": "לא ניתן היה לשמור את אישור הגישה",
|
|
2883
|
+
"removeFailed": "לא ניתן היה להסיר את אישור הגישה"
|
|
2884
|
+
}
|
|
2885
|
+
},
|
|
2852
2886
|
"apiTokens": {
|
|
2853
2887
|
"title": "אסימוני גישה ל-API",
|
|
2854
2888
|
"intro": "צור אסימונים שמערכות חיצוניות מציגות ל-API של cat-factory. כל אסימון מאמת את עצמו כמרחב העבודה הזה בנקודות הקצה /api/v1. הסוד מוצג פעם אחת בלבד, בעת היצירה, ולא ניתן לשחזרו, לכן שמור אותו מיד.",
|
package/i18n/locales/it.json
CHANGED
|
@@ -568,6 +568,40 @@
|
|
|
568
568
|
"removeFailed": "Impossibile rimuovere la voce del registry"
|
|
569
569
|
}
|
|
570
570
|
},
|
|
571
|
+
"capabilityCredentials": {
|
|
572
|
+
"tab": "Credenziali delle capacità",
|
|
573
|
+
"intro": "I segreti che i server di strumenti e le integrazioni generative di questo deployment richiedono per nome. I valori vengono salvati solo per questa board, cifrati a riposo, e consegnati direttamente al processo dell'agente: non finiscono mai in un prompt né in un log. I valori sono di sola scrittura, quindi uno salvato si sostituisce digitandone uno nuovo e non viene mai riletto.",
|
|
574
|
+
"credentialNoun": "credenziale {key}",
|
|
575
|
+
"required": "Obbligatoria",
|
|
576
|
+
"optional": "Facoltativa",
|
|
577
|
+
"stored": "Salvata",
|
|
578
|
+
"storedAt": "Impostata l'ultima volta il {date}",
|
|
579
|
+
"notStoredWithFallback": "Per questa board non è salvato nulla. Questo deployment legge la chiave anche dal proprio ambiente, quindi la capacità potrebbe funzionare comunque.",
|
|
580
|
+
"notStored": "Per questa board non è salvato nulla e questo deployment non ha un ripiego sull'ambiente, quindi la capacità non può autenticarsi.",
|
|
581
|
+
"setValue": "Valore",
|
|
582
|
+
"replaceValue": "Sostituisci il valore salvato",
|
|
583
|
+
"save": "Salva",
|
|
584
|
+
"remove": "Rimuovi credenziale",
|
|
585
|
+
"noneDeclared": "Nessuna delle capacità registrate in questo deployment richiede credenziali.",
|
|
586
|
+
"subject": {
|
|
587
|
+
"toolServer": "Server di strumenti",
|
|
588
|
+
"binaryGenerator": "Integrazione generativa"
|
|
589
|
+
},
|
|
590
|
+
"incomplete": {
|
|
591
|
+
"title": "Questo elenco potrebbe essere incompleto",
|
|
592
|
+
"body": "Non è stato possibile leggere le integrazioni generative, quindi qui sotto potrebbe mancare una credenziale richiesta da una di esse. Le chiavi salvate che nessuno richiede restano nascoste finché l'elenco non può essere letto di nuovo."
|
|
593
|
+
},
|
|
594
|
+
"orphaned": {
|
|
595
|
+
"heading": "Salvate ma non richieste",
|
|
596
|
+
"body": "Nulla di ciò che questo deployment ha registrato richiede queste chiavi: è quello che lascia dietro di sé un'integrazione dismessa o una variabile rinominata. Restano cifrate finché non le rimuovi."
|
|
597
|
+
},
|
|
598
|
+
"toast": {
|
|
599
|
+
"loadFailed": "Impossibile caricare le credenziali delle capacità",
|
|
600
|
+
"saved": "{key} salvata",
|
|
601
|
+
"saveFailed": "Impossibile salvare la credenziale",
|
|
602
|
+
"removeFailed": "Impossibile rimuovere la credenziale"
|
|
603
|
+
}
|
|
604
|
+
},
|
|
571
605
|
"apiTokens": {
|
|
572
606
|
"title": "Token di accesso API",
|
|
573
607
|
"intro": "Crea token che i sistemi esterni presentano all'API di cat-factory. Ogni token si autentica come questo spazio di lavoro sugli endpoint /api/v1. Il segreto viene mostrato una sola volta, al momento della creazione, e non può essere recuperato, quindi conservalo subito.",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -2850,6 +2850,40 @@
|
|
|
2850
2850
|
"removeFailed": "レジストリエントリを削除できませんでした"
|
|
2851
2851
|
}
|
|
2852
2852
|
},
|
|
2853
|
+
"capabilityCredentials": {
|
|
2854
|
+
"tab": "機能の認証情報",
|
|
2855
|
+
"intro": "このデプロイのツールサーバーと生成系インテグレーションが名前で要求するシークレットです。値はこのボードにのみ保存され、保管時は暗号化され、エージェントのプロセスへ直接渡されます。プロンプトにもログにも現れません。値は書き込み専用なので、保存済みの値は新しい値を入力して置き換えるだけで、読み出すことはできません。",
|
|
2856
|
+
"credentialNoun": "認証情報 {key}",
|
|
2857
|
+
"required": "必須",
|
|
2858
|
+
"optional": "任意",
|
|
2859
|
+
"stored": "保存済み",
|
|
2860
|
+
"storedAt": "最終設定: {date}",
|
|
2861
|
+
"notStoredWithFallback": "このボードには何も保存されていません。このデプロイは自身の環境からもこのキーを読み取るため、機能は動作している可能性があります。",
|
|
2862
|
+
"notStored": "このボードには何も保存されておらず、このデプロイには環境からのフォールバックもないため、機能は認証できません。",
|
|
2863
|
+
"setValue": "値",
|
|
2864
|
+
"replaceValue": "保存済みの値を置き換える",
|
|
2865
|
+
"save": "保存",
|
|
2866
|
+
"remove": "認証情報を削除",
|
|
2867
|
+
"noneDeclared": "このデプロイに登録された機能で認証情報を要求するものはありません。",
|
|
2868
|
+
"subject": {
|
|
2869
|
+
"toolServer": "ツールサーバー",
|
|
2870
|
+
"binaryGenerator": "生成系インテグレーション"
|
|
2871
|
+
},
|
|
2872
|
+
"incomplete": {
|
|
2873
|
+
"title": "この一覧は不完全な可能性があります",
|
|
2874
|
+
"body": "生成系インテグレーションを読み取れなかったため、いずれかが要求する認証情報が下の一覧から欠けている可能性があります。どこからも要求されていない保存済みのキーは、一覧を再び読み取れるようになるまで表示されません。"
|
|
2875
|
+
},
|
|
2876
|
+
"orphaned": {
|
|
2877
|
+
"heading": "保存済みだが要求されていない",
|
|
2878
|
+
"body": "このデプロイに登録されたもののうち、これらのキーを要求するものはありません。廃止されたインテグレーションや名前が変わった変数が残していったものです。削除するまで暗号化されたまま残ります。"
|
|
2879
|
+
},
|
|
2880
|
+
"toast": {
|
|
2881
|
+
"loadFailed": "機能の認証情報を読み込めませんでした",
|
|
2882
|
+
"saved": "{key} を保存しました",
|
|
2883
|
+
"saveFailed": "認証情報を保存できませんでした",
|
|
2884
|
+
"removeFailed": "認証情報を削除できませんでした"
|
|
2885
|
+
}
|
|
2886
|
+
},
|
|
2853
2887
|
"apiTokens": {
|
|
2854
2888
|
"title": "APIアクセストークン",
|
|
2855
2889
|
"intro": "外部システムが cat-factory API に提示するトークンを作成します。各トークンは /api/v1 エンドポイントでこのワークスペースとして認証されます。シークレットは作成時に一度だけ表示され、復元できないため、すぐに保存してください。",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -2709,6 +2709,40 @@
|
|
|
2709
2709
|
"removeFailed": "Nie udało się usunąć wpisu rejestru"
|
|
2710
2710
|
}
|
|
2711
2711
|
},
|
|
2712
|
+
"capabilityCredentials": {
|
|
2713
|
+
"tab": "Poświadczenia funkcji",
|
|
2714
|
+
"intro": "Sekrety, o które serwery narzędzi i integracje generatywne tego wdrożenia proszą po nazwie. Wartości są zapisywane tylko dla tej tablicy, szyfrowane w spoczynku i przekazywane wprost do procesu agenta: nigdy nie trafiają do promptu ani do logu. Wartości można tylko zapisywać, więc zapisaną zastępuje się, wpisując nową, i nigdy nie jest odczytywana.",
|
|
2715
|
+
"credentialNoun": "poświadczenie {key}",
|
|
2716
|
+
"required": "Wymagane",
|
|
2717
|
+
"optional": "Opcjonalne",
|
|
2718
|
+
"stored": "Zapisane",
|
|
2719
|
+
"storedAt": "Ostatnio ustawione {date}",
|
|
2720
|
+
"notStoredWithFallback": "Dla tej tablicy nic nie jest zapisane. To wdrożenie odczytuje klucz również z własnego środowiska, więc funkcja może nadal działać.",
|
|
2721
|
+
"notStored": "Dla tej tablicy nic nie jest zapisane, a to wdrożenie nie ma awaryjnego odczytu ze środowiska, więc funkcja nie może się uwierzytelnić.",
|
|
2722
|
+
"setValue": "Wartość",
|
|
2723
|
+
"replaceValue": "Zastąp zapisaną wartość",
|
|
2724
|
+
"save": "Zapisz",
|
|
2725
|
+
"remove": "Usuń poświadczenie",
|
|
2726
|
+
"noneDeclared": "Żadna z zarejestrowanych funkcji tego wdrożenia nie prosi o poświadczenia.",
|
|
2727
|
+
"subject": {
|
|
2728
|
+
"toolServer": "Serwer narzędzi",
|
|
2729
|
+
"binaryGenerator": "Integracja generatywna"
|
|
2730
|
+
},
|
|
2731
|
+
"incomplete": {
|
|
2732
|
+
"title": "Ta lista może być niepełna",
|
|
2733
|
+
"body": "Nie udało się odczytać integracji generatywnych, więc poniżej może brakować poświadczenia, o które prosi jedna z nich. Zapisane klucze, o które nikt nie prosi, pozostają ukryte, dopóki listy nie da się odczytać ponownie."
|
|
2734
|
+
},
|
|
2735
|
+
"orphaned": {
|
|
2736
|
+
"heading": "Zapisane, ale nieużywane",
|
|
2737
|
+
"body": "Nic, co zarejestrowało to wdrożenie, nie prosi o te klucze. Właśnie to zostawia po sobie wycofana integracja albo zmiana nazwy zmiennej. Pozostaną zaszyfrowane, dopóki ich nie usuniesz."
|
|
2738
|
+
},
|
|
2739
|
+
"toast": {
|
|
2740
|
+
"loadFailed": "Nie udało się wczytać poświadczeń funkcji",
|
|
2741
|
+
"saved": "Zapisano {key}",
|
|
2742
|
+
"saveFailed": "Nie udało się zapisać poświadczenia",
|
|
2743
|
+
"removeFailed": "Nie udało się usunąć poświadczenia"
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2712
2746
|
"apiTokens": {
|
|
2713
2747
|
"title": "Tokeny dostępu do API",
|
|
2714
2748
|
"intro": "Twórz tokeny, które systemy zewnętrzne przedstawiają API cat-factory. Każdy token uwierzytelnia się jako ten obszar roboczy w punktach końcowych /api/v1. Sekret jest wyświetlany tylko raz, podczas tworzenia, i nie można go odzyskać, więc zapisz go od razu.",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -2850,6 +2850,40 @@
|
|
|
2850
2850
|
"removeFailed": "Kayıt defteri girdisi kaldırılamadı"
|
|
2851
2851
|
}
|
|
2852
2852
|
},
|
|
2853
|
+
"capabilityCredentials": {
|
|
2854
|
+
"tab": "Yetenek kimlik bilgileri",
|
|
2855
|
+
"intro": "Bu kurulumdaki araç sunucularının ve üretken entegrasyonların adıyla istediği sırlar. Değerler yalnızca bu pano için saklanır, beklerken şifrelenir ve doğrudan ajanın sürecine verilir: ne bir isteme ne de bir günlüğe düşer. Değerler yalnızca yazılabilir, dolayısıyla saklanan bir değer yenisi yazılarak değiştirilir ve hiçbir zaman geri okunmaz.",
|
|
2856
|
+
"credentialNoun": "{key} kimlik bilgisi",
|
|
2857
|
+
"required": "Zorunlu",
|
|
2858
|
+
"optional": "İsteğe bağlı",
|
|
2859
|
+
"stored": "Saklanıyor",
|
|
2860
|
+
"storedAt": "Son ayarlanma: {date}",
|
|
2861
|
+
"notStoredWithFallback": "Bu pano için hiçbir şey saklanmıyor. Bu kurulum anahtarı kendi ortamından da okuyor, dolayısıyla yetenek hâlâ çalışıyor olabilir.",
|
|
2862
|
+
"notStored": "Bu pano için hiçbir şey saklanmıyor ve bu kurulumun ortam yedeği yok, dolayısıyla yetenek kimlik doğrulaması yapamaz.",
|
|
2863
|
+
"setValue": "Değer",
|
|
2864
|
+
"replaceValue": "Saklanan değeri değiştir",
|
|
2865
|
+
"save": "Kaydet",
|
|
2866
|
+
"remove": "Kimlik bilgisini kaldır",
|
|
2867
|
+
"noneDeclared": "Bu kurulumda kayıtlı yeteneklerin hiçbiri kimlik bilgisi istemiyor.",
|
|
2868
|
+
"subject": {
|
|
2869
|
+
"toolServer": "Araç sunucusu",
|
|
2870
|
+
"binaryGenerator": "Üretken entegrasyon"
|
|
2871
|
+
},
|
|
2872
|
+
"incomplete": {
|
|
2873
|
+
"title": "Bu liste eksik olabilir",
|
|
2874
|
+
"body": "Üretken entegrasyonlar okunamadı, bu yüzden aşağıda bunlardan birinin istediği bir kimlik bilgisi eksik olabilir. Hiçbir şeyin istemediği saklanan anahtarlar, liste yeniden okunabilene kadar gizli kalır."
|
|
2875
|
+
},
|
|
2876
|
+
"orphaned": {
|
|
2877
|
+
"heading": "Saklanıyor ama istenmiyor",
|
|
2878
|
+
"body": "Bu kurulumun kaydettiği hiçbir şey bu anahtarları istemiyor; kaldırılmış bir entegrasyonun ya da adı değişmiş bir değişkenin geride bıraktığı tam olarak budur. Siz kaldırana kadar şifreli kalırlar."
|
|
2879
|
+
},
|
|
2880
|
+
"toast": {
|
|
2881
|
+
"loadFailed": "Yetenek kimlik bilgileri yüklenemedi",
|
|
2882
|
+
"saved": "{key} kaydedildi",
|
|
2883
|
+
"saveFailed": "Kimlik bilgisi kaydedilemedi",
|
|
2884
|
+
"removeFailed": "Kimlik bilgisi kaldırılamadı"
|
|
2885
|
+
}
|
|
2886
|
+
},
|
|
2853
2887
|
"apiTokens": {
|
|
2854
2888
|
"title": "API erişim belirteçleri",
|
|
2855
2889
|
"intro": "Harici sistemlerin cat-factory API'sine sunduğu belirteçler oluşturun. Her belirteç, /api/v1 uç noktalarında bu çalışma alanı olarak kimlik doğrular. Gizli anahtar yalnızca oluşturulduğunda bir kez gösterilir ve kurtarılamaz, bu yüzden onu hemen saklayın.",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -2709,6 +2709,40 @@
|
|
|
2709
2709
|
"removeFailed": "Не вдалося видалити запис реєстру"
|
|
2710
2710
|
}
|
|
2711
2711
|
},
|
|
2712
|
+
"capabilityCredentials": {
|
|
2713
|
+
"tab": "Облікові дані можливостей",
|
|
2714
|
+
"intro": "Секрети, які сервери інструментів і генеративні інтеграції цього розгортання запитують за іменем. Значення зберігаються лише для цієї дошки, зашифрованими, і передаються просто в процес агента: вони ніколи не потрапляють ані в підказку, ані в журнал. Значення доступні лише для запису, тож збережене замінюють, ввівши нове, і ніколи не читають назад.",
|
|
2715
|
+
"credentialNoun": "облікові дані {key}",
|
|
2716
|
+
"required": "Обовʼязкові",
|
|
2717
|
+
"optional": "Необовʼязкові",
|
|
2718
|
+
"stored": "Збережено",
|
|
2719
|
+
"storedAt": "Востаннє задано {date}",
|
|
2720
|
+
"notStoredWithFallback": "Для цієї дошки нічого не збережено. Це розгортання також читає ключ із власного середовища, тож можливість може й далі працювати.",
|
|
2721
|
+
"notStored": "Для цієї дошки нічого не збережено, а запасного читання із середовища в цьому розгортанні немає, тож можливість не зможе пройти автентифікацію.",
|
|
2722
|
+
"setValue": "Значення",
|
|
2723
|
+
"replaceValue": "Замінити збережене значення",
|
|
2724
|
+
"save": "Зберегти",
|
|
2725
|
+
"remove": "Вилучити облікові дані",
|
|
2726
|
+
"noneDeclared": "Жодна із зареєстрованих можливостей цього розгортання не запитує облікових даних.",
|
|
2727
|
+
"subject": {
|
|
2728
|
+
"toolServer": "Сервер інструментів",
|
|
2729
|
+
"binaryGenerator": "Генеративна інтеграція"
|
|
2730
|
+
},
|
|
2731
|
+
"incomplete": {
|
|
2732
|
+
"title": "Цей список може бути неповним",
|
|
2733
|
+
"body": "Не вдалося прочитати генеративні інтеграції, тож нижче можуть бути відсутні облікові дані, які запитує одна з них. Збережені ключі, яких ніхто не запитує, лишаються прихованими, доки список не вдасться прочитати знову."
|
|
2734
|
+
},
|
|
2735
|
+
"orphaned": {
|
|
2736
|
+
"heading": "Збережені, але не запитані",
|
|
2737
|
+
"body": "Ніщо із зареєстрованого в цьому розгортанні не запитує ці ключі. Саме це лишає по собі вимкнена інтеграція або перейменована змінна. Вони лишатимуться зашифрованими, доки ви їх не вилучите."
|
|
2738
|
+
},
|
|
2739
|
+
"toast": {
|
|
2740
|
+
"loadFailed": "Не вдалося завантажити облікові дані можливостей",
|
|
2741
|
+
"saved": "{key} збережено",
|
|
2742
|
+
"saveFailed": "Не вдалося зберегти облікові дані",
|
|
2743
|
+
"removeFailed": "Не вдалося вилучити облікові дані"
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2712
2746
|
"apiTokens": {
|
|
2713
2747
|
"title": "Токени доступу до API",
|
|
2714
2748
|
"intro": "Створюйте токени, які зовнішні системи надають API cat-factory. Кожен токен автентифікується як цей робочий простір на кінцевих точках /api/v1. Секрет показується лише один раз, під час створення, і його не можна відновити, тож збережіть його одразу.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.209.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.217.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|