@cat-factory/app 0.9.1 → 0.11.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/board/AddTaskModal.vue +209 -21
- package/app/components/board/nodes/BlockNode.vue +2 -2
- package/app/components/focus/BlockFocusView.vue +2 -2
- package/app/components/layout/AccountTeamSettings.vue +6 -3
- package/app/components/layout/CommandBar.vue +7 -33
- package/app/components/layout/IntegrationsHub.vue +230 -0
- package/app/components/layout/SideBar.vue +8 -170
- package/app/components/panels/GenericStructuredResultView.vue +131 -0
- package/app/components/panels/InspectorPanel.vue +6 -2
- package/app/components/panels/StepResultViewHost.vue +4 -0
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +148 -0
- package/app/components/providers/ApiKeysSection.vue +67 -26
- package/app/components/providers/VendorCredentialsModal.vue +115 -84
- package/app/components/settings/IssueTrackerWritebackPanel.vue +45 -57
- package/app/components/settings/MergeThresholdsPanel.vue +189 -226
- package/app/components/settings/ObservabilityConnectionPanel.vue +151 -0
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +46 -61
- package/app/components/settings/WorkspaceSettingsPanel.vue +136 -63
- package/app/composables/api/releaseHealth.ts +11 -10
- package/app/pages/index.vue +4 -8
- package/app/stores/agents.ts +27 -2
- package/app/stores/releaseHealth.ts +48 -12
- package/app/stores/ui.ts +34 -42
- package/app/stores/workspace.ts +4 -0
- package/app/types/domain.ts +33 -1
- package/app/types/execution.ts +6 -0
- package/app/types/releaseHealth.ts +19 -11
- package/app/utils/catalog.spec.ts +10 -0
- package/app/utils/catalog.ts +20 -6
- package/package.json +1 -1
- package/app/components/board/ContextPicker.vue +0 -367
- package/app/components/settings/DatadogPanel.vue +0 -213
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Provider API keys: connect a vendor API key so agent steps and inline calls run on
|
|
3
|
+
// that provider. Keys are stored encrypted in the DB, pooled and rotated by usage —
|
|
4
|
+
// replacing deployment env vars.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// `category` splits the providers into two kinds:
|
|
7
|
+
// - 'direct' (default): you reach the vendor directly (OpenAI/Anthropic/Qwen/DeepSeek/
|
|
8
|
+
// Moonshot).
|
|
9
|
+
// - 'proxy': an intermediary gateway that fronts many vendors behind one key
|
|
10
|
+
// (OpenRouter, LiteLLM). These are NOT direct vendors, so they get their own section.
|
|
11
|
+
//
|
|
12
|
+
// Two scopes:
|
|
7
13
|
// - Default (no `accountId`): manage WORKSPACE keys (shared by the team) and YOUR own
|
|
8
14
|
// keys (your personal pool, usable in any workspace), toggled by the Scope select.
|
|
9
15
|
// - With `accountId`: manage ACCOUNT-wide keys (shared by every workspace in the
|
|
@@ -11,7 +17,9 @@
|
|
|
11
17
|
import { computed, ref, watch } from 'vue'
|
|
12
18
|
import type { ApiKey, ApiKeyProvider } from '~/types/domain'
|
|
13
19
|
|
|
14
|
-
const props = defineProps<{ accountId?: string }>()
|
|
20
|
+
const props = withDefaults(defineProps<{ accountId?: string; category?: 'direct' | 'proxy' }>(), {
|
|
21
|
+
category: 'direct',
|
|
22
|
+
})
|
|
15
23
|
|
|
16
24
|
const workspace = useWorkspaceStore()
|
|
17
25
|
const keys = useApiKeysStore()
|
|
@@ -21,13 +29,15 @@ const toast = useToast()
|
|
|
21
29
|
/** Account-wide mode (single account scope) vs the default workspace/user toggle. */
|
|
22
30
|
const isAccount = computed(() => !!props.accountId)
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
const PROVIDERS: {
|
|
32
|
+
interface ProviderMeta {
|
|
26
33
|
value: ApiKeyProvider
|
|
27
34
|
label: string
|
|
28
35
|
url: string
|
|
29
36
|
steps: string[]
|
|
30
|
-
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Direct vendors: the key reaches that one vendor's own endpoint. */
|
|
40
|
+
const DIRECT_PROVIDERS: ProviderMeta[] = [
|
|
31
41
|
{
|
|
32
42
|
value: 'openai',
|
|
33
43
|
label: 'OpenAI',
|
|
@@ -73,6 +83,10 @@ const PROVIDERS: {
|
|
|
73
83
|
'Copy the key; it authenticates the OpenAI-compatible Moonshot endpoint.',
|
|
74
84
|
],
|
|
75
85
|
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
/** Proxies / gateways: one key fronts many vendors. These are intermediaries, not vendors. */
|
|
89
|
+
const PROXY_PROVIDERS: ProviderMeta[] = [
|
|
76
90
|
{
|
|
77
91
|
value: 'openrouter',
|
|
78
92
|
label: 'OpenRouter',
|
|
@@ -93,8 +107,12 @@ const PROVIDERS: {
|
|
|
93
107
|
},
|
|
94
108
|
]
|
|
95
109
|
|
|
110
|
+
/** Providers for the requested category; labels everywhere fall back to the full set. */
|
|
111
|
+
const PROVIDERS = computed(() => (props.category === 'proxy' ? PROXY_PROVIDERS : DIRECT_PROVIDERS))
|
|
112
|
+
const ALL_PROVIDERS = [...DIRECT_PROVIDERS, ...PROXY_PROVIDERS]
|
|
113
|
+
|
|
96
114
|
const scope = ref<'workspace' | 'user'>('workspace')
|
|
97
|
-
const provider = ref<ApiKeyProvider>('openai')
|
|
115
|
+
const provider = ref<ApiKeyProvider>(props.category === 'proxy' ? 'openrouter' : 'openai')
|
|
98
116
|
const label = ref('')
|
|
99
117
|
const key = ref('')
|
|
100
118
|
const busy = ref(false)
|
|
@@ -115,17 +133,23 @@ watch(
|
|
|
115
133
|
{ immediate: true },
|
|
116
134
|
)
|
|
117
135
|
|
|
118
|
-
const selected = computed(
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
const selected = computed(
|
|
137
|
+
() => PROVIDERS.value.find((p) => p.value === provider.value) ?? PROVIDERS.value[0]!,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
/** Keys for the active scope, narrowed to this section's category (direct vs proxy). */
|
|
141
|
+
const categoryProviders = computed(() => new Set(PROVIDERS.value.map((p) => p.value)))
|
|
142
|
+
const connected = computed<ApiKey[]>(() => {
|
|
143
|
+
const all = isAccount.value
|
|
121
144
|
? keys.accountKeys
|
|
122
145
|
: scope.value === 'workspace'
|
|
123
146
|
? keys.workspaceKeys
|
|
124
|
-
: keys.userKeys
|
|
125
|
-
)
|
|
147
|
+
: keys.userKeys
|
|
148
|
+
return all.filter((k) => categoryProviders.value.has(k.provider))
|
|
149
|
+
})
|
|
126
150
|
|
|
127
151
|
function providerLabel(p: ApiKeyProvider): string {
|
|
128
|
-
return
|
|
152
|
+
return ALL_PROVIDERS.find((x) => x.value === p)?.label ?? p
|
|
129
153
|
}
|
|
130
154
|
|
|
131
155
|
async function add() {
|
|
@@ -176,17 +200,34 @@ async function remove(k: ApiKey) {
|
|
|
176
200
|
<div class="space-y-4">
|
|
177
201
|
<div>
|
|
178
202
|
<h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
179
|
-
Direct provider API keys
|
|
203
|
+
{{ category === 'proxy' ? 'Proxy / gateway API keys' : 'Direct provider API keys' }}
|
|
180
204
|
</h4>
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
<template v-if="category === 'proxy'">
|
|
206
|
+
<p v-if="isAccount" class="mt-1 text-sm text-slate-400">
|
|
207
|
+
Connect a <strong>proxy</strong> key (OpenRouter, LiteLLM) shared by
|
|
208
|
+
<strong>every workspace</strong> in this account. A proxy is an intermediary that fronts
|
|
209
|
+
many vendors behind a single key. Keys are stored encrypted, pooled, and rotated by usage.
|
|
210
|
+
</p>
|
|
211
|
+
<p v-else class="mt-1 text-sm text-slate-400">
|
|
212
|
+
Connect a <strong>proxy</strong> key (OpenRouter, LiteLLM). A proxy is an intermediary
|
|
213
|
+
that reaches many vendors' models through a single gateway, rather than one vendor
|
|
214
|
+
directly. Keys are stored encrypted, pooled, and rotated by usage. Scope a key to this
|
|
215
|
+
<strong>workspace</strong> (shared with the team) or to <strong>you</strong> (your own
|
|
216
|
+
pool, usable anywhere).
|
|
217
|
+
</p>
|
|
218
|
+
</template>
|
|
219
|
+
<template v-else>
|
|
220
|
+
<p v-if="isAccount" class="mt-1 text-sm text-slate-400">
|
|
221
|
+
Connect a vendor API key shared by <strong>every workspace</strong> in this account. Keys
|
|
222
|
+
are stored encrypted, pooled, and rotated by usage. Account keys are admin-managed.
|
|
223
|
+
</p>
|
|
224
|
+
<p v-else class="mt-1 text-sm text-slate-400">
|
|
225
|
+
Connect a vendor API key so models run directly on that provider. Keys are stored
|
|
226
|
+
encrypted, pooled, and rotated by usage. Scope a key to this
|
|
227
|
+
<strong>workspace</strong> (shared with the team) or to <strong>you</strong> (your own
|
|
228
|
+
pool, usable anywhere).
|
|
229
|
+
</p>
|
|
230
|
+
</template>
|
|
190
231
|
</div>
|
|
191
232
|
|
|
192
233
|
<!-- scope + provider -->
|
|
@@ -201,7 +242,7 @@ async function remove(k: ApiKey) {
|
|
|
201
242
|
class="w-48"
|
|
202
243
|
/>
|
|
203
244
|
</UFormField>
|
|
204
|
-
<UFormField label="Provider">
|
|
245
|
+
<UFormField :label="category === 'proxy' ? 'Proxy' : 'Provider'">
|
|
205
246
|
<USelect
|
|
206
247
|
v-model="provider"
|
|
207
248
|
:items="PROVIDERS.map((p) => ({ label: p.label, value: p.value }))"
|
|
@@ -18,6 +18,21 @@ const open = computed({
|
|
|
18
18
|
set: (v: boolean) => (v ? ui.openVendorCredentials() : ui.closeVendorCredentials()),
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
// Horizontal tabs replace the old long vertical scroll: each credential kind is its own
|
|
22
|
+
// section (pooled subscriptions, direct vendor keys, proxy/gateway keys, personal subs).
|
|
23
|
+
const activeTab = ref('pool')
|
|
24
|
+
const tabs = [
|
|
25
|
+
{ value: 'pool', label: 'Workspace pool', icon: 'i-lucide-users', slot: 'pool' },
|
|
26
|
+
{ value: 'direct', label: 'Direct providers', icon: 'i-lucide-key-round', slot: 'direct' },
|
|
27
|
+
{ value: 'proxy', label: 'Proxies', icon: 'i-lucide-route', slot: 'proxy' },
|
|
28
|
+
{
|
|
29
|
+
value: 'personal',
|
|
30
|
+
label: 'Personal subscriptions',
|
|
31
|
+
icon: 'i-lucide-user',
|
|
32
|
+
slot: 'personal',
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
21
36
|
// Only commercial coding-plan vendors that permit team/organization use are poolable here.
|
|
22
37
|
// Claude, GLM and ChatGPT/Codex are licensed for individual use only, so they are connected
|
|
23
38
|
// per-user in the "Personal subscriptions" section below (PersonalSubscriptionSection).
|
|
@@ -102,96 +117,112 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
102
117
|
<template>
|
|
103
118
|
<UModal v-model:open="open" title="LLM Vendors" :ui="{ content: 'max-w-2xl' }">
|
|
104
119
|
<template #body>
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<div>
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
120
|
+
<UTabs
|
|
121
|
+
v-model="activeTab"
|
|
122
|
+
:items="tabs"
|
|
123
|
+
variant="link"
|
|
124
|
+
:ui="{ root: 'gap-4', list: 'overflow-x-auto' }"
|
|
125
|
+
>
|
|
126
|
+
<!-- Workspace pool (commercial coding-plan subscriptions) -->
|
|
127
|
+
<template #pool>
|
|
128
|
+
<div class="space-y-5">
|
|
129
|
+
<p class="text-sm text-slate-400">
|
|
130
|
+
Connect a <strong>commercial</strong> coding-plan subscription (Kimi, DeepSeek) that
|
|
131
|
+
permits team/organization use to run agent steps on the Claude Code harness instead of
|
|
132
|
+
an API key. Tokens are stored encrypted, pooled, and rotated by usage. Subscription
|
|
133
|
+
models are flat-rate quota — they don’t draw on your spend budget. Individual-use
|
|
134
|
+
subscriptions (Claude, GLM, ChatGPT/Codex) are connected per-user in the Personal
|
|
135
|
+
subscriptions tab.
|
|
136
|
+
</p>
|
|
137
|
+
|
|
138
|
+
<!-- vendor picker -->
|
|
139
|
+
<div class="flex flex-wrap items-end gap-3">
|
|
140
|
+
<UFormField label="Vendor">
|
|
141
|
+
<USelect
|
|
142
|
+
v-model="vendor"
|
|
143
|
+
:items="visibleVendors.map((v) => ({ label: v.label, value: v.value }))"
|
|
144
|
+
class="w-64"
|
|
145
|
+
/>
|
|
146
|
+
</UFormField>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- guided steps -->
|
|
150
|
+
<ol
|
|
151
|
+
class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 pl-8 text-sm text-slate-300"
|
|
152
|
+
>
|
|
153
|
+
<li v-for="(step, i) in steps" :key="i">{{ step }}</li>
|
|
154
|
+
</ol>
|
|
155
|
+
|
|
156
|
+
<!-- add form -->
|
|
157
|
+
<div class="space-y-2">
|
|
158
|
+
<UFormField label="Label (optional)">
|
|
159
|
+
<UInput v-model="label" placeholder="e.g. work account" />
|
|
160
|
+
</UFormField>
|
|
161
|
+
<UFormField label="Token">
|
|
162
|
+
<UTextarea
|
|
163
|
+
v-model="token"
|
|
164
|
+
:rows="3"
|
|
165
|
+
:placeholder="tokenPlaceholder"
|
|
166
|
+
class="font-mono"
|
|
167
|
+
/>
|
|
168
|
+
</UFormField>
|
|
169
|
+
<div class="flex justify-end">
|
|
170
|
+
<UButton
|
|
171
|
+
:loading="busy"
|
|
172
|
+
:disabled="!token.trim()"
|
|
173
|
+
icon="i-lucide-plus"
|
|
174
|
+
@click="add()"
|
|
175
|
+
>
|
|
176
|
+
Connect
|
|
177
|
+
</UButton>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<!-- connected pool -->
|
|
182
|
+
<div v-if="creds.credentials.length" class="space-y-2">
|
|
183
|
+
<h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
184
|
+
Connected ({{ creds.credentials.length }})
|
|
185
|
+
</h4>
|
|
186
|
+
<div
|
|
187
|
+
v-for="c in creds.credentials"
|
|
188
|
+
:key="c.id"
|
|
189
|
+
class="flex items-center justify-between rounded-md border border-slate-700 bg-slate-900/50 px-3 py-2 text-sm"
|
|
190
|
+
>
|
|
191
|
+
<div>
|
|
192
|
+
<span class="font-medium text-slate-200">{{ c.label }}</span>
|
|
193
|
+
<span class="ml-2 text-xs text-slate-500">{{ vendorLabel(c.vendor) }}</span>
|
|
194
|
+
<div class="text-[11px] tabular-nums text-slate-500">
|
|
195
|
+
{{ (c.inputTokens + c.outputTokens).toLocaleString() }} tok this window ·
|
|
196
|
+
{{ c.requestCount }} run{{ c.requestCount === 1 ? '' : 's' }}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<UButton
|
|
200
|
+
icon="i-lucide-trash-2"
|
|
201
|
+
color="error"
|
|
202
|
+
variant="ghost"
|
|
203
|
+
size="xs"
|
|
204
|
+
@click="remove(c.id)"
|
|
205
|
+
/>
|
|
173
206
|
</div>
|
|
174
207
|
</div>
|
|
175
|
-
<UButton
|
|
176
|
-
icon="i-lucide-trash-2"
|
|
177
|
-
color="error"
|
|
178
|
-
variant="ghost"
|
|
179
|
-
size="xs"
|
|
180
|
-
@click="remove(c.id)"
|
|
181
|
-
/>
|
|
182
208
|
</div>
|
|
183
|
-
</
|
|
209
|
+
</template>
|
|
210
|
+
|
|
211
|
+
<!-- Direct provider API keys (OpenAI/Anthropic/Qwen/DeepSeek/Moonshot), pooled -->
|
|
212
|
+
<template #direct>
|
|
213
|
+
<ProvidersApiKeysSection category="direct" />
|
|
214
|
+
</template>
|
|
184
215
|
|
|
185
|
-
<!--
|
|
186
|
-
<
|
|
187
|
-
<ProvidersApiKeysSection />
|
|
188
|
-
</
|
|
216
|
+
<!-- Proxies / gateways (OpenRouter, LiteLLM): intermediaries that front many vendors -->
|
|
217
|
+
<template #proxy>
|
|
218
|
+
<ProvidersApiKeysSection category="proxy" />
|
|
219
|
+
</template>
|
|
189
220
|
|
|
190
|
-
<!--
|
|
191
|
-
<
|
|
221
|
+
<!-- Personal (individual-usage) subscriptions: Claude / GLM / Codex, per-user -->
|
|
222
|
+
<template #personal>
|
|
192
223
|
<ProvidersPersonalSubscriptionSection />
|
|
193
|
-
</
|
|
194
|
-
</
|
|
224
|
+
</template>
|
|
225
|
+
</UTabs>
|
|
195
226
|
</template>
|
|
196
227
|
</UModal>
|
|
197
228
|
</template>
|
|
@@ -4,27 +4,23 @@
|
|
|
4
4
|
// progresses — comment when the PR opens, and comment + close as resolved when it
|
|
5
5
|
// merges. Each is overridable per task in the inspector. Persisted on the workspace
|
|
6
6
|
// tracker settings (the selection + Jira project key are preserved on save).
|
|
7
|
-
import { ref, watch } from 'vue'
|
|
7
|
+
import { onMounted, ref, watch } from 'vue'
|
|
8
8
|
|
|
9
|
-
const ui = useUiStore()
|
|
10
9
|
const tracker = useTrackerStore()
|
|
11
10
|
const toast = useToast()
|
|
12
11
|
|
|
13
|
-
const open = computed({
|
|
14
|
-
get: () => ui.issueWritebackOpen,
|
|
15
|
-
set: (v: boolean) => (v ? ui.openIssueWriteback() : ui.closeIssueWriteback()),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
12
|
const commentOnPrOpen = ref(false)
|
|
19
13
|
const resolveOnMerge = ref(false)
|
|
20
14
|
const saving = ref(false)
|
|
21
15
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
// Sync the local toggles from the store on mount (the tab renders when Workspace
|
|
17
|
+
// settings opens) and whenever the stored settings change underneath.
|
|
18
|
+
function hydrate() {
|
|
25
19
|
commentOnPrOpen.value = tracker.settings.writebackCommentOnPrOpen
|
|
26
20
|
resolveOnMerge.value = tracker.settings.writebackResolveOnMerge
|
|
27
|
-
}
|
|
21
|
+
}
|
|
22
|
+
onMounted(hydrate)
|
|
23
|
+
watch(() => tracker.settings, hydrate, { deep: true })
|
|
28
24
|
|
|
29
25
|
async function save() {
|
|
30
26
|
saving.value = true
|
|
@@ -51,53 +47,45 @@ async function save() {
|
|
|
51
47
|
</script>
|
|
52
48
|
|
|
53
49
|
<template>
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
first status in their <span class="text-slate-300">Done</span> category.
|
|
62
|
-
</p>
|
|
50
|
+
<div class="space-y-4">
|
|
51
|
+
<p class="text-xs text-slate-400">
|
|
52
|
+
When a task is linked to a tracker issue (GitHub Issues or Jira), write back to it as the
|
|
53
|
+
task's pull request progresses. Each toggle is the workspace default and can be overridden per
|
|
54
|
+
task in the inspector. GitHub issues close natively; Jira issues transition to the first
|
|
55
|
+
status in their <span class="text-slate-300">Done</span> category.
|
|
56
|
+
</p>
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
</span>
|
|
74
|
-
</label>
|
|
58
|
+
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
59
|
+
<USwitch v-model="commentOnPrOpen" />
|
|
60
|
+
<span class="text-sm">
|
|
61
|
+
<span class="block text-slate-200">Comment when a PR opens</span>
|
|
62
|
+
<span class="block text-xs text-slate-500">
|
|
63
|
+
Post a comment on the linked issue with the new pull request's link.
|
|
64
|
+
</span>
|
|
65
|
+
</span>
|
|
66
|
+
</label>
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</span>
|
|
86
|
-
</label>
|
|
68
|
+
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
69
|
+
<USwitch v-model="resolveOnMerge" />
|
|
70
|
+
<span class="text-sm">
|
|
71
|
+
<span class="block text-slate-200">Close as resolved when a PR merges</span>
|
|
72
|
+
<span class="block text-xs text-slate-500">
|
|
73
|
+
Comment that the PR merged, then close / resolve the linked issue.
|
|
74
|
+
</span>
|
|
75
|
+
</span>
|
|
76
|
+
</label>
|
|
87
77
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</template>
|
|
102
|
-
</UModal>
|
|
78
|
+
<div class="flex justify-end">
|
|
79
|
+
<UButton
|
|
80
|
+
color="primary"
|
|
81
|
+
variant="soft"
|
|
82
|
+
size="sm"
|
|
83
|
+
icon="i-lucide-save"
|
|
84
|
+
:loading="saving"
|
|
85
|
+
@click="save"
|
|
86
|
+
>
|
|
87
|
+
Save
|
|
88
|
+
</UButton>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
103
91
|
</template>
|