@cat-factory/app 0.46.12 → 0.47.1
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 +1 -1
- package/app/components/auth/LoginScreen.vue +131 -1
- package/app/components/layout/AccountDeploymentSettings.vue +232 -5
- package/app/components/providers/AiPresetMismatchDialog.vue +26 -13
- package/app/components/providers/AiProviderOnboardingModal.vue +18 -15
- package/app/components/providers/ApiKeysSection.vue +105 -63
- package/app/components/providers/PersonalCredentialModal.vue +35 -29
- package/app/components/providers/PersonalSubscriptionSection.vue +17 -2
- package/app/components/providers/SignInRequiredNotice.vue +19 -0
- package/app/components/providers/VendorCredentialsModal.vue +82 -39
- 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 +663 -0
- package/i18n/locales/es.json +651 -0
- package/i18n/locales/fr.json +651 -0
- package/i18n/locales/pl.json +651 -0
- package/i18n/locales/uk.json +651 -0
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
9
9
|
import type { SubscriptionVendor } from '~/types/domain'
|
|
10
10
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
11
11
|
|
|
12
|
+
const { t, n } = useI18n()
|
|
12
13
|
const ui = useUiStore()
|
|
13
14
|
const workspace = useWorkspaceStore()
|
|
14
15
|
const creds = useVendorCredentialsStore()
|
|
@@ -25,27 +26,51 @@ const back = useIntegrationBack(open)
|
|
|
25
26
|
// Initialised from the ui store so a caller can deep-link to a tab — the user-scoped
|
|
26
27
|
// "My subscriptions" entry opens straight onto the `personal` tab.
|
|
27
28
|
const activeTab = ref(ui.vendorCredentialsTab)
|
|
28
|
-
const tabs = [
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const tabs = computed(() => [
|
|
30
|
+
{
|
|
31
|
+
value: 'pool',
|
|
32
|
+
label: t('providers.vendorCredentials.tabs.pool'),
|
|
33
|
+
icon: 'i-lucide-users',
|
|
34
|
+
slot: 'pool',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: 'direct',
|
|
38
|
+
label: t('providers.vendorCredentials.tabs.direct'),
|
|
39
|
+
icon: 'i-lucide-key-round',
|
|
40
|
+
slot: 'direct',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: 'proxy',
|
|
44
|
+
label: t('providers.vendorCredentials.tabs.proxy'),
|
|
45
|
+
icon: 'i-lucide-route',
|
|
46
|
+
slot: 'proxy',
|
|
47
|
+
},
|
|
32
48
|
{
|
|
33
49
|
value: 'personal',
|
|
34
|
-
label: '
|
|
50
|
+
label: t('providers.vendorCredentials.tabs.personal'),
|
|
35
51
|
icon: 'i-lucide-user',
|
|
36
52
|
slot: 'personal',
|
|
37
53
|
},
|
|
38
|
-
]
|
|
54
|
+
])
|
|
39
55
|
|
|
40
56
|
// Only commercial coding-plan vendors that permit team/organization use are poolable here.
|
|
41
57
|
// Claude, GLM and ChatGPT/Codex are licensed for individual use only, so they are connected
|
|
42
58
|
// per-user in the "Personal subscriptions" section below (PersonalSubscriptionSection).
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
|
|
59
|
+
// Labels resolve through i18n (reactive to the locale) via literal keys.
|
|
60
|
+
const VENDORS = computed<{ value: SubscriptionVendor; label: string; harness: string }[]>(() => [
|
|
61
|
+
{
|
|
62
|
+
value: 'kimi',
|
|
63
|
+
label: t('providers.vendorCredentials.vendors.kimi.label'),
|
|
64
|
+
harness: 'Claude Code',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
value: 'deepseek',
|
|
68
|
+
label: t('providers.vendorCredentials.vendors.deepseek.label'),
|
|
69
|
+
harness: 'Claude Code',
|
|
70
|
+
},
|
|
71
|
+
])
|
|
47
72
|
|
|
48
|
-
const visibleVendors = computed(() => VENDORS)
|
|
73
|
+
const visibleVendors = computed(() => VENDORS.value)
|
|
49
74
|
|
|
50
75
|
const vendor = ref<SubscriptionVendor>('kimi')
|
|
51
76
|
const label = ref('')
|
|
@@ -68,22 +93,22 @@ const steps = computed<string[]>(() => {
|
|
|
68
93
|
switch (vendor.value) {
|
|
69
94
|
case 'kimi':
|
|
70
95
|
return [
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
96
|
+
t('providers.vendorCredentials.vendors.kimi.step1'),
|
|
97
|
+
t('providers.vendorCredentials.vendors.kimi.step2'),
|
|
98
|
+
t('providers.vendorCredentials.vendors.kimi.step3'),
|
|
74
99
|
]
|
|
75
100
|
case 'deepseek':
|
|
76
101
|
return [
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
102
|
+
t('providers.vendorCredentials.vendors.deepseek.step1'),
|
|
103
|
+
t('providers.vendorCredentials.vendors.deepseek.step2'),
|
|
104
|
+
t('providers.vendorCredentials.vendors.deepseek.step3'),
|
|
80
105
|
]
|
|
81
106
|
default:
|
|
82
107
|
return []
|
|
83
108
|
}
|
|
84
109
|
})
|
|
85
110
|
|
|
86
|
-
const tokenPlaceholder = computed(() => '
|
|
111
|
+
const tokenPlaceholder = computed(() => t('providers.vendorCredentials.tokenPlaceholder'))
|
|
87
112
|
|
|
88
113
|
async function add() {
|
|
89
114
|
if (!token.value.trim()) return
|
|
@@ -91,15 +116,21 @@ async function add() {
|
|
|
91
116
|
try {
|
|
92
117
|
await creds.add({
|
|
93
118
|
vendor: vendor.value,
|
|
94
|
-
label:
|
|
119
|
+
label:
|
|
120
|
+
label.value.trim() ||
|
|
121
|
+
t('providers.vendorCredentials.defaultLabel', { vendor: vendor.value }),
|
|
95
122
|
token: token.value.trim(),
|
|
96
123
|
})
|
|
97
124
|
token.value = ''
|
|
98
125
|
label.value = ''
|
|
99
|
-
toast.add({
|
|
126
|
+
toast.add({
|
|
127
|
+
title: t('providers.vendorCredentials.toast.connected'),
|
|
128
|
+
icon: 'i-lucide-check',
|
|
129
|
+
color: 'success',
|
|
130
|
+
})
|
|
100
131
|
} catch (e) {
|
|
101
132
|
toast.add({
|
|
102
|
-
title: '
|
|
133
|
+
title: t('providers.vendorCredentials.toast.connectFailed'),
|
|
103
134
|
description: e instanceof Error ? e.message : String(e),
|
|
104
135
|
color: 'error',
|
|
105
136
|
})
|
|
@@ -113,7 +144,7 @@ async function remove(id: string) {
|
|
|
113
144
|
await creds.remove(id)
|
|
114
145
|
} catch (e) {
|
|
115
146
|
toast.add({
|
|
116
|
-
title: '
|
|
147
|
+
title: t('providers.vendorCredentials.toast.removeFailed'),
|
|
117
148
|
description: e instanceof Error ? e.message : String(e),
|
|
118
149
|
color: 'error',
|
|
119
150
|
})
|
|
@@ -121,14 +152,18 @@ async function remove(id: string) {
|
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
function vendorLabel(v: SubscriptionVendor): string {
|
|
124
|
-
return VENDORS.find((x) => x.value === v)?.label ?? v
|
|
155
|
+
return VENDORS.value.find((x) => x.value === v)?.label ?? v
|
|
125
156
|
}
|
|
126
157
|
</script>
|
|
127
158
|
|
|
128
159
|
<template>
|
|
129
|
-
<UModal
|
|
160
|
+
<UModal
|
|
161
|
+
v-model:open="open"
|
|
162
|
+
:title="t('providers.vendorCredentials.title')"
|
|
163
|
+
:ui="{ content: 'max-w-2xl' }"
|
|
164
|
+
>
|
|
130
165
|
<template #title>
|
|
131
|
-
<IntegrationBackTitle title="
|
|
166
|
+
<IntegrationBackTitle :title="t('providers.vendorCredentials.title')" @back="back" />
|
|
132
167
|
</template>
|
|
133
168
|
<template #body>
|
|
134
169
|
<UTabs
|
|
@@ -141,17 +176,12 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
141
176
|
<template #pool>
|
|
142
177
|
<div class="space-y-5">
|
|
143
178
|
<p class="text-sm text-slate-400">
|
|
144
|
-
|
|
145
|
-
permits team/organization use to run agent steps on the Claude Code harness instead of
|
|
146
|
-
an API key. Tokens are stored encrypted, pooled, and rotated by usage. Subscription
|
|
147
|
-
models are flat-rate quota — they don’t draw on your spend budget. Individual-use
|
|
148
|
-
subscriptions (Claude, GLM, ChatGPT/Codex) are connected per-user in the Personal
|
|
149
|
-
subscriptions tab.
|
|
179
|
+
{{ t('providers.vendorCredentials.poolIntro') }}
|
|
150
180
|
</p>
|
|
151
181
|
|
|
152
182
|
<!-- vendor picker -->
|
|
153
183
|
<div class="flex flex-wrap items-end gap-3">
|
|
154
|
-
<UFormField label="
|
|
184
|
+
<UFormField :label="t('providers.vendorCredentials.vendorField')">
|
|
155
185
|
<USelect
|
|
156
186
|
v-model="vendor"
|
|
157
187
|
:items="visibleVendors.map((v) => ({ label: v.label, value: v.value }))"
|
|
@@ -169,10 +199,13 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
169
199
|
|
|
170
200
|
<!-- add form -->
|
|
171
201
|
<div class="space-y-2">
|
|
172
|
-
<UFormField label="
|
|
173
|
-
<UInput
|
|
202
|
+
<UFormField :label="t('providers.vendorCredentials.labelField')">
|
|
203
|
+
<UInput
|
|
204
|
+
v-model="label"
|
|
205
|
+
:placeholder="t('providers.vendorCredentials.labelPlaceholder')"
|
|
206
|
+
/>
|
|
174
207
|
</UFormField>
|
|
175
|
-
<UFormField label="
|
|
208
|
+
<UFormField :label="t('providers.vendorCredentials.tokenField')">
|
|
176
209
|
<UTextarea
|
|
177
210
|
v-model="token"
|
|
178
211
|
:rows="3"
|
|
@@ -187,7 +220,7 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
187
220
|
icon="i-lucide-plus"
|
|
188
221
|
@click="add()"
|
|
189
222
|
>
|
|
190
|
-
|
|
223
|
+
{{ t('providers.vendorCredentials.connect') }}
|
|
191
224
|
</UButton>
|
|
192
225
|
</div>
|
|
193
226
|
</div>
|
|
@@ -195,7 +228,9 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
195
228
|
<!-- connected pool -->
|
|
196
229
|
<div v-if="creds.credentials.length" class="space-y-2">
|
|
197
230
|
<h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
198
|
-
|
|
231
|
+
{{
|
|
232
|
+
t('providers.vendorCredentials.connected', { count: creds.credentials.length })
|
|
233
|
+
}}
|
|
199
234
|
</h4>
|
|
200
235
|
<div
|
|
201
236
|
v-for="c in creds.credentials"
|
|
@@ -206,8 +241,16 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
206
241
|
<span class="font-medium text-slate-200">{{ c.label }}</span>
|
|
207
242
|
<span class="ml-2 text-xs text-slate-500">{{ vendorLabel(c.vendor) }}</span>
|
|
208
243
|
<div class="text-[11px] tabular-nums text-slate-500">
|
|
209
|
-
{{
|
|
210
|
-
|
|
244
|
+
{{
|
|
245
|
+
t(
|
|
246
|
+
'providers.vendorCredentials.usage',
|
|
247
|
+
{
|
|
248
|
+
tokens: n(c.inputTokens + c.outputTokens, 'decimal'),
|
|
249
|
+
count: c.requestCount,
|
|
250
|
+
},
|
|
251
|
+
c.requestCount,
|
|
252
|
+
)
|
|
253
|
+
}}
|
|
211
254
|
</div>
|
|
212
255
|
</div>
|
|
213
256
|
<UButton
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import AccountTeamSettings from '~/components/layout/AccountTeamSettings.vue'
|
|
9
9
|
import AccountFragmentSettings from '~/components/layout/AccountFragmentSettings.vue'
|
|
10
10
|
|
|
11
|
+
const { t } = useI18n()
|
|
11
12
|
const ui = useUiStore()
|
|
12
13
|
const accounts = useAccountsStore()
|
|
13
14
|
|
|
@@ -23,21 +24,23 @@ const activeTab = computed({
|
|
|
23
24
|
set: (v: string) => ui.setAccountSettingsTab(v),
|
|
24
25
|
})
|
|
25
26
|
|
|
26
|
-
const tabs = [
|
|
27
|
-
{ value: 'team', label: '
|
|
27
|
+
const tabs = computed(() => [
|
|
28
|
+
{ value: 'team', label: t('settings.account.tabs.team'), icon: 'i-lucide-users', slot: 'team' },
|
|
28
29
|
{
|
|
29
30
|
value: 'fragments',
|
|
30
|
-
label: '
|
|
31
|
+
label: t('settings.account.tabs.fragments'),
|
|
31
32
|
icon: 'i-lucide-book-marked',
|
|
32
33
|
slot: 'fragments',
|
|
33
34
|
},
|
|
34
|
-
]
|
|
35
|
+
])
|
|
35
36
|
</script>
|
|
36
37
|
|
|
37
38
|
<template>
|
|
38
|
-
<UModal v-model:open="open" title="
|
|
39
|
+
<UModal v-model:open="open" :title="t('settings.account.title')" :ui="{ content: 'max-w-3xl' }">
|
|
39
40
|
<template #body>
|
|
40
|
-
<p v-if="!accounts.activeAccountId" class="text-sm text-slate-400">
|
|
41
|
+
<p v-if="!accounts.activeAccountId" class="text-sm text-slate-400">
|
|
42
|
+
{{ t('settings.account.noAccount') }}
|
|
43
|
+
</p>
|
|
41
44
|
<UTabs
|
|
42
45
|
v-else
|
|
43
46
|
v-model="activeTab"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
18
18
|
import type { TaskSourceDiagnosticStatus, TaskSourceKind, TrackerKind } from '~/types/domain'
|
|
19
19
|
|
|
20
|
+
const { t } = useI18n()
|
|
20
21
|
const tracker = useTrackerStore()
|
|
21
22
|
const tasks = useTasksStore()
|
|
22
23
|
const ui = useUiStore()
|
|
@@ -78,10 +79,14 @@ async function save() {
|
|
|
78
79
|
writebackCommentOnPrOpen: commentOnPrOpen.value,
|
|
79
80
|
writebackResolveOnMerge: resolveOnMerge.value,
|
|
80
81
|
})
|
|
81
|
-
toast.add({
|
|
82
|
+
toast.add({
|
|
83
|
+
title: t('settings.issueTracker.toast.saved'),
|
|
84
|
+
icon: 'i-lucide-check',
|
|
85
|
+
color: 'success',
|
|
86
|
+
})
|
|
82
87
|
} catch (e) {
|
|
83
88
|
toast.add({
|
|
84
|
-
title: '
|
|
89
|
+
title: t('settings.issueTracker.toast.saveFailed'),
|
|
85
90
|
description: e instanceof Error ? e.message : String(e),
|
|
86
91
|
icon: 'i-lucide-triangle-alert',
|
|
87
92
|
color: 'error',
|
|
@@ -100,7 +105,7 @@ async function toggleSource(source: TaskSourceKind, enabled: boolean) {
|
|
|
100
105
|
await tasks.setEnabled(source, enabled)
|
|
101
106
|
} catch (e) {
|
|
102
107
|
toast.add({
|
|
103
|
-
title: '
|
|
108
|
+
title: t('settings.issueTracker.toast.updateFailed'),
|
|
104
109
|
description: e instanceof Error ? e.message : String(e),
|
|
105
110
|
icon: 'i-lucide-triangle-alert',
|
|
106
111
|
color: 'error',
|
|
@@ -118,12 +123,20 @@ const probeFailureHint = computed(() => {
|
|
|
118
123
|
const err = tasks.probeError
|
|
119
124
|
if (tasks.available !== false || !err) return null
|
|
120
125
|
if (err.status === 503) {
|
|
121
|
-
return '
|
|
126
|
+
return t('settings.issueTracker.probeFailure.disabled')
|
|
122
127
|
}
|
|
123
128
|
if (err.status && err.status >= 500) {
|
|
124
|
-
return
|
|
129
|
+
return t('settings.issueTracker.probeFailure.serverError', {
|
|
130
|
+
status: err.status,
|
|
131
|
+
message: err.message,
|
|
132
|
+
})
|
|
125
133
|
}
|
|
126
|
-
return
|
|
134
|
+
return err.status
|
|
135
|
+
? t('settings.issueTracker.probeFailure.loadFailedStatus', {
|
|
136
|
+
status: err.status,
|
|
137
|
+
message: err.message,
|
|
138
|
+
})
|
|
139
|
+
: t('settings.issueTracker.probeFailure.loadFailed', { message: err.message })
|
|
127
140
|
})
|
|
128
141
|
|
|
129
142
|
async function checkSetup(source: TaskSourceKind) {
|
|
@@ -131,7 +144,7 @@ async function checkSetup(source: TaskSourceKind) {
|
|
|
131
144
|
await tasks.checkSetup(source)
|
|
132
145
|
} catch (e) {
|
|
133
146
|
toast.add({
|
|
134
|
-
title: '
|
|
147
|
+
title: t('settings.issueTracker.toast.checkFailed'),
|
|
135
148
|
description: e instanceof Error ? e.message : String(e),
|
|
136
149
|
icon: 'i-lucide-triangle-alert',
|
|
137
150
|
color: 'error',
|
|
@@ -163,17 +176,22 @@ const STATUS_UI: Record<
|
|
|
163
176
|
color="error"
|
|
164
177
|
variant="subtle"
|
|
165
178
|
icon="i-lucide-triangle-alert"
|
|
166
|
-
title="
|
|
179
|
+
:title="t('settings.issueTracker.probeFailure.title')"
|
|
167
180
|
:description="probeFailureHint"
|
|
168
181
|
/>
|
|
169
182
|
|
|
170
183
|
<!-- 1. Filing tracker ----------------------------------------------------->
|
|
171
184
|
<section class="space-y-3">
|
|
172
185
|
<div>
|
|
173
|
-
<h3 class="text-sm font-semibold text-slate-200">
|
|
186
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
187
|
+
{{ t('settings.issueTracker.filing.heading') }}
|
|
188
|
+
</h3>
|
|
174
189
|
<p class="mt-1 text-[11px] text-slate-400">
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
<i18n-t keypath="settings.issueTracker.filing.description" tag="span" scope="global">
|
|
191
|
+
<template #none>
|
|
192
|
+
<span class="text-slate-300">{{ t('settings.issueTracker.filing.none') }}</span>
|
|
193
|
+
</template>
|
|
194
|
+
</i18n-t>
|
|
177
195
|
</p>
|
|
178
196
|
</div>
|
|
179
197
|
|
|
@@ -185,7 +203,7 @@ const STATUS_UI: Record<
|
|
|
185
203
|
:variant="trackerKind === null ? 'solid' : 'subtle'"
|
|
186
204
|
@click="trackerKind = null"
|
|
187
205
|
>
|
|
188
|
-
|
|
206
|
+
{{ t('settings.issueTracker.filing.none') }}
|
|
189
207
|
</UButton>
|
|
190
208
|
<UButton
|
|
191
209
|
icon="i-lucide-github"
|
|
@@ -194,7 +212,7 @@ const STATUS_UI: Record<
|
|
|
194
212
|
:variant="trackerKind === 'github' ? 'solid' : 'subtle'"
|
|
195
213
|
@click="trackerKind = 'github'"
|
|
196
214
|
>
|
|
197
|
-
|
|
215
|
+
{{ t('settings.issueTracker.vendor.github') }}
|
|
198
216
|
</UButton>
|
|
199
217
|
<UButton
|
|
200
218
|
icon="i-lucide-trello"
|
|
@@ -218,36 +236,58 @@ const STATUS_UI: Record<
|
|
|
218
236
|
|
|
219
237
|
<!-- Inline readiness hints for the picked tracker. -->
|
|
220
238
|
<p v-if="trackerKind === 'github' && !githubAvailable" class="text-[11px] text-amber-400">
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
239
|
+
<i18n-t keypath="settings.issueTracker.filing.githubHint" tag="span" scope="global">
|
|
240
|
+
<template #link>
|
|
241
|
+
<button class="underline" @click="ui.openGitHub()">
|
|
242
|
+
{{ t('settings.issueTracker.filing.githubHintLink') }}
|
|
243
|
+
</button>
|
|
244
|
+
</template>
|
|
245
|
+
</i18n-t>
|
|
224
246
|
</p>
|
|
225
247
|
<p v-else-if="trackerKind === 'jira' && !jiraConnected" class="text-[11px] text-amber-400">
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
248
|
+
<i18n-t keypath="settings.issueTracker.filing.jiraHint" tag="span" scope="global">
|
|
249
|
+
<template #link>
|
|
250
|
+
<button class="underline" @click="ui.openTaskConnect('jira')">
|
|
251
|
+
{{ t('settings.issueTracker.filing.connectLink') }}
|
|
252
|
+
</button>
|
|
253
|
+
</template>
|
|
254
|
+
</i18n-t>
|
|
229
255
|
</p>
|
|
230
256
|
<p
|
|
231
257
|
v-else-if="trackerKind === 'linear' && !linearConnected"
|
|
232
258
|
class="text-[11px] text-amber-400"
|
|
233
259
|
>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
<i18n-t keypath="settings.issueTracker.filing.linearHint" tag="span" scope="global">
|
|
261
|
+
<template #link>
|
|
262
|
+
<button class="underline" @click="ui.openTaskConnect('linear')">
|
|
263
|
+
{{ t('settings.issueTracker.filing.connectLink') }}
|
|
264
|
+
</button>
|
|
265
|
+
</template>
|
|
266
|
+
</i18n-t>
|
|
237
267
|
</p>
|
|
238
268
|
|
|
239
|
-
<UFormField
|
|
269
|
+
<UFormField
|
|
270
|
+
v-if="trackerKind === 'jira'"
|
|
271
|
+
:label="t('settings.issueTracker.filing.jiraProjectKey')"
|
|
272
|
+
class="w-48"
|
|
273
|
+
>
|
|
240
274
|
<UInput v-model="jiraProjectKey" placeholder="ENG" size="sm" class="w-full" />
|
|
241
275
|
<template #help>
|
|
242
|
-
<span class="text-[11px] text-slate-500">
|
|
276
|
+
<span class="text-[11px] text-slate-500">
|
|
277
|
+
{{ t('settings.issueTracker.filing.jiraProjectKeyHelp') }}
|
|
278
|
+
</span>
|
|
243
279
|
</template>
|
|
244
280
|
</UFormField>
|
|
245
281
|
|
|
246
|
-
<UFormField
|
|
282
|
+
<UFormField
|
|
283
|
+
v-if="trackerKind === 'linear'"
|
|
284
|
+
:label="t('settings.issueTracker.filing.linearTeamId')"
|
|
285
|
+
class="w-64"
|
|
286
|
+
>
|
|
247
287
|
<UInput v-model="linearTeamId" placeholder="team_…" size="sm" class="w-full" />
|
|
248
288
|
<template #help>
|
|
249
289
|
<span class="text-[11px] text-slate-500">
|
|
250
|
-
|
|
290
|
+
{{ t('settings.issueTracker.filing.linearTeamIdHelp') }}
|
|
251
291
|
</span>
|
|
252
292
|
</template>
|
|
253
293
|
</UFormField>
|
|
@@ -256,11 +296,11 @@ const STATUS_UI: Record<
|
|
|
256
296
|
<!-- 2. Linking sources ---------------------------------------------------->
|
|
257
297
|
<section class="space-y-3">
|
|
258
298
|
<div>
|
|
259
|
-
<h3 class="text-sm font-semibold text-slate-200">
|
|
299
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
300
|
+
{{ t('settings.issueTracker.linking.heading') }}
|
|
301
|
+
</h3>
|
|
260
302
|
<p class="mt-1 text-[11px] text-slate-400">
|
|
261
|
-
|
|
262
|
-
see the issue description and comments while implementing. This is independent of the
|
|
263
|
-
filing tracker above.
|
|
303
|
+
{{ t('settings.issueTracker.linking.description') }}
|
|
264
304
|
</p>
|
|
265
305
|
</div>
|
|
266
306
|
|
|
@@ -270,12 +310,14 @@ const STATUS_UI: Record<
|
|
|
270
310
|
<div class="flex min-w-0 items-center gap-2.5">
|
|
271
311
|
<UIcon name="i-lucide-github" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
272
312
|
<div class="min-w-0">
|
|
273
|
-
<div class="text-sm font-medium text-slate-200">
|
|
313
|
+
<div class="text-sm font-medium text-slate-200">
|
|
314
|
+
{{ t('settings.issueTracker.vendor.github') }}
|
|
315
|
+
</div>
|
|
274
316
|
<div class="text-[11px] text-slate-500">
|
|
275
317
|
{{
|
|
276
318
|
githubAvailable
|
|
277
|
-
? '
|
|
278
|
-
: '
|
|
319
|
+
? t('settings.issueTracker.linking.github.available')
|
|
320
|
+
: t('settings.issueTracker.linking.github.unavailable')
|
|
279
321
|
}}
|
|
280
322
|
</div>
|
|
281
323
|
</div>
|
|
@@ -289,7 +331,7 @@ const STATUS_UI: Record<
|
|
|
289
331
|
:loading="tasks.checking === 'github'"
|
|
290
332
|
@click="checkSetup('github')"
|
|
291
333
|
>
|
|
292
|
-
|
|
334
|
+
{{ t('settings.issueTracker.linking.checkSetup') }}
|
|
293
335
|
</UButton>
|
|
294
336
|
<USwitch
|
|
295
337
|
v-if="githubAvailable"
|
|
@@ -305,7 +347,7 @@ const STATUS_UI: Record<
|
|
|
305
347
|
icon="i-lucide-github"
|
|
306
348
|
@click="ui.openGitHub()"
|
|
307
349
|
>
|
|
308
|
-
|
|
350
|
+
{{ t('settings.issueTracker.linking.install') }}
|
|
309
351
|
</UButton>
|
|
310
352
|
</div>
|
|
311
353
|
</div>
|
|
@@ -329,9 +371,15 @@ const STATUS_UI: Record<
|
|
|
329
371
|
<div class="flex min-w-0 items-center gap-2.5">
|
|
330
372
|
<UIcon name="i-lucide-trello" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
331
373
|
<div class="min-w-0">
|
|
332
|
-
<div class="text-sm font-medium text-slate-200">
|
|
374
|
+
<div class="text-sm font-medium text-slate-200">
|
|
375
|
+
{{ t('settings.issueTracker.vendor.jira') }}
|
|
376
|
+
</div>
|
|
333
377
|
<div class="text-[11px] text-slate-500">
|
|
334
|
-
{{
|
|
378
|
+
{{
|
|
379
|
+
jiraConnected
|
|
380
|
+
? t('settings.issueTracker.linking.connected')
|
|
381
|
+
: t('settings.issueTracker.linking.jira.connectHint')
|
|
382
|
+
}}
|
|
335
383
|
</div>
|
|
336
384
|
</div>
|
|
337
385
|
</div>
|
|
@@ -345,7 +393,7 @@ const STATUS_UI: Record<
|
|
|
345
393
|
:loading="tasks.checking === 'jira'"
|
|
346
394
|
@click="checkSetup('jira')"
|
|
347
395
|
>
|
|
348
|
-
|
|
396
|
+
{{ t('settings.issueTracker.linking.checkSetup') }}
|
|
349
397
|
</UButton>
|
|
350
398
|
<USwitch
|
|
351
399
|
v-if="jira?.available"
|
|
@@ -361,7 +409,7 @@ const STATUS_UI: Record<
|
|
|
361
409
|
icon="i-lucide-plug"
|
|
362
410
|
@click="ui.openTaskConnect('jira')"
|
|
363
411
|
>
|
|
364
|
-
|
|
412
|
+
{{ t('settings.issueTracker.linking.connect') }}
|
|
365
413
|
</UButton>
|
|
366
414
|
</div>
|
|
367
415
|
</div>
|
|
@@ -385,9 +433,15 @@ const STATUS_UI: Record<
|
|
|
385
433
|
<div class="flex min-w-0 items-center gap-2.5">
|
|
386
434
|
<UIcon name="i-lucide-square-kanban" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
387
435
|
<div class="min-w-0">
|
|
388
|
-
<div class="text-sm font-medium text-slate-200">
|
|
436
|
+
<div class="text-sm font-medium text-slate-200">
|
|
437
|
+
{{ t('settings.issueTracker.vendor.linear') }}
|
|
438
|
+
</div>
|
|
389
439
|
<div class="text-[11px] text-slate-500">
|
|
390
|
-
{{
|
|
440
|
+
{{
|
|
441
|
+
linearConnected
|
|
442
|
+
? t('settings.issueTracker.linking.connected')
|
|
443
|
+
: t('settings.issueTracker.linking.linear.connectHint')
|
|
444
|
+
}}
|
|
391
445
|
</div>
|
|
392
446
|
</div>
|
|
393
447
|
</div>
|
|
@@ -401,7 +455,7 @@ const STATUS_UI: Record<
|
|
|
401
455
|
:loading="tasks.checking === 'linear'"
|
|
402
456
|
@click="checkSetup('linear')"
|
|
403
457
|
>
|
|
404
|
-
|
|
458
|
+
{{ t('settings.issueTracker.linking.checkSetup') }}
|
|
405
459
|
</UButton>
|
|
406
460
|
<USwitch
|
|
407
461
|
v-if="linear?.available"
|
|
@@ -417,7 +471,7 @@ const STATUS_UI: Record<
|
|
|
417
471
|
icon="i-lucide-plug"
|
|
418
472
|
@click="ui.openTaskConnect('linear')"
|
|
419
473
|
>
|
|
420
|
-
|
|
474
|
+
{{ t('settings.issueTracker.linking.connect') }}
|
|
421
475
|
</UButton>
|
|
422
476
|
</div>
|
|
423
477
|
</div>
|
|
@@ -439,21 +493,26 @@ const STATUS_UI: Record<
|
|
|
439
493
|
<!-- 3. Writeback ---------------------------------------------------------->
|
|
440
494
|
<section class="space-y-3">
|
|
441
495
|
<div>
|
|
442
|
-
<h3 class="text-sm font-semibold text-slate-200">
|
|
496
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
497
|
+
{{ t('settings.issueTracker.writeback.heading') }}
|
|
498
|
+
</h3>
|
|
443
499
|
<p class="mt-1 text-[11px] text-slate-400">
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
500
|
+
<i18n-t keypath="settings.issueTracker.writeback.description" tag="span" scope="global">
|
|
501
|
+
<template #done>
|
|
502
|
+
<span class="text-slate-300">{{ t('settings.issueTracker.writeback.done') }}</span>
|
|
503
|
+
</template>
|
|
504
|
+
</i18n-t>
|
|
448
505
|
</p>
|
|
449
506
|
</div>
|
|
450
507
|
|
|
451
508
|
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
452
509
|
<USwitch v-model="commentOnPrOpen" />
|
|
453
510
|
<span class="text-sm">
|
|
454
|
-
<span class="block text-slate-200">
|
|
511
|
+
<span class="block text-slate-200">
|
|
512
|
+
{{ t('settings.issueTracker.writeback.commentOnOpen.label') }}
|
|
513
|
+
</span>
|
|
455
514
|
<span class="block text-xs text-slate-500">
|
|
456
|
-
|
|
515
|
+
{{ t('settings.issueTracker.writeback.commentOnOpen.help') }}
|
|
457
516
|
</span>
|
|
458
517
|
</span>
|
|
459
518
|
</label>
|
|
@@ -461,9 +520,11 @@ const STATUS_UI: Record<
|
|
|
461
520
|
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
462
521
|
<USwitch v-model="resolveOnMerge" />
|
|
463
522
|
<span class="text-sm">
|
|
464
|
-
<span class="block text-slate-200">
|
|
523
|
+
<span class="block text-slate-200">
|
|
524
|
+
{{ t('settings.issueTracker.writeback.resolveOnMerge.label') }}
|
|
525
|
+
</span>
|
|
465
526
|
<span class="block text-xs text-slate-500">
|
|
466
|
-
|
|
527
|
+
{{ t('settings.issueTracker.writeback.resolveOnMerge.help') }}
|
|
467
528
|
</span>
|
|
468
529
|
</span>
|
|
469
530
|
</label>
|
|
@@ -478,7 +539,7 @@ const STATUS_UI: Record<
|
|
|
478
539
|
:disabled="!canSave"
|
|
479
540
|
@click="save"
|
|
480
541
|
>
|
|
481
|
-
|
|
542
|
+
{{ t('common.save') }}
|
|
482
543
|
</UButton>
|
|
483
544
|
</div>
|
|
484
545
|
</div>
|