@cat-factory/app 0.46.11 → 0.47.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/auth/AuthGate.vue +3 -2
- package/app/components/auth/LoginScreen.vue +172 -32
- package/app/components/auth/ResetPasswordScreen.vue +16 -13
- package/app/components/auth/UserMenu.vue +3 -2
- package/app/components/layout/AccountDeploymentSettings.vue +307 -37
- package/app/components/layout/AccountFragmentSettings.vue +2 -3
- package/app/components/layout/AccountTeamSettings.vue +75 -45
- package/app/components/layout/AiProvidersBanner.vue +11 -11
- package/app/components/layout/BoardSwitcher.vue +63 -29
- package/app/components/layout/CommandBar.vue +68 -54
- package/app/components/layout/GitHubPatBanner.vue +13 -6
- package/app/components/layout/IntegrationBackTitle.vue +4 -1
- package/app/components/layout/IntegrationsHub.vue +67 -49
- package/app/components/layout/NotificationsInbox.vue +48 -19
- package/app/components/layout/PersonalSetupModal.vue +26 -16
- package/app/components/layout/ProviderConfigBanner.vue +14 -9
- package/app/components/layout/SideBar.vue +20 -18
- package/app/components/layout/SpendWarningBanner.vue +16 -16
- package/app/components/providers/ApiKeysSection.vue +27 -3
- package/app/components/providers/PersonalSubscriptionSection.vue +18 -4
- package/app/components/providers/SignInRequiredNotice.vue +19 -0
- package/app/components/settings/AccountSettingsPanel.vue +9 -6
- package/app/components/settings/IssueTrackerPanel.vue +114 -53
- package/app/components/settings/LocalModeSettingsPanel.vue +60 -28
- package/app/components/settings/LocalModelEndpointsPanel.vue +72 -27
- package/app/components/settings/MergeThresholdsPanel.vue +93 -45
- package/app/components/settings/ModelConfigurationPanel.vue +62 -36
- package/app/components/settings/ObservabilityConnectionPanel.vue +65 -35
- package/app/components/settings/OpenRouterCatalogPanel.vue +70 -40
- package/app/components/settings/ProviderConnectionPanel.vue +115 -61
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +9 -12
- package/app/components/settings/UserSecretsSection.vue +37 -18
- package/app/components/settings/WorkspaceSettingsPanel.vue +114 -62
- package/app/composables/api/auth.ts +7 -0
- package/app/stores/auth.ts +25 -1
- package/app/types/accountSettings.ts +4 -0
- package/i18n/locales/en.json +908 -0
- package/i18n/locales/es.json +899 -0
- package/i18n/locales/fr.json +899 -0
- package/i18n/locales/pl.json +899 -0
- package/i18n/locales/uk.json +899 -0
- package/package.json +2 -2
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, onMounted, reactive, ref } from 'vue'
|
|
3
|
+
import type { ContentStorageBackend, ContentStorageConfig } from '~/types/accountSettings'
|
|
3
4
|
|
|
4
5
|
// Deployment integration secrets for an account (admin only): the Slack app OAuth
|
|
5
|
-
// credentials
|
|
6
|
-
//
|
|
7
|
-
// ever shows whether each integration is
|
|
8
|
-
// blank inputs leave a configured secret
|
|
9
|
-
// wired (no ENCRYPTION_KEY).
|
|
6
|
+
// credentials, the container web-search upstream keys, and the binary-artifact (screenshot)
|
|
7
|
+
// content-storage backend — all moved out of env into the per-account settings store, sealed
|
|
8
|
+
// at rest. Secrets are write-only: the panel only ever shows whether each integration is
|
|
9
|
+
// configured (the `summary`), never the values; blank inputs leave a configured secret
|
|
10
|
+
// unchanged. Hidden when the settings store isn't wired (no ENCRYPTION_KEY).
|
|
10
11
|
const props = defineProps<{ accountId: string }>()
|
|
11
12
|
|
|
12
13
|
const store = useAccountSettingsStore()
|
|
13
14
|
const toast = useToast()
|
|
15
|
+
const { t } = useI18n()
|
|
14
16
|
|
|
15
17
|
const slack = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
|
|
16
18
|
const web = reactive({ braveApiKey: '', searxngUrl: '', searxngApiKey: '' })
|
|
@@ -19,12 +21,57 @@ const savingWeb = ref(false)
|
|
|
19
21
|
|
|
20
22
|
const summary = computed(() => store.view?.summary ?? null)
|
|
21
23
|
|
|
24
|
+
// ---- Content storage (binary artifacts / screenshots) --------------------
|
|
25
|
+
// Exhaustive enum→key map (drift guard tier 2): every backend resolves to a static literal
|
|
26
|
+
// `t()` key, so adding a backend without a label fails the typecheck on this Record.
|
|
27
|
+
const contentBackendLabels = computed<Record<ContentStorageBackend, string>>(() => ({
|
|
28
|
+
off: t('layout.accountDeployment.contentStorage.backends.off'),
|
|
29
|
+
fs: t('layout.accountDeployment.contentStorage.backends.fs'),
|
|
30
|
+
s3: t('layout.accountDeployment.contentStorage.backends.s3'),
|
|
31
|
+
r2: t('layout.accountDeployment.contentStorage.backends.r2'),
|
|
32
|
+
db: t('layout.accountDeployment.contentStorage.backends.db'),
|
|
33
|
+
}))
|
|
34
|
+
const storageCapability = computed(() => store.view?.contentStorageCapability ?? null)
|
|
35
|
+
const storageSummary = computed(() => summary.value?.contentStorage ?? null)
|
|
36
|
+
const backendItems = computed(() =>
|
|
37
|
+
(storageCapability.value?.supportedBackends ?? []).map((b) => ({
|
|
38
|
+
label: contentBackendLabels.value[b],
|
|
39
|
+
value: b,
|
|
40
|
+
})),
|
|
41
|
+
)
|
|
42
|
+
const csBackend = ref<ContentStorageBackend>('off')
|
|
43
|
+
const cs = reactive({
|
|
44
|
+
basePath: '',
|
|
45
|
+
region: '',
|
|
46
|
+
bucket: '',
|
|
47
|
+
prefix: '',
|
|
48
|
+
endpoint: '',
|
|
49
|
+
forcePathStyle: false,
|
|
50
|
+
accessKeyId: '',
|
|
51
|
+
secretAccessKey: '',
|
|
52
|
+
})
|
|
53
|
+
const savingStorage = ref(false)
|
|
54
|
+
|
|
55
|
+
function hydrateStorage() {
|
|
56
|
+
const cfg = store.view?.config?.contentStorage
|
|
57
|
+
csBackend.value = cfg?.backend ?? storageCapability.value?.defaultBackend ?? 'off'
|
|
58
|
+
cs.basePath = cfg?.fs?.basePath ?? ''
|
|
59
|
+
cs.region = cfg?.s3?.region ?? ''
|
|
60
|
+
cs.bucket = cfg?.s3?.bucket ?? ''
|
|
61
|
+
cs.prefix = cfg?.s3?.prefix ?? ''
|
|
62
|
+
cs.endpoint = cfg?.s3?.endpoint ?? ''
|
|
63
|
+
cs.forcePathStyle = cfg?.s3?.forcePathStyle ?? false
|
|
64
|
+
cs.accessKeyId = ''
|
|
65
|
+
cs.secretAccessKey = ''
|
|
66
|
+
}
|
|
67
|
+
|
|
22
68
|
onMounted(async () => {
|
|
23
69
|
try {
|
|
24
70
|
await store.load(props.accountId)
|
|
71
|
+
hydrateStorage()
|
|
25
72
|
} catch (e) {
|
|
26
73
|
toast.add({
|
|
27
|
-
title: '
|
|
74
|
+
title: t('layout.accountDeployment.loadFailed'),
|
|
28
75
|
description: e instanceof Error ? e.message : String(e),
|
|
29
76
|
icon: 'i-lucide-triangle-alert',
|
|
30
77
|
color: 'error',
|
|
@@ -32,9 +79,75 @@ onMounted(async () => {
|
|
|
32
79
|
}
|
|
33
80
|
})
|
|
34
81
|
|
|
82
|
+
async function saveStorage() {
|
|
83
|
+
const backend = csBackend.value
|
|
84
|
+
const config: ContentStorageConfig = { backend }
|
|
85
|
+
if (backend === 'fs' && cs.basePath.trim()) {
|
|
86
|
+
config.fs = { basePath: cs.basePath.trim() }
|
|
87
|
+
}
|
|
88
|
+
if (backend === 's3') {
|
|
89
|
+
if (!cs.region.trim() || !cs.bucket.trim()) {
|
|
90
|
+
toast.add({
|
|
91
|
+
title: t('layout.accountDeployment.contentStorage.regionBucketValidation'),
|
|
92
|
+
color: 'error',
|
|
93
|
+
})
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
config.s3 = {
|
|
97
|
+
region: cs.region.trim(),
|
|
98
|
+
bucket: cs.bucket.trim(),
|
|
99
|
+
...(cs.prefix.trim() ? { prefix: cs.prefix.trim() } : {}),
|
|
100
|
+
...(cs.endpoint.trim() ? { endpoint: cs.endpoint.trim() } : {}),
|
|
101
|
+
...(cs.forcePathStyle ? { forcePathStyle: true } : {}),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const input: Parameters<typeof store.save>[1] = { config: { contentStorage: config } }
|
|
105
|
+
if (backend === 's3') {
|
|
106
|
+
const id = cs.accessKeyId.trim()
|
|
107
|
+
const key = cs.secretAccessKey.trim()
|
|
108
|
+
if (id && key) {
|
|
109
|
+
input.secrets = { s3: { accessKeyId: id, secretAccessKey: key } }
|
|
110
|
+
} else if (id || key) {
|
|
111
|
+
toast.add({
|
|
112
|
+
title: t('layout.accountDeployment.contentStorage.bothKeysValidation'),
|
|
113
|
+
color: 'error',
|
|
114
|
+
})
|
|
115
|
+
return
|
|
116
|
+
} else if (!storageSummary.value?.s3CredentialsConfigured) {
|
|
117
|
+
toast.add({
|
|
118
|
+
title: t('layout.accountDeployment.contentStorage.keysValidation'),
|
|
119
|
+
color: 'error',
|
|
120
|
+
})
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
// else: keys already stored and none re-entered → leave them unchanged.
|
|
124
|
+
} else {
|
|
125
|
+
// Switching off S3: drop any stored S3 credentials.
|
|
126
|
+
input.secrets = { s3: null }
|
|
127
|
+
}
|
|
128
|
+
savingStorage.value = true
|
|
129
|
+
try {
|
|
130
|
+
await store.save(props.accountId, input)
|
|
131
|
+
hydrateStorage()
|
|
132
|
+
toast.add({
|
|
133
|
+
title: t('layout.accountDeployment.contentStorage.saved'),
|
|
134
|
+
icon: 'i-lucide-check',
|
|
135
|
+
color: 'success',
|
|
136
|
+
})
|
|
137
|
+
} catch (e) {
|
|
138
|
+
toast.add({
|
|
139
|
+
title: t('layout.accountDeployment.contentStorage.saveFailed'),
|
|
140
|
+
description: e instanceof Error ? e.message : String(e),
|
|
141
|
+
color: 'error',
|
|
142
|
+
})
|
|
143
|
+
} finally {
|
|
144
|
+
savingStorage.value = false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
35
148
|
async function saveSlack() {
|
|
36
149
|
if (!slack.clientId.trim() || !slack.clientSecret.trim() || !slack.redirectUrl.trim()) {
|
|
37
|
-
toast.add({ title: '
|
|
150
|
+
toast.add({ title: t('layout.accountDeployment.slack.validation'), color: 'error' })
|
|
38
151
|
return
|
|
39
152
|
}
|
|
40
153
|
savingSlack.value = true
|
|
@@ -51,10 +164,14 @@ async function saveSlack() {
|
|
|
51
164
|
slack.clientId = ''
|
|
52
165
|
slack.clientSecret = ''
|
|
53
166
|
slack.redirectUrl = ''
|
|
54
|
-
toast.add({
|
|
167
|
+
toast.add({
|
|
168
|
+
title: t('layout.accountDeployment.slack.saved'),
|
|
169
|
+
icon: 'i-lucide-check',
|
|
170
|
+
color: 'success',
|
|
171
|
+
})
|
|
55
172
|
} catch (e) {
|
|
56
173
|
toast.add({
|
|
57
|
-
title: '
|
|
174
|
+
title: t('layout.accountDeployment.slack.saveFailed'),
|
|
58
175
|
description: e instanceof Error ? e.message : String(e),
|
|
59
176
|
color: 'error',
|
|
60
177
|
})
|
|
@@ -67,10 +184,14 @@ async function clearSlack() {
|
|
|
67
184
|
savingSlack.value = true
|
|
68
185
|
try {
|
|
69
186
|
await store.save(props.accountId, { secrets: { slackOAuth: null } })
|
|
70
|
-
toast.add({
|
|
187
|
+
toast.add({
|
|
188
|
+
title: t('layout.accountDeployment.slack.cleared'),
|
|
189
|
+
icon: 'i-lucide-check',
|
|
190
|
+
color: 'success',
|
|
191
|
+
})
|
|
71
192
|
} catch (e) {
|
|
72
193
|
toast.add({
|
|
73
|
-
title: '
|
|
194
|
+
title: t('layout.accountDeployment.slack.clearFailed'),
|
|
74
195
|
description: e instanceof Error ? e.message : String(e),
|
|
75
196
|
color: 'error',
|
|
76
197
|
})
|
|
@@ -83,7 +204,7 @@ async function saveWeb() {
|
|
|
83
204
|
const brave = web.braveApiKey.trim()
|
|
84
205
|
const searxng = web.searxngUrl.trim()
|
|
85
206
|
if (!brave && !searxng) {
|
|
86
|
-
toast.add({ title: '
|
|
207
|
+
toast.add({ title: t('layout.accountDeployment.web.validation'), color: 'error' })
|
|
87
208
|
return
|
|
88
209
|
}
|
|
89
210
|
savingWeb.value = true
|
|
@@ -100,10 +221,14 @@ async function saveWeb() {
|
|
|
100
221
|
web.braveApiKey = ''
|
|
101
222
|
web.searxngUrl = ''
|
|
102
223
|
web.searxngApiKey = ''
|
|
103
|
-
toast.add({
|
|
224
|
+
toast.add({
|
|
225
|
+
title: t('layout.accountDeployment.web.saved'),
|
|
226
|
+
icon: 'i-lucide-check',
|
|
227
|
+
color: 'success',
|
|
228
|
+
})
|
|
104
229
|
} catch (e) {
|
|
105
230
|
toast.add({
|
|
106
|
-
title: '
|
|
231
|
+
title: t('layout.accountDeployment.web.saveFailed'),
|
|
107
232
|
description: e instanceof Error ? e.message : String(e),
|
|
108
233
|
color: 'error',
|
|
109
234
|
})
|
|
@@ -116,10 +241,14 @@ async function clearWeb() {
|
|
|
116
241
|
savingWeb.value = true
|
|
117
242
|
try {
|
|
118
243
|
await store.save(props.accountId, { secrets: { webSearch: null } })
|
|
119
|
-
toast.add({
|
|
244
|
+
toast.add({
|
|
245
|
+
title: t('layout.accountDeployment.web.cleared'),
|
|
246
|
+
icon: 'i-lucide-check',
|
|
247
|
+
color: 'success',
|
|
248
|
+
})
|
|
120
249
|
} catch (e) {
|
|
121
250
|
toast.add({
|
|
122
|
-
title: '
|
|
251
|
+
title: t('layout.accountDeployment.web.clearFailed'),
|
|
123
252
|
description: e instanceof Error ? e.message : String(e),
|
|
124
253
|
color: 'error',
|
|
125
254
|
})
|
|
@@ -132,38 +261,50 @@ async function clearWeb() {
|
|
|
132
261
|
<template>
|
|
133
262
|
<div v-if="store.available !== false" class="space-y-6">
|
|
134
263
|
<div>
|
|
135
|
-
<h3 class="mb-1 font-semibold text-white">
|
|
264
|
+
<h3 class="mb-1 font-semibold text-white">{{ t('layout.accountDeployment.title') }}</h3>
|
|
136
265
|
<p class="text-[11px] text-slate-400">
|
|
137
|
-
|
|
138
|
-
shown after saving; leave a field blank to keep the stored secret.
|
|
266
|
+
{{ t('layout.accountDeployment.intro') }}
|
|
139
267
|
</p>
|
|
140
268
|
</div>
|
|
141
269
|
|
|
142
270
|
<!-- Slack app OAuth -->
|
|
143
271
|
<section class="space-y-2">
|
|
144
272
|
<div class="flex items-center gap-2">
|
|
145
|
-
<h4 class="text-sm font-semibold text-slate-200">
|
|
273
|
+
<h4 class="text-sm font-semibold text-slate-200">
|
|
274
|
+
{{ t('layout.accountDeployment.slack.title') }}
|
|
275
|
+
</h4>
|
|
146
276
|
<UBadge
|
|
147
277
|
:color="summary?.slackOAuthConfigured ? 'success' : 'neutral'"
|
|
148
278
|
variant="subtle"
|
|
149
279
|
size="xs"
|
|
150
280
|
>
|
|
151
|
-
{{
|
|
281
|
+
{{
|
|
282
|
+
summary?.slackOAuthConfigured
|
|
283
|
+
? t('layout.accountDeployment.configured')
|
|
284
|
+
: t('layout.accountDeployment.notSet')
|
|
285
|
+
}}
|
|
152
286
|
</UBadge>
|
|
153
287
|
</div>
|
|
154
288
|
<p class="text-[11px] text-slate-400">
|
|
155
|
-
|
|
156
|
-
pasting a bot token.
|
|
289
|
+
{{ t('layout.accountDeployment.slack.description') }}
|
|
157
290
|
</p>
|
|
158
291
|
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
159
|
-
<UInput
|
|
292
|
+
<UInput
|
|
293
|
+
v-model="slack.clientId"
|
|
294
|
+
:placeholder="t('layout.accountDeployment.slack.clientId')"
|
|
295
|
+
size="sm"
|
|
296
|
+
/>
|
|
160
297
|
<UInput
|
|
161
298
|
v-model="slack.clientSecret"
|
|
162
299
|
type="password"
|
|
163
|
-
placeholder="
|
|
300
|
+
:placeholder="t('layout.accountDeployment.slack.clientSecret')"
|
|
301
|
+
size="sm"
|
|
302
|
+
/>
|
|
303
|
+
<UInput
|
|
304
|
+
v-model="slack.redirectUrl"
|
|
305
|
+
:placeholder="t('layout.accountDeployment.slack.redirectUrl')"
|
|
164
306
|
size="sm"
|
|
165
307
|
/>
|
|
166
|
-
<UInput v-model="slack.redirectUrl" placeholder="Redirect URL" size="sm" />
|
|
167
308
|
</div>
|
|
168
309
|
<div class="flex gap-2">
|
|
169
310
|
<UButton
|
|
@@ -173,7 +314,7 @@ async function clearWeb() {
|
|
|
173
314
|
:loading="savingSlack"
|
|
174
315
|
@click="saveSlack"
|
|
175
316
|
>
|
|
176
|
-
|
|
317
|
+
{{ t('common.save') }}
|
|
177
318
|
</UButton>
|
|
178
319
|
<UButton
|
|
179
320
|
v-if="summary?.slackOAuthConfigured"
|
|
@@ -183,7 +324,7 @@ async function clearWeb() {
|
|
|
183
324
|
:loading="savingSlack"
|
|
184
325
|
@click="clearSlack"
|
|
185
326
|
>
|
|
186
|
-
|
|
327
|
+
{{ t('layout.accountDeployment.clear') }}
|
|
187
328
|
</UButton>
|
|
188
329
|
</div>
|
|
189
330
|
</section>
|
|
@@ -191,22 +332,36 @@ async function clearWeb() {
|
|
|
191
332
|
<!-- Web search keys -->
|
|
192
333
|
<section class="space-y-2 border-t border-slate-800 pt-6">
|
|
193
334
|
<div class="flex items-center gap-2">
|
|
194
|
-
<h4 class="text-sm font-semibold text-slate-200">
|
|
335
|
+
<h4 class="text-sm font-semibold text-slate-200">
|
|
336
|
+
{{ t('layout.accountDeployment.web.title') }}
|
|
337
|
+
</h4>
|
|
195
338
|
<UBadge :color="summary?.webSearch ? 'success' : 'neutral'" variant="subtle" size="xs">
|
|
196
|
-
{{
|
|
339
|
+
{{
|
|
340
|
+
summary?.webSearch
|
|
341
|
+
? t('layout.accountDeployment.web.configured', { provider: summary.webSearch })
|
|
342
|
+
: t('layout.accountDeployment.notSet')
|
|
343
|
+
}}
|
|
197
344
|
</UBadge>
|
|
198
345
|
</div>
|
|
199
346
|
<p class="text-[11px] text-slate-400">
|
|
200
|
-
|
|
201
|
-
(recommended), or a self-hosted SearXNG URL (with an optional bearer key).
|
|
347
|
+
{{ t('layout.accountDeployment.web.description') }}
|
|
202
348
|
</p>
|
|
203
349
|
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
204
|
-
<UInput
|
|
205
|
-
|
|
350
|
+
<UInput
|
|
351
|
+
v-model="web.braveApiKey"
|
|
352
|
+
type="password"
|
|
353
|
+
:placeholder="t('layout.accountDeployment.web.braveKey')"
|
|
354
|
+
size="sm"
|
|
355
|
+
/>
|
|
356
|
+
<UInput
|
|
357
|
+
v-model="web.searxngUrl"
|
|
358
|
+
:placeholder="t('layout.accountDeployment.web.searxngUrl')"
|
|
359
|
+
size="sm"
|
|
360
|
+
/>
|
|
206
361
|
<UInput
|
|
207
362
|
v-model="web.searxngApiKey"
|
|
208
363
|
type="password"
|
|
209
|
-
placeholder="
|
|
364
|
+
:placeholder="t('layout.accountDeployment.web.searxngKey')"
|
|
210
365
|
size="sm"
|
|
211
366
|
/>
|
|
212
367
|
</div>
|
|
@@ -218,7 +373,7 @@ async function clearWeb() {
|
|
|
218
373
|
:loading="savingWeb"
|
|
219
374
|
@click="saveWeb"
|
|
220
375
|
>
|
|
221
|
-
|
|
376
|
+
{{ t('common.save') }}
|
|
222
377
|
</UButton>
|
|
223
378
|
<UButton
|
|
224
379
|
v-if="summary?.webSearch"
|
|
@@ -228,7 +383,122 @@ async function clearWeb() {
|
|
|
228
383
|
:loading="savingWeb"
|
|
229
384
|
@click="clearWeb"
|
|
230
385
|
>
|
|
231
|
-
|
|
386
|
+
{{ t('layout.accountDeployment.clear') }}
|
|
387
|
+
</UButton>
|
|
388
|
+
</div>
|
|
389
|
+
</section>
|
|
390
|
+
|
|
391
|
+
<!-- Content storage (binary artifacts / screenshots) -->
|
|
392
|
+
<section v-if="storageCapability" class="space-y-2 border-t border-slate-800 pt-6">
|
|
393
|
+
<div class="flex items-center gap-2">
|
|
394
|
+
<h4 class="text-sm font-semibold text-slate-200">
|
|
395
|
+
{{ t('layout.accountDeployment.contentStorage.title') }}
|
|
396
|
+
</h4>
|
|
397
|
+
<UBadge
|
|
398
|
+
:color="
|
|
399
|
+
storageSummary?.backend && storageSummary.backend !== 'off' ? 'success' : 'neutral'
|
|
400
|
+
"
|
|
401
|
+
variant="subtle"
|
|
402
|
+
size="xs"
|
|
403
|
+
>
|
|
404
|
+
{{
|
|
405
|
+
storageSummary?.backend
|
|
406
|
+
? contentBackendLabels[storageSummary.backend]
|
|
407
|
+
: t('layout.accountDeployment.contentStorage.default', {
|
|
408
|
+
backend: contentBackendLabels[storageCapability.defaultBackend],
|
|
409
|
+
})
|
|
410
|
+
}}
|
|
411
|
+
</UBadge>
|
|
412
|
+
</div>
|
|
413
|
+
<p class="text-[11px] text-slate-400">
|
|
414
|
+
{{ t('layout.accountDeployment.contentStorage.description') }}
|
|
415
|
+
</p>
|
|
416
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
417
|
+
<USelect v-model="csBackend" :items="backendItems" value-key="value" size="sm" />
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<!-- Filesystem -->
|
|
421
|
+
<div v-if="csBackend === 'fs'" class="grid grid-cols-1 gap-2">
|
|
422
|
+
<UInput
|
|
423
|
+
v-model="cs.basePath"
|
|
424
|
+
:placeholder="t('layout.accountDeployment.contentStorage.basePath')"
|
|
425
|
+
size="sm"
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<!-- S3 / S3-compatible -->
|
|
430
|
+
<template v-if="csBackend === 's3'">
|
|
431
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
432
|
+
<UInput
|
|
433
|
+
v-model="cs.region"
|
|
434
|
+
:placeholder="t('layout.accountDeployment.contentStorage.region')"
|
|
435
|
+
size="sm"
|
|
436
|
+
/>
|
|
437
|
+
<UInput
|
|
438
|
+
v-model="cs.bucket"
|
|
439
|
+
:placeholder="t('layout.accountDeployment.contentStorage.bucket')"
|
|
440
|
+
size="sm"
|
|
441
|
+
/>
|
|
442
|
+
<UInput
|
|
443
|
+
v-model="cs.prefix"
|
|
444
|
+
:placeholder="t('layout.accountDeployment.contentStorage.prefix')"
|
|
445
|
+
size="sm"
|
|
446
|
+
/>
|
|
447
|
+
<UInput
|
|
448
|
+
v-model="cs.endpoint"
|
|
449
|
+
:placeholder="t('layout.accountDeployment.contentStorage.endpoint')"
|
|
450
|
+
size="sm"
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
<UCheckbox
|
|
454
|
+
v-model="cs.forcePathStyle"
|
|
455
|
+
:label="t('layout.accountDeployment.contentStorage.forcePathStyle')"
|
|
456
|
+
size="sm"
|
|
457
|
+
/>
|
|
458
|
+
<div class="flex items-center gap-2">
|
|
459
|
+
<span class="text-[11px] text-slate-400">
|
|
460
|
+
{{ t('layout.accountDeployment.contentStorage.accessKeys') }}
|
|
461
|
+
</span>
|
|
462
|
+
<UBadge
|
|
463
|
+
:color="storageSummary?.s3CredentialsConfigured ? 'success' : 'neutral'"
|
|
464
|
+
variant="subtle"
|
|
465
|
+
size="xs"
|
|
466
|
+
>
|
|
467
|
+
{{
|
|
468
|
+
storageSummary?.s3CredentialsConfigured
|
|
469
|
+
? t('layout.accountDeployment.configured')
|
|
470
|
+
: t('layout.accountDeployment.notSet')
|
|
471
|
+
}}
|
|
472
|
+
</UBadge>
|
|
473
|
+
</div>
|
|
474
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
475
|
+
<UInput
|
|
476
|
+
v-model="cs.accessKeyId"
|
|
477
|
+
type="password"
|
|
478
|
+
:placeholder="t('layout.accountDeployment.contentStorage.accessKeyId')"
|
|
479
|
+
size="sm"
|
|
480
|
+
/>
|
|
481
|
+
<UInput
|
|
482
|
+
v-model="cs.secretAccessKey"
|
|
483
|
+
type="password"
|
|
484
|
+
:placeholder="t('layout.accountDeployment.contentStorage.secretAccessKey')"
|
|
485
|
+
size="sm"
|
|
486
|
+
/>
|
|
487
|
+
</div>
|
|
488
|
+
<p class="text-[11px] text-slate-400">
|
|
489
|
+
{{ t('layout.accountDeployment.contentStorage.keysHint') }}
|
|
490
|
+
</p>
|
|
491
|
+
</template>
|
|
492
|
+
|
|
493
|
+
<div class="flex gap-2">
|
|
494
|
+
<UButton
|
|
495
|
+
color="primary"
|
|
496
|
+
size="xs"
|
|
497
|
+
icon="i-lucide-save"
|
|
498
|
+
:loading="savingStorage"
|
|
499
|
+
@click="saveStorage"
|
|
500
|
+
>
|
|
501
|
+
{{ t('common.save') }}
|
|
232
502
|
</UButton>
|
|
233
503
|
</div>
|
|
234
504
|
</section>
|
|
@@ -8,6 +8,7 @@ import FragmentLibraryManager from '~/components/fragments/FragmentLibraryManage
|
|
|
8
8
|
|
|
9
9
|
const props = defineProps<{ accountId: string }>()
|
|
10
10
|
const workspace = useWorkspaceStore()
|
|
11
|
+
const { t } = useI18n()
|
|
11
12
|
|
|
12
13
|
// Account-tier document fragments are fetched through a board's stored
|
|
13
14
|
// document-source connection (credentials are per-workspace). Prefer the active
|
|
@@ -22,9 +23,7 @@ const viaWorkspaceId = computed(() => {
|
|
|
22
23
|
<div class="space-y-6 text-sm">
|
|
23
24
|
<section>
|
|
24
25
|
<p class="mb-3 text-[11px] text-slate-400">
|
|
25
|
-
|
|
26
|
-
and can override or add its own. Code-aware agents (coder, reviewer, architect, fixers) fold
|
|
27
|
-
the relevant ones into their prompt.
|
|
26
|
+
{{ t('layout.accountFragments.intro') }}
|
|
28
27
|
</p>
|
|
29
28
|
<FragmentLibraryManager
|
|
30
29
|
kind="account"
|