@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
|
@@ -16,6 +16,7 @@ interface Command {
|
|
|
16
16
|
run: () => void | Promise<void>
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const { t } = useI18n()
|
|
19
20
|
const ui = useUiStore()
|
|
20
21
|
const github = useGitHubStore()
|
|
21
22
|
const slack = useSlackStore()
|
|
@@ -34,13 +35,19 @@ const activeIndex = ref(0)
|
|
|
34
35
|
const commands = computed<Command[]>(() => {
|
|
35
36
|
const list: Command[] = []
|
|
36
37
|
|
|
38
|
+
const groupCreate = t('layout.commandBar.groups.create')
|
|
39
|
+
const groupRepositories = t('layout.commandBar.groups.repositories')
|
|
40
|
+
const groupIntegrations = t('layout.commandBar.groups.integrations')
|
|
41
|
+
const groupWorkspace = t('layout.commandBar.groups.workspace')
|
|
42
|
+
const groupAccount = t('layout.commandBar.groups.account')
|
|
43
|
+
|
|
37
44
|
// ---- Create -------------------------------------------------------------
|
|
38
45
|
list.push({
|
|
39
46
|
id: 'new-pipeline',
|
|
40
|
-
label: '
|
|
41
|
-
group:
|
|
47
|
+
label: t('layout.commandBar.cmd.newPipeline'),
|
|
48
|
+
group: groupCreate,
|
|
42
49
|
icon: 'i-lucide-workflow',
|
|
43
|
-
keywords: '
|
|
50
|
+
keywords: t('layout.commandBar.keywords.newPipeline'),
|
|
44
51
|
run: () => ui.openBuilder(),
|
|
45
52
|
})
|
|
46
53
|
|
|
@@ -48,19 +55,19 @@ const commands = computed<Command[]>(() => {
|
|
|
48
55
|
if (github.available) {
|
|
49
56
|
list.push({
|
|
50
57
|
id: 'add-from-repo',
|
|
51
|
-
label: '
|
|
52
|
-
group:
|
|
58
|
+
label: t('layout.commandBar.cmd.addFromRepo'),
|
|
59
|
+
group: groupRepositories,
|
|
53
60
|
icon: 'i-lucide-folder-git-2',
|
|
54
|
-
keywords: '
|
|
61
|
+
keywords: t('layout.commandBar.keywords.addFromRepo'),
|
|
55
62
|
run: () => ui.openAddService(),
|
|
56
63
|
})
|
|
57
64
|
}
|
|
58
65
|
list.push({
|
|
59
66
|
id: 'bootstrap-repo',
|
|
60
|
-
label: '
|
|
61
|
-
group:
|
|
67
|
+
label: t('layout.commandBar.cmd.bootstrapRepo'),
|
|
68
|
+
group: groupRepositories,
|
|
62
69
|
icon: 'i-lucide-git-branch-plus',
|
|
63
|
-
keywords: '
|
|
70
|
+
keywords: t('layout.commandBar.keywords.bootstrapRepo'),
|
|
64
71
|
run: () => ui.openBootstrap(),
|
|
65
72
|
})
|
|
66
73
|
|
|
@@ -68,20 +75,24 @@ const commands = computed<Command[]>(() => {
|
|
|
68
75
|
if (github.available) {
|
|
69
76
|
list.push({
|
|
70
77
|
id: 'github',
|
|
71
|
-
label: github.connected
|
|
72
|
-
|
|
78
|
+
label: github.connected
|
|
79
|
+
? t('layout.commandBar.cmd.githubManage')
|
|
80
|
+
: t('layout.commandBar.cmd.githubConnect'),
|
|
81
|
+
group: groupIntegrations,
|
|
73
82
|
icon: 'i-lucide-github',
|
|
74
|
-
keywords: '
|
|
83
|
+
keywords: t('layout.commandBar.keywords.github'),
|
|
75
84
|
run: () => ui.openGitHub(),
|
|
76
85
|
})
|
|
77
86
|
}
|
|
78
87
|
if (slack.available) {
|
|
79
88
|
list.push({
|
|
80
89
|
id: 'slack',
|
|
81
|
-
label: slack.connected
|
|
82
|
-
|
|
90
|
+
label: slack.connected
|
|
91
|
+
? t('layout.commandBar.cmd.slackManage')
|
|
92
|
+
: t('layout.commandBar.cmd.slackConnect'),
|
|
93
|
+
group: groupIntegrations,
|
|
83
94
|
icon: 'i-lucide-slack',
|
|
84
|
-
keywords: 'slack
|
|
95
|
+
keywords: t('layout.commandBar.keywords.slack'),
|
|
85
96
|
run: () => ui.openSlack(),
|
|
86
97
|
})
|
|
87
98
|
}
|
|
@@ -89,20 +100,22 @@ const commands = computed<Command[]>(() => {
|
|
|
89
100
|
for (const src of documents.sources) {
|
|
90
101
|
list.push({
|
|
91
102
|
id: `doc-connect-${src.source}`,
|
|
92
|
-
label: documents.isConnected(src.source)
|
|
93
|
-
|
|
103
|
+
label: documents.isConnected(src.source)
|
|
104
|
+
? t('layout.commandBar.cmd.sourceManage', { source: src.label })
|
|
105
|
+
: t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
|
|
106
|
+
group: groupIntegrations,
|
|
94
107
|
icon: src.icon,
|
|
95
|
-
keywords: '
|
|
108
|
+
keywords: t('layout.commandBar.keywords.documentSource'),
|
|
96
109
|
run: () => ui.openDocumentConnect(src.source),
|
|
97
110
|
})
|
|
98
111
|
}
|
|
99
112
|
if (documents.anyConnected) {
|
|
100
113
|
list.push({
|
|
101
114
|
id: 'doc-import',
|
|
102
|
-
label: '
|
|
103
|
-
group:
|
|
115
|
+
label: t('layout.commandBar.cmd.documentImport'),
|
|
116
|
+
group: groupIntegrations,
|
|
104
117
|
icon: 'i-lucide-file-down',
|
|
105
|
-
keywords: '
|
|
118
|
+
keywords: t('layout.commandBar.keywords.documentImport'),
|
|
106
119
|
run: () => ui.openDocumentImport(null),
|
|
107
120
|
})
|
|
108
121
|
}
|
|
@@ -111,20 +124,22 @@ const commands = computed<Command[]>(() => {
|
|
|
111
124
|
for (const src of tasks.sources) {
|
|
112
125
|
list.push({
|
|
113
126
|
id: `task-connect-${src.source}`,
|
|
114
|
-
label: src.available
|
|
115
|
-
|
|
127
|
+
label: src.available
|
|
128
|
+
? t('layout.commandBar.cmd.sourceManage', { source: src.label })
|
|
129
|
+
: t('layout.commandBar.cmd.sourceConnect', { source: src.label }),
|
|
130
|
+
group: groupIntegrations,
|
|
116
131
|
icon: src.icon,
|
|
117
|
-
keywords: '
|
|
132
|
+
keywords: t('layout.commandBar.keywords.taskSource'),
|
|
118
133
|
run: () => ui.openTaskConnect(src.source),
|
|
119
134
|
})
|
|
120
135
|
}
|
|
121
136
|
if (tasks.anyOffered) {
|
|
122
137
|
list.push({
|
|
123
138
|
id: 'task-import',
|
|
124
|
-
label: '
|
|
125
|
-
group:
|
|
139
|
+
label: t('layout.commandBar.cmd.taskImport'),
|
|
140
|
+
group: groupIntegrations,
|
|
126
141
|
icon: 'i-lucide-file-down',
|
|
127
|
-
keywords: '
|
|
142
|
+
keywords: t('layout.commandBar.keywords.taskImport'),
|
|
128
143
|
run: () => ui.openTaskImport(null),
|
|
129
144
|
})
|
|
130
145
|
}
|
|
@@ -134,68 +149,67 @@ const commands = computed<Command[]>(() => {
|
|
|
134
149
|
if (library.available) {
|
|
135
150
|
list.push({
|
|
136
151
|
id: 'fragments',
|
|
137
|
-
label: '
|
|
138
|
-
group:
|
|
152
|
+
label: t('layout.commandBar.cmd.fragments'),
|
|
153
|
+
group: groupWorkspace,
|
|
139
154
|
icon: 'i-lucide-book-marked',
|
|
140
|
-
keywords: '
|
|
155
|
+
keywords: t('layout.commandBar.keywords.fragments'),
|
|
141
156
|
run: () => ui.openFragmentLibrary(),
|
|
142
157
|
})
|
|
143
158
|
}
|
|
144
159
|
list.push({
|
|
145
160
|
id: 'merge-thresholds',
|
|
146
|
-
label: '
|
|
147
|
-
group:
|
|
161
|
+
label: t('layout.commandBar.cmd.mergeThresholds'),
|
|
162
|
+
group: groupWorkspace,
|
|
148
163
|
icon: 'i-lucide-git-merge',
|
|
149
|
-
keywords: '
|
|
164
|
+
keywords: t('layout.commandBar.keywords.mergeThresholds'),
|
|
150
165
|
run: () => ui.openWorkspaceSettings('merge'),
|
|
151
166
|
})
|
|
152
167
|
list.push({
|
|
153
168
|
id: 'workspace-settings',
|
|
154
|
-
label: '
|
|
155
|
-
group:
|
|
169
|
+
label: t('layout.commandBar.cmd.workspaceSettings'),
|
|
170
|
+
group: groupWorkspace,
|
|
156
171
|
icon: 'i-lucide-sliders-horizontal',
|
|
157
|
-
keywords: '
|
|
172
|
+
keywords: t('layout.commandBar.keywords.workspaceSettings'),
|
|
158
173
|
run: () => ui.openWorkspaceSettings(),
|
|
159
174
|
})
|
|
160
175
|
list.push({
|
|
161
176
|
id: 'model-configuration',
|
|
162
|
-
label: '
|
|
163
|
-
group:
|
|
177
|
+
label: t('layout.commandBar.cmd.modelConfiguration'),
|
|
178
|
+
group: groupWorkspace,
|
|
164
179
|
icon: 'i-lucide-cpu',
|
|
165
|
-
keywords: '
|
|
180
|
+
keywords: t('layout.commandBar.keywords.modelConfiguration'),
|
|
166
181
|
run: () => ui.openModelConfig(),
|
|
167
182
|
})
|
|
168
183
|
list.push({
|
|
169
184
|
id: 'service-fragment-defaults',
|
|
170
|
-
label: '
|
|
171
|
-
group:
|
|
185
|
+
label: t('layout.commandBar.cmd.serviceFragmentDefaults'),
|
|
186
|
+
group: groupWorkspace,
|
|
172
187
|
icon: 'i-lucide-book-open-check',
|
|
173
|
-
keywords: '
|
|
188
|
+
keywords: t('layout.commandBar.keywords.serviceFragmentDefaults'),
|
|
174
189
|
run: () => ui.openWorkspaceSettings('fragments'),
|
|
175
190
|
})
|
|
176
191
|
list.push({
|
|
177
192
|
id: 'account-settings',
|
|
178
|
-
label: '
|
|
179
|
-
group:
|
|
193
|
+
label: t('layout.commandBar.cmd.accountSettings'),
|
|
194
|
+
group: groupAccount,
|
|
180
195
|
icon: 'i-lucide-settings',
|
|
181
|
-
keywords:
|
|
182
|
-
'account team members roles invitations email api keys fragment best practice library context organization personal',
|
|
196
|
+
keywords: t('layout.commandBar.keywords.accountSettings'),
|
|
183
197
|
run: () => ui.openAccountSettings(),
|
|
184
198
|
})
|
|
185
199
|
list.push({
|
|
186
200
|
id: 'local-models',
|
|
187
|
-
label: '
|
|
188
|
-
group:
|
|
201
|
+
label: t('layout.commandBar.cmd.localModels'),
|
|
202
|
+
group: groupWorkspace,
|
|
189
203
|
icon: 'i-lucide-server',
|
|
190
|
-
keywords: '
|
|
204
|
+
keywords: t('layout.commandBar.keywords.localModels'),
|
|
191
205
|
run: () => ui.openLocalModels(),
|
|
192
206
|
})
|
|
193
207
|
list.push({
|
|
194
208
|
id: 'sandbox',
|
|
195
|
-
label: '
|
|
196
|
-
group:
|
|
209
|
+
label: t('layout.commandBar.cmd.sandbox'),
|
|
210
|
+
group: groupWorkspace,
|
|
197
211
|
icon: 'i-lucide-flask-conical',
|
|
198
|
-
keywords: 'sandbox
|
|
212
|
+
keywords: t('layout.commandBar.keywords.sandbox'),
|
|
199
213
|
run: () => ui.openSandbox(),
|
|
200
214
|
})
|
|
201
215
|
|
|
@@ -285,7 +299,7 @@ function indexOf(cmd: Command) {
|
|
|
285
299
|
ref="inputRef"
|
|
286
300
|
v-model="query"
|
|
287
301
|
variant="none"
|
|
288
|
-
placeholder="
|
|
302
|
+
:placeholder="t('layout.commandBar.searchPlaceholder')"
|
|
289
303
|
class="w-full"
|
|
290
304
|
:ui="{ base: 'py-3 text-sm' }"
|
|
291
305
|
/>
|
|
@@ -294,7 +308,7 @@ function indexOf(cmd: Command) {
|
|
|
294
308
|
|
|
295
309
|
<div class="max-h-80 overflow-y-auto p-1.5">
|
|
296
310
|
<p v-if="filtered.length === 0" class="px-3 py-6 text-center text-sm text-slate-500">
|
|
297
|
-
|
|
311
|
+
{{ t('layout.commandBar.noMatches') }}
|
|
298
312
|
</p>
|
|
299
313
|
|
|
300
314
|
<div v-for="group in groups" :key="group.name" class="mb-1">
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
3
|
|
|
4
|
+
const { t } = useI18n()
|
|
5
|
+
|
|
4
6
|
// Local-mode setup prompt: when the local facade boots without a GitHub PAT, every
|
|
5
7
|
// repo-operating agent step (clone, push, open PR, CI gate, merge) will fail. The server
|
|
6
8
|
// also logs this, but a dev terminal is easy to miss — so surface it in the UI with the
|
|
@@ -23,19 +25,20 @@ const show = computed(() => !!setupUrl.value && !dismissed.value)
|
|
|
23
25
|
<UIcon name="i-lucide-key-round" class="mt-0.5 h-9 w-9 shrink-0 text-amber-400" />
|
|
24
26
|
<div class="min-w-0 flex-1">
|
|
25
27
|
<div class="flex items-start justify-between gap-3">
|
|
26
|
-
<h2 class="text-lg font-semibold text-amber-100">
|
|
28
|
+
<h2 class="text-lg font-semibold text-amber-100">
|
|
29
|
+
{{ t('layout.githubPatBanner.title') }}
|
|
30
|
+
</h2>
|
|
27
31
|
<UButton
|
|
28
32
|
color="neutral"
|
|
29
33
|
variant="ghost"
|
|
30
34
|
size="xs"
|
|
31
35
|
icon="i-lucide-x"
|
|
32
|
-
aria-label="
|
|
36
|
+
:aria-label="t('common.close')"
|
|
33
37
|
@click="dismissed = true"
|
|
34
38
|
/>
|
|
35
39
|
</div>
|
|
36
40
|
<p class="mt-1 text-sm text-amber-200/90">
|
|
37
|
-
|
|
38
|
-
clone, push, open PRs, gate on CI or merge will fail.
|
|
41
|
+
{{ t('layout.githubPatBanner.body') }}
|
|
39
42
|
</p>
|
|
40
43
|
|
|
41
44
|
<div class="mt-4">
|
|
@@ -48,10 +51,14 @@ const show = computed(() => !!setupUrl.value && !dismissed.value)
|
|
|
48
51
|
icon="i-lucide-external-link"
|
|
49
52
|
trailing
|
|
50
53
|
>
|
|
51
|
-
|
|
54
|
+
{{ t('layout.githubPatBanner.createToken') }}
|
|
52
55
|
</UButton>
|
|
53
56
|
<p class="mt-2 text-xs text-amber-300/70">
|
|
54
|
-
|
|
57
|
+
<i18n-t keypath="layout.githubPatBanner.thenSet" tag="span" scope="global">
|
|
58
|
+
<template #envVar>
|
|
59
|
+
<code class="font-mono">GITHUB_PAT</code>
|
|
60
|
+
</template>
|
|
61
|
+
</i18n-t>
|
|
55
62
|
</p>
|
|
56
63
|
</div>
|
|
57
64
|
</div>
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
// emits `back` and the host panel closes itself + reopens the right hub.
|
|
9
9
|
defineProps<{ title?: string }>()
|
|
10
10
|
const emit = defineEmits<{ back: [] }>()
|
|
11
|
+
const { t } = useI18n()
|
|
11
12
|
const ui = useUiStore()
|
|
12
13
|
const cameFromHub = computed(() => ui.cameFromIntegrations || ui.cameFromPersonal)
|
|
13
14
|
const backLabel = computed(() =>
|
|
14
|
-
ui.cameFromPersonal
|
|
15
|
+
ui.cameFromPersonal
|
|
16
|
+
? t('layout.integrationBack.backToMySetup')
|
|
17
|
+
: t('layout.integrationBack.backToIntegrations'),
|
|
15
18
|
)
|
|
16
19
|
</script>
|
|
17
20
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// subscriptions) now live in the "My setup" hub (UserMenu → My setup), NOT here — keeping
|
|
12
12
|
// this hub purely workspace-scoped. When auth is disabled there is no UserMenu to host them,
|
|
13
13
|
// so a "Personal (only you)" group falls back into this hub so they stay reachable.
|
|
14
|
+
const { t } = useI18n()
|
|
14
15
|
const ui = useUiStore()
|
|
15
16
|
const auth = useAuthStore()
|
|
16
17
|
const github = useGitHubStore()
|
|
@@ -112,14 +113,16 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
112
113
|
// Top of the hub: an OpenRouter key is the fastest path to 300+ models, so it leads.
|
|
113
114
|
const openRouterKeyConnected = apiKeys.configuredProviders.has('openrouter')
|
|
114
115
|
out.push({
|
|
115
|
-
title: '
|
|
116
|
+
title: t('layout.integrationsHub.groups.models'),
|
|
116
117
|
items: [
|
|
117
118
|
{
|
|
118
119
|
key: 'openrouter',
|
|
119
120
|
icon: 'i-lucide-waypoints',
|
|
120
121
|
label: 'OpenRouter',
|
|
121
|
-
description: '
|
|
122
|
-
status: openRouterKeyConnected
|
|
122
|
+
description: t('layout.integrationsHub.items.openrouter.description'),
|
|
123
|
+
status: openRouterKeyConnected
|
|
124
|
+
? t('layout.integrationsHub.status.keyConnected')
|
|
125
|
+
: undefined,
|
|
123
126
|
connected: openRouterKeyConnected,
|
|
124
127
|
recommended: true,
|
|
125
128
|
onClick: () => go(ui.openOpenRouter),
|
|
@@ -127,8 +130,8 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
127
130
|
{
|
|
128
131
|
key: 'vendors',
|
|
129
132
|
icon: 'i-lucide-key-round',
|
|
130
|
-
label: '
|
|
131
|
-
description: '
|
|
133
|
+
label: t('layout.integrationsHub.items.vendors.label'),
|
|
134
|
+
description: t('layout.integrationsHub.items.vendors.description'),
|
|
132
135
|
onClick: () => go(ui.openVendorCredentials),
|
|
133
136
|
},
|
|
134
137
|
],
|
|
@@ -141,14 +144,15 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
141
144
|
key: 'github',
|
|
142
145
|
icon: 'i-lucide-github',
|
|
143
146
|
label: 'GitHub',
|
|
144
|
-
description: '
|
|
147
|
+
description: t('layout.integrationsHub.items.github.description'),
|
|
145
148
|
status: github.connected ? github.connection?.accountLogin : undefined,
|
|
146
149
|
connected: github.connected,
|
|
147
150
|
recommended: true,
|
|
148
151
|
onClick: () => go(ui.openGitHub),
|
|
149
152
|
})
|
|
150
153
|
}
|
|
151
|
-
if (code.length)
|
|
154
|
+
if (code.length)
|
|
155
|
+
out.push({ title: t('layout.integrationsHub.groups.sourceControl'), items: code })
|
|
152
156
|
|
|
153
157
|
// --- Communication ---------------------------------------------------------
|
|
154
158
|
const comms: IntegrationItem[] = []
|
|
@@ -157,13 +161,14 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
157
161
|
key: 'slack',
|
|
158
162
|
icon: 'i-lucide-slack',
|
|
159
163
|
label: 'Slack',
|
|
160
|
-
description: '
|
|
164
|
+
description: t('layout.integrationsHub.items.slack.description'),
|
|
161
165
|
status: slack.connected ? slack.connection?.teamName : undefined,
|
|
162
166
|
connected: slack.connected,
|
|
163
167
|
onClick: () => go(ui.openSlack),
|
|
164
168
|
})
|
|
165
169
|
}
|
|
166
|
-
if (comms.length)
|
|
170
|
+
if (comms.length)
|
|
171
|
+
out.push({ title: t('layout.integrationsHub.groups.communication'), items: comms })
|
|
167
172
|
|
|
168
173
|
// --- Documents (dynamic sources: Confluence / Notion / GitHub) -------------
|
|
169
174
|
if (documents.available && documents.sources.length) {
|
|
@@ -171,8 +176,12 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
171
176
|
key: `doc:${src.source}`,
|
|
172
177
|
icon: src.icon,
|
|
173
178
|
label: src.label,
|
|
174
|
-
description:
|
|
175
|
-
|
|
179
|
+
description: t('layout.integrationsHub.items.documentSource.description', {
|
|
180
|
+
source: src.label,
|
|
181
|
+
}),
|
|
182
|
+
status: documents.isConnected(src.source)
|
|
183
|
+
? t('layout.integrationsHub.status.connected')
|
|
184
|
+
: undefined,
|
|
176
185
|
connected: documents.isConnected(src.source),
|
|
177
186
|
onClick: () => go(() => ui.openDocumentConnect(src.source)),
|
|
178
187
|
}))
|
|
@@ -180,12 +189,12 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
180
189
|
docs.push({
|
|
181
190
|
key: 'doc:import',
|
|
182
191
|
icon: 'i-lucide-file-down',
|
|
183
|
-
label: '
|
|
184
|
-
description: '
|
|
192
|
+
label: t('layout.integrationsHub.items.documentImport.label'),
|
|
193
|
+
description: t('layout.integrationsHub.items.documentImport.description'),
|
|
185
194
|
onClick: () => go(() => ui.openDocumentImport(null)),
|
|
186
195
|
})
|
|
187
196
|
}
|
|
188
|
-
out.push({ title: '
|
|
197
|
+
out.push({ title: t('layout.integrationsHub.groups.documents'), items: docs })
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
// --- Task trackers (dynamic sources: Jira / GitHub) ------------------------
|
|
@@ -194,32 +203,32 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
194
203
|
key: `task:${src.source}`,
|
|
195
204
|
icon: src.icon,
|
|
196
205
|
label: src.label,
|
|
197
|
-
description:
|
|
206
|
+
description: t('layout.integrationsHub.items.taskSource.description', { source: src.label }),
|
|
198
207
|
// Available + enabled ⇒ offered (green); available + off ⇒ "Disabled" (amber);
|
|
199
208
|
// not available ⇒ no badge (Jira needs connecting; GitHub needs its App).
|
|
200
209
|
connected: src.available && src.enabled,
|
|
201
210
|
attention: src.available && !src.enabled,
|
|
202
|
-
attentionLabel: '
|
|
211
|
+
attentionLabel: t('layout.integrationsHub.status.disabled'),
|
|
203
212
|
onClick: () => go(() => ui.openTaskConnect(src.source)),
|
|
204
213
|
}))
|
|
205
214
|
if (tasks.anyOffered) {
|
|
206
215
|
trackers.push({
|
|
207
216
|
key: 'task:import',
|
|
208
217
|
icon: 'i-lucide-file-down',
|
|
209
|
-
label: '
|
|
210
|
-
description: '
|
|
218
|
+
label: t('layout.integrationsHub.items.taskImport.label'),
|
|
219
|
+
description: t('layout.integrationsHub.items.taskImport.description'),
|
|
211
220
|
onClick: () => go(() => ui.openTaskImport(null)),
|
|
212
221
|
})
|
|
213
222
|
}
|
|
214
223
|
// Choosing the filing tracker / writeback is workspace CONFIG, not an integration, so it
|
|
215
224
|
// sits as a quiet footer link under the sources rather than a competing row.
|
|
216
225
|
out.push({
|
|
217
|
-
title: '
|
|
226
|
+
title: t('layout.integrationsHub.groups.taskTrackers'),
|
|
218
227
|
items: trackers,
|
|
219
228
|
footerLink: {
|
|
220
229
|
key: 'task:tracker',
|
|
221
230
|
icon: 'i-lucide-list-checks',
|
|
222
|
-
label: '
|
|
231
|
+
label: t('layout.integrationsHub.items.trackerSettings.label'),
|
|
223
232
|
status: trackerLabel.value,
|
|
224
233
|
onClick: () => go(() => ui.openWorkspaceSettings('tracker')),
|
|
225
234
|
},
|
|
@@ -232,14 +241,16 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
232
241
|
// doesn't show a dead "Connect" row that only 503s.
|
|
233
242
|
if (releaseHealth.available) {
|
|
234
243
|
out.push({
|
|
235
|
-
title: '
|
|
244
|
+
title: t('layout.integrationsHub.groups.observability'),
|
|
236
245
|
items: [
|
|
237
246
|
{
|
|
238
247
|
key: 'observability',
|
|
239
248
|
icon: 'i-lucide-activity',
|
|
240
|
-
label: '
|
|
241
|
-
description: '
|
|
242
|
-
status: releaseHealth.connection.connected
|
|
249
|
+
label: t('layout.integrationsHub.items.observability.label'),
|
|
250
|
+
description: t('layout.integrationsHub.items.observability.description'),
|
|
251
|
+
status: releaseHealth.connection.connected
|
|
252
|
+
? t('layout.integrationsHub.status.connected')
|
|
253
|
+
: undefined,
|
|
243
254
|
connected: releaseHealth.connection.connected,
|
|
244
255
|
onClick: () => go(ui.openObservabilityConnection),
|
|
245
256
|
},
|
|
@@ -257,9 +268,9 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
257
268
|
infra.push({
|
|
258
269
|
key: 'environment',
|
|
259
270
|
icon: 'i-lucide-cloud',
|
|
260
|
-
label: '
|
|
261
|
-
description: '
|
|
262
|
-
status: conn ? '
|
|
271
|
+
label: t('layout.integrationsHub.items.environment.label'),
|
|
272
|
+
description: t('layout.integrationsHub.items.environment.description'),
|
|
273
|
+
status: conn ? t('layout.integrationsHub.status.connected') : undefined,
|
|
263
274
|
connected: !!conn,
|
|
264
275
|
onClick: () => go(() => ui.openProviderConnection('environment')),
|
|
265
276
|
})
|
|
@@ -269,9 +280,9 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
269
280
|
infra.push({
|
|
270
281
|
key: 'runner-pool',
|
|
271
282
|
icon: 'i-lucide-server-cog',
|
|
272
|
-
label: '
|
|
273
|
-
description: '
|
|
274
|
-
status: conn ? '
|
|
283
|
+
label: t('layout.integrationsHub.items.runnerPool.label'),
|
|
284
|
+
description: t('layout.integrationsHub.items.runnerPool.description'),
|
|
285
|
+
status: conn ? t('layout.integrationsHub.status.connected') : undefined,
|
|
275
286
|
connected: !!conn,
|
|
276
287
|
onClick: () => go(() => ui.openProviderConnection('runner-pool')),
|
|
277
288
|
})
|
|
@@ -283,12 +294,13 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
283
294
|
infra.push({
|
|
284
295
|
key: 'local-mode',
|
|
285
296
|
icon: 'i-lucide-container',
|
|
286
|
-
label: '
|
|
287
|
-
description: '
|
|
297
|
+
label: t('layout.integrationsHub.items.localMode.label'),
|
|
298
|
+
description: t('layout.integrationsHub.items.localMode.description'),
|
|
288
299
|
onClick: () => go(ui.openLocalModeSettings),
|
|
289
300
|
})
|
|
290
301
|
}
|
|
291
|
-
if (infra.length)
|
|
302
|
+
if (infra.length)
|
|
303
|
+
out.push({ title: t('layout.integrationsHub.groups.infrastructure'), items: infra })
|
|
292
304
|
|
|
293
305
|
// --- Personal (only you) — fallback when there is no UserMenu to host "My setup" -------
|
|
294
306
|
// Per-user connections normally live in the My-setup hub; with auth disabled they fold in
|
|
@@ -296,22 +308,22 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
296
308
|
if (!personalHubReachable.value) {
|
|
297
309
|
const pat = !!userSecrets.statusFor('github_pat')
|
|
298
310
|
out.push({
|
|
299
|
-
title: '
|
|
311
|
+
title: t('layout.integrationsHub.groups.personal'),
|
|
300
312
|
items: [
|
|
301
313
|
{
|
|
302
314
|
key: 'github-pat',
|
|
303
315
|
icon: 'i-lucide-key-round',
|
|
304
|
-
label: '
|
|
305
|
-
description: '
|
|
306
|
-
status: pat ? '
|
|
316
|
+
label: t('layout.integrationsHub.items.githubPat.label'),
|
|
317
|
+
description: t('layout.integrationsHub.items.githubPat.description'),
|
|
318
|
+
status: pat ? t('layout.integrationsHub.status.connected') : undefined,
|
|
307
319
|
connected: pat,
|
|
308
320
|
onClick: () => go(ui.openUserSecrets),
|
|
309
321
|
},
|
|
310
322
|
{
|
|
311
323
|
key: 'local-runners',
|
|
312
324
|
icon: 'i-lucide-server',
|
|
313
|
-
label: '
|
|
314
|
-
description: '
|
|
325
|
+
label: t('layout.integrationsHub.items.localRunners.label'),
|
|
326
|
+
description: t('layout.integrationsHub.items.localRunners.description'),
|
|
315
327
|
onClick: () => go(ui.openLocalModels),
|
|
316
328
|
},
|
|
317
329
|
],
|
|
@@ -360,11 +372,15 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
|
|
|
360
372
|
</script>
|
|
361
373
|
|
|
362
374
|
<template>
|
|
363
|
-
<UModal
|
|
375
|
+
<UModal
|
|
376
|
+
v-model:open="open"
|
|
377
|
+
:title="t('layout.integrationsHub.title')"
|
|
378
|
+
:ui="{ content: 'max-w-xl' }"
|
|
379
|
+
>
|
|
364
380
|
<template #body>
|
|
365
381
|
<div class="space-y-5">
|
|
366
382
|
<p class="text-xs text-slate-400">
|
|
367
|
-
|
|
383
|
+
{{ t('layout.integrationsHub.intro') }}
|
|
368
384
|
</p>
|
|
369
385
|
|
|
370
386
|
<!-- Get-started cue: an empty workspace gets the two essentials up front so the first
|
|
@@ -375,10 +391,10 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
|
|
|
375
391
|
>
|
|
376
392
|
<div class="mb-2 flex items-center gap-2 text-sm font-medium text-primary-200">
|
|
377
393
|
<UIcon name="i-lucide-rocket" class="h-4 w-4 shrink-0" />
|
|
378
|
-
<span>
|
|
394
|
+
<span>{{ t('layout.integrationsHub.getStarted.title') }}</span>
|
|
379
395
|
</div>
|
|
380
396
|
<p class="mb-3 text-xs text-slate-300">
|
|
381
|
-
|
|
397
|
+
{{ t('layout.integrationsHub.getStarted.body') }}
|
|
382
398
|
</p>
|
|
383
399
|
<div class="flex flex-wrap gap-2">
|
|
384
400
|
<UButton
|
|
@@ -399,12 +415,12 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
|
|
|
399
415
|
v-model="query"
|
|
400
416
|
icon="i-lucide-search"
|
|
401
417
|
size="sm"
|
|
402
|
-
placeholder="
|
|
418
|
+
:placeholder="t('layout.integrationsHub.searchPlaceholder')"
|
|
403
419
|
class="w-full"
|
|
404
420
|
/>
|
|
405
421
|
|
|
406
422
|
<p v-if="!filteredGroups.length" class="px-1 py-6 text-center text-sm text-slate-500">
|
|
407
|
-
|
|
423
|
+
{{ t('layout.integrationsHub.noMatches', { query }) }}
|
|
408
424
|
</p>
|
|
409
425
|
|
|
410
426
|
<section v-for="group in filteredGroups" :key="group.title">
|
|
@@ -424,19 +440,21 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
|
|
|
424
440
|
<div class="flex items-center gap-2">
|
|
425
441
|
<span class="truncate text-sm font-medium text-slate-100">{{ item.label }}</span>
|
|
426
442
|
<UBadge v-if="item.connected" color="success" variant="subtle" size="sm">
|
|
427
|
-
{{ item.status || '
|
|
443
|
+
{{ item.status || t('layout.integrationsHub.status.connected') }}
|
|
428
444
|
</UBadge>
|
|
429
445
|
<UBadge v-else-if="item.attention" color="warning" variant="subtle" size="sm">
|
|
430
|
-
{{ item.attentionLabel || '
|
|
446
|
+
{{ item.attentionLabel || t('layout.integrationsHub.status.needsAttention') }}
|
|
431
447
|
</UBadge>
|
|
432
|
-
<span v-else class="text-[11px] text-slate-500">
|
|
448
|
+
<span v-else class="text-[11px] text-slate-500">{{
|
|
449
|
+
t('layout.integrationsHub.status.notConnected')
|
|
450
|
+
}}</span>
|
|
433
451
|
<UBadge
|
|
434
452
|
v-if="!anyConnected && item.recommended && !item.connected"
|
|
435
453
|
color="primary"
|
|
436
454
|
variant="subtle"
|
|
437
455
|
size="sm"
|
|
438
456
|
>
|
|
439
|
-
|
|
457
|
+
{{ t('layout.integrationsHub.status.recommended') }}
|
|
440
458
|
</UBadge>
|
|
441
459
|
</div>
|
|
442
460
|
<p class="truncate text-xs text-slate-400">{{ item.description }}</p>
|