@cat-factory/app 0.76.0 → 0.78.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/TaskDependencyEdges.vue +64 -0
- package/app/components/layout/IntegrationsHub.vue +23 -0
- package/app/components/panels/AgentStepDetail.vue +41 -0
- package/app/components/panels/InspectorPanel.vue +4 -0
- package/app/components/panels/inspector/FrontendBindingsResolved.vue +111 -0
- package/app/components/panels/inspector/FrontendConfig.vue +8 -0
- package/app/components/panels/inspector/ServiceConnections.vue +151 -0
- package/app/components/panels/inspector/TaskRunSettings.vue +66 -0
- package/app/components/settings/PackageRegistriesPanel.vue +222 -0
- package/app/composables/api/environments.ts +10 -0
- package/app/composables/api/packageRegistries.ts +24 -0
- package/app/composables/useApi.ts +4 -0
- package/app/pages/index.vue +4 -0
- package/app/stores/environments.ts +52 -0
- package/app/stores/packageRegistries.ts +66 -0
- package/app/stores/ui.ts +13 -0
- package/app/types/domain.ts +3 -0
- package/app/types/packageRegistries.ts +13 -0
- package/i18n/locales/en.json +49 -1
- package/i18n/locales/es.json +49 -1
- package/i18n/locales/fr.json +49 -1
- package/i18n/locales/he.json +49 -1
- package/i18n/locales/ja.json +49 -1
- package/i18n/locales/pl.json +49 -1
- package/i18n/locales/tr.json +49 -1
- package/i18n/locales/uk.json +49 -1
- package/package.json +2 -2
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Private package registries — the workspace's npm-registry entries (npm private
|
|
3
|
+
// orgs, GitHub Packages) that agent containers use to resolve private dependencies
|
|
4
|
+
// on checkout. Tokens are write-only: the list renders from the redacted summary
|
|
5
|
+
// (vendor + scopes + token tail) and an entry is edited by deleting + re-adding.
|
|
6
|
+
// Opened from the Integrations hub.
|
|
7
|
+
import { computed, reactive, ref, watch } from 'vue'
|
|
8
|
+
import type { PackageRegistryVendor } from '~/types/packageRegistries'
|
|
9
|
+
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
10
|
+
|
|
11
|
+
const { t } = useI18n()
|
|
12
|
+
const ui = useUiStore()
|
|
13
|
+
const store = usePackageRegistriesStore()
|
|
14
|
+
const toast = useToast()
|
|
15
|
+
const { confirmAction, toastDone } = useConfirmAction()
|
|
16
|
+
|
|
17
|
+
const open = computed({
|
|
18
|
+
get: () => ui.packageRegistriesOpen,
|
|
19
|
+
set: (v: boolean) => (v ? ui.openPackageRegistries() : ui.closePackageRegistries()),
|
|
20
|
+
})
|
|
21
|
+
const back = useIntegrationBack(open)
|
|
22
|
+
|
|
23
|
+
// The registry vendors a workspace can connect. Fixed set — the host derives from the
|
|
24
|
+
// vendor server-side, so it renders read-only here. Vendor names stay verbatim.
|
|
25
|
+
const VENDORS: { value: PackageRegistryVendor; label: string; host: string }[] = [
|
|
26
|
+
{ value: 'npmjs', label: 'npm (npmjs.com)', host: 'registry.npmjs.org' },
|
|
27
|
+
{ value: 'github-packages', label: 'GitHub Packages', host: 'npm.pkg.github.com' },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const form = reactive({
|
|
31
|
+
vendor: 'npmjs' as PackageRegistryVendor,
|
|
32
|
+
scopes: '',
|
|
33
|
+
token: '',
|
|
34
|
+
})
|
|
35
|
+
const busy = ref(false)
|
|
36
|
+
|
|
37
|
+
const vendorHost = computed(() => VENDORS.find((v) => v.value === form.vendor)?.host ?? '')
|
|
38
|
+
|
|
39
|
+
function vendorLabel(vendor: PackageRegistryVendor): string {
|
|
40
|
+
return VENDORS.find((v) => v.value === vendor)?.label ?? vendor
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse the comma/space-separated scopes input into `@org` entries. */
|
|
44
|
+
const parsedScopes = computed(() =>
|
|
45
|
+
form.scopes
|
|
46
|
+
.split(/[\s,]+/)
|
|
47
|
+
.map((s) => s.trim())
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.map((s) => (s.startsWith('@') ? s : `@${s}`)),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
function notifyError(title: string, e: unknown) {
|
|
53
|
+
toast.add({
|
|
54
|
+
title,
|
|
55
|
+
description: e instanceof Error ? e.message : String(e),
|
|
56
|
+
icon: 'i-lucide-triangle-alert',
|
|
57
|
+
color: 'error',
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
watch(
|
|
62
|
+
open,
|
|
63
|
+
async (isOpen) => {
|
|
64
|
+
if (!isOpen) return
|
|
65
|
+
try {
|
|
66
|
+
await store.ensureLoaded()
|
|
67
|
+
} catch (e) {
|
|
68
|
+
notifyError(t('settings.packageRegistries.toast.loadFailed'), e)
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{ immediate: true },
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async function addEntry() {
|
|
75
|
+
busy.value = true
|
|
76
|
+
try {
|
|
77
|
+
await store.add({
|
|
78
|
+
ecosystem: 'npm',
|
|
79
|
+
vendor: form.vendor,
|
|
80
|
+
scopes: parsedScopes.value,
|
|
81
|
+
token: form.token.trim(),
|
|
82
|
+
})
|
|
83
|
+
form.scopes = ''
|
|
84
|
+
form.token = ''
|
|
85
|
+
toast.add({
|
|
86
|
+
title: t('settings.packageRegistries.toast.added'),
|
|
87
|
+
icon: 'i-lucide-check',
|
|
88
|
+
color: 'success',
|
|
89
|
+
})
|
|
90
|
+
} catch (e) {
|
|
91
|
+
notifyError(t('settings.packageRegistries.toast.addFailed'), e)
|
|
92
|
+
} finally {
|
|
93
|
+
busy.value = false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function removeEntry(entryId: string) {
|
|
98
|
+
const noun = t('settings.packageRegistries.entryNoun')
|
|
99
|
+
if (!(await confirmAction('remove', noun))) return
|
|
100
|
+
busy.value = true
|
|
101
|
+
try {
|
|
102
|
+
await store.remove(entryId)
|
|
103
|
+
toastDone('remove', noun)
|
|
104
|
+
} catch (e) {
|
|
105
|
+
notifyError(t('settings.packageRegistries.toast.removeFailed'), e)
|
|
106
|
+
} finally {
|
|
107
|
+
busy.value = false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<template>
|
|
113
|
+
<UModal
|
|
114
|
+
v-model:open="open"
|
|
115
|
+
:title="t('settings.packageRegistries.title')"
|
|
116
|
+
:ui="{ content: 'max-w-lg' }"
|
|
117
|
+
>
|
|
118
|
+
<template #title>
|
|
119
|
+
<IntegrationBackTitle :title="t('settings.packageRegistries.title')" @back="back" />
|
|
120
|
+
</template>
|
|
121
|
+
<template #body>
|
|
122
|
+
<div class="space-y-4" data-testid="package-registries-panel">
|
|
123
|
+
<p class="text-sm text-slate-400">
|
|
124
|
+
{{ t('settings.packageRegistries.intro') }}
|
|
125
|
+
</p>
|
|
126
|
+
|
|
127
|
+
<section
|
|
128
|
+
v-if="store.entries.length"
|
|
129
|
+
class="space-y-2 rounded-lg border border-slate-700 p-3"
|
|
130
|
+
>
|
|
131
|
+
<h3 class="text-sm font-semibold">
|
|
132
|
+
{{ t('settings.packageRegistries.list.heading') }}
|
|
133
|
+
</h3>
|
|
134
|
+
<div
|
|
135
|
+
v-for="entry in store.entries"
|
|
136
|
+
:key="entry.id"
|
|
137
|
+
class="flex items-center justify-between gap-2 rounded-md border border-slate-800 px-3 py-2"
|
|
138
|
+
>
|
|
139
|
+
<div class="min-w-0 space-y-1">
|
|
140
|
+
<div class="flex items-center gap-2">
|
|
141
|
+
<span class="text-sm font-medium">{{ vendorLabel(entry.vendor) }}</span>
|
|
142
|
+
<span class="text-[11px] text-slate-500">
|
|
143
|
+
{{ t('settings.packageRegistries.list.tokenTail', { tail: entry.tokenTail }) }}
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="flex flex-wrap gap-1">
|
|
147
|
+
<UBadge
|
|
148
|
+
v-for="scope in entry.scopes"
|
|
149
|
+
:key="scope"
|
|
150
|
+
color="neutral"
|
|
151
|
+
variant="soft"
|
|
152
|
+
size="sm"
|
|
153
|
+
>
|
|
154
|
+
{{ scope }}
|
|
155
|
+
</UBadge>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<UButton
|
|
159
|
+
color="error"
|
|
160
|
+
variant="ghost"
|
|
161
|
+
icon="i-lucide-trash-2"
|
|
162
|
+
size="sm"
|
|
163
|
+
:loading="busy"
|
|
164
|
+
:data-testid="`package-registry-delete-${entry.id}`"
|
|
165
|
+
:aria-label="t('settings.packageRegistries.list.remove')"
|
|
166
|
+
@click="removeEntry(entry.id)"
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
</section>
|
|
170
|
+
|
|
171
|
+
<section class="space-y-3 rounded-lg border border-slate-700 p-3">
|
|
172
|
+
<h3 class="text-sm font-semibold">
|
|
173
|
+
{{ t('settings.packageRegistries.add.heading') }}
|
|
174
|
+
</h3>
|
|
175
|
+
|
|
176
|
+
<UFormField :label="t('settings.packageRegistries.add.vendor')">
|
|
177
|
+
<USelect
|
|
178
|
+
v-model="form.vendor"
|
|
179
|
+
:items="VENDORS"
|
|
180
|
+
value-key="value"
|
|
181
|
+
class="w-full"
|
|
182
|
+
data-testid="package-registry-vendor"
|
|
183
|
+
/>
|
|
184
|
+
</UFormField>
|
|
185
|
+
<p class="text-[11px] text-slate-500">
|
|
186
|
+
{{ t('settings.packageRegistries.add.host', { host: vendorHost }) }}
|
|
187
|
+
</p>
|
|
188
|
+
|
|
189
|
+
<UFormField
|
|
190
|
+
:label="t('settings.packageRegistries.add.scopes')"
|
|
191
|
+
:help="t('settings.packageRegistries.add.scopesHelp')"
|
|
192
|
+
>
|
|
193
|
+
<UInput
|
|
194
|
+
v-model="form.scopes"
|
|
195
|
+
placeholder="@my-org, @my-other-org"
|
|
196
|
+
class="w-full"
|
|
197
|
+
data-testid="package-registry-scopes"
|
|
198
|
+
/>
|
|
199
|
+
</UFormField>
|
|
200
|
+
|
|
201
|
+
<UFormField :label="t('settings.packageRegistries.add.token')">
|
|
202
|
+
<UInput
|
|
203
|
+
v-model="form.token"
|
|
204
|
+
type="password"
|
|
205
|
+
class="w-full"
|
|
206
|
+
data-testid="package-registry-token"
|
|
207
|
+
/>
|
|
208
|
+
</UFormField>
|
|
209
|
+
|
|
210
|
+
<UButton
|
|
211
|
+
:loading="busy"
|
|
212
|
+
:disabled="!parsedScopes.length || !form.token.trim()"
|
|
213
|
+
data-testid="package-registry-save"
|
|
214
|
+
@click="addEntry"
|
|
215
|
+
>
|
|
216
|
+
{{ t('settings.packageRegistries.add.save') }}
|
|
217
|
+
</UButton>
|
|
218
|
+
</section>
|
|
219
|
+
</div>
|
|
220
|
+
</template>
|
|
221
|
+
</UModal>
|
|
222
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { listEnvironmentsContract } from '@cat-factory/contracts'
|
|
2
|
+
import type { ApiContext } from './context'
|
|
3
|
+
|
|
4
|
+
/** Ephemeral environments: the workspace's live env handles (used to resolve frontend bindings). */
|
|
5
|
+
export function environmentsApi({ send, ws }: ApiContext) {
|
|
6
|
+
return {
|
|
7
|
+
listEnvironments: (workspaceId: string) =>
|
|
8
|
+
send(listEnvironmentsContract, { pathPrefix: ws(workspaceId) }),
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addPackageRegistryContract,
|
|
3
|
+
deletePackageRegistryContract,
|
|
4
|
+
listPackageRegistriesContract,
|
|
5
|
+
} from '@cat-factory/contracts'
|
|
6
|
+
import type { AddPackageRegistryInput } from '~/types/packageRegistries'
|
|
7
|
+
import type { ApiContext } from './context'
|
|
8
|
+
|
|
9
|
+
/** Private package registries: the workspace's entries agent containers install with. */
|
|
10
|
+
export function packageRegistriesApi({ send, ws }: ApiContext) {
|
|
11
|
+
return {
|
|
12
|
+
listPackageRegistries: (workspaceId: string) =>
|
|
13
|
+
send(listPackageRegistriesContract, { pathPrefix: ws(workspaceId) }),
|
|
14
|
+
|
|
15
|
+
addPackageRegistry: (workspaceId: string, body: AddPackageRegistryInput) =>
|
|
16
|
+
send(addPackageRegistryContract, { pathPrefix: ws(workspaceId), body }),
|
|
17
|
+
|
|
18
|
+
deletePackageRegistry: (workspaceId: string, entryId: string) =>
|
|
19
|
+
send(deletePackageRegistryContract, {
|
|
20
|
+
pathPrefix: ws(workspaceId),
|
|
21
|
+
pathParams: { entryId },
|
|
22
|
+
}),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -18,11 +18,13 @@ import { kaizenApi } from './api/kaizen'
|
|
|
18
18
|
import { localSettingsApi } from './api/localSettings'
|
|
19
19
|
import { modelsApi } from './api/models'
|
|
20
20
|
import { notificationsApi } from './api/notifications'
|
|
21
|
+
import { packageRegistriesApi } from './api/packageRegistries'
|
|
21
22
|
import { presetsApi } from './api/presets'
|
|
22
23
|
import { providerConnectionsApi } from './api/providerConnections'
|
|
23
24
|
import { provisioningLogsApi } from './api/provisioningLogs'
|
|
24
25
|
import { recurringApi } from './api/recurring'
|
|
25
26
|
import { previewApi } from './api/preview'
|
|
27
|
+
import { environmentsApi } from './api/environments'
|
|
26
28
|
import { releaseHealthApi } from './api/releaseHealth'
|
|
27
29
|
import { sandboxApi } from './api/sandbox'
|
|
28
30
|
import { reviewsApi } from './api/reviews'
|
|
@@ -112,7 +114,9 @@ export function useApi() {
|
|
|
112
114
|
...infraHandlersApi(ctx),
|
|
113
115
|
...provisioningLogsApi(ctx),
|
|
114
116
|
...releaseHealthApi(ctx),
|
|
117
|
+
...packageRegistriesApi(ctx),
|
|
115
118
|
...previewApi(ctx),
|
|
119
|
+
...environmentsApi(ctx),
|
|
116
120
|
...recurringApi(ctx),
|
|
117
121
|
...sandboxApi(ctx),
|
|
118
122
|
...githubApi(ctx),
|
package/app/pages/index.vue
CHANGED
|
@@ -85,6 +85,9 @@ const AccountSettingsPanel = defineAsyncComponent(
|
|
|
85
85
|
const ObservabilityConnectionPanel = defineAsyncComponent(
|
|
86
86
|
() => import('~/components/settings/ObservabilityConnectionPanel.vue'),
|
|
87
87
|
)
|
|
88
|
+
const PackageRegistriesPanel = defineAsyncComponent(
|
|
89
|
+
() => import('~/components/settings/PackageRegistriesPanel.vue'),
|
|
90
|
+
)
|
|
88
91
|
const InfrastructureWindow = defineAsyncComponent(
|
|
89
92
|
() => import('~/components/settings/InfrastructureWindow.vue'),
|
|
90
93
|
)
|
|
@@ -356,6 +359,7 @@ watch(
|
|
|
356
359
|
<WorkspaceSettingsPanel v-if="ui.workspaceSettingsOpen" />
|
|
357
360
|
<AccountSettingsPanel v-if="ui.accountSettingsOpen" />
|
|
358
361
|
<ObservabilityConnectionPanel v-if="ui.observabilityConnectionOpen" />
|
|
362
|
+
<PackageRegistriesPanel v-if="ui.packageRegistriesOpen" />
|
|
359
363
|
<InfrastructureWindow v-if="ui.infrastructureOpen" />
|
|
360
364
|
<ModelConfigurationPanel v-if="ui.modelConfigOpen" />
|
|
361
365
|
<LocalModelEndpointsPanel v-if="ui.localModelsOpen" />
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
boundServiceFrameIds,
|
|
5
|
+
indexLiveServiceEnvUrls,
|
|
6
|
+
type FrontendConfig,
|
|
7
|
+
} from '@cat-factory/contracts'
|
|
8
|
+
import type { EnvironmentHandle } from '~/types/domain'
|
|
9
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The workspace's live ephemeral-environment handles, fetched on demand from
|
|
13
|
+
* `GET /workspaces/:ws/environments`. Used to resolve a `frontend` frame's backend bindings to
|
|
14
|
+
* their live service URLs (the SPA mirror of the backend's `AgentContextBuilder` resolution) — so
|
|
15
|
+
* the inspector and the run/step detail can show each `envVar → live URL | mocked` the SAME way a
|
|
16
|
+
* UI-test run would. Kept as a thin, load-on-open cache (no snapshot delivery, no self-poll):
|
|
17
|
+
* the callers refresh it when the frontend inspector / a UI-test step detail opens.
|
|
18
|
+
*/
|
|
19
|
+
export const useEnvironmentsStore = defineStore('environments', () => {
|
|
20
|
+
const api = useApi()
|
|
21
|
+
|
|
22
|
+
/** The last-fetched env handles for the current workspace. */
|
|
23
|
+
const handles = ref<EnvironmentHandle[]>([])
|
|
24
|
+
/** A load is in flight (drives an optional spinner). */
|
|
25
|
+
const loading = ref(false)
|
|
26
|
+
|
|
27
|
+
/** (Re)load the workspace's environment handles; failures leave the last-known list. */
|
|
28
|
+
async function load(): Promise<void> {
|
|
29
|
+
const ws = useWorkspaceStore()
|
|
30
|
+
loading.value = true
|
|
31
|
+
try {
|
|
32
|
+
handles.value = await api.listEnvironments(ws.requireId())
|
|
33
|
+
} catch {
|
|
34
|
+
// Transient: keep the last-known handles rather than blanking the resolved view.
|
|
35
|
+
} finally {
|
|
36
|
+
loading.value = false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The live `serviceFrameId → url` map for exactly the service FRAMES a frontend config binds —
|
|
42
|
+
* the same newest-wins, ready-with-URL indexing the backend applies (`indexLiveServiceEnvUrls`),
|
|
43
|
+
* so the SPA's resolved-binding view can't drift from what a run would resolve.
|
|
44
|
+
*/
|
|
45
|
+
function liveServiceEnvUrls(
|
|
46
|
+
config: Pick<FrontendConfig, 'backendBindings'>,
|
|
47
|
+
): Map<string, string> {
|
|
48
|
+
return indexLiveServiceEnvUrls(handles.value, boundServiceFrameIds(config))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { handles, loading, load, liveServiceEnvUrls }
|
|
52
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { AddPackageRegistryInput, PackageRegistryEntryView } from '~/types/packageRegistries'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
import { apiErrorStatus } from '~/composables/api/errors'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The workspace's private package-registry entries (npm private orgs, GitHub
|
|
9
|
+
* Packages) that agent containers install with. Tokens are write-only — the store
|
|
10
|
+
* only ever holds the redacted summary views. Loaded on demand (the registries
|
|
11
|
+
* panel + the Integrations hub badge), not from the snapshot.
|
|
12
|
+
*/
|
|
13
|
+
export const usePackageRegistriesStore = defineStore('packageRegistries', () => {
|
|
14
|
+
const api = useApi()
|
|
15
|
+
|
|
16
|
+
const entries = ref<PackageRegistryEntryView[]>([])
|
|
17
|
+
const loading = ref(false)
|
|
18
|
+
// Mirrors the backend's opt-in gate (the module 503s when the encryption key is
|
|
19
|
+
// absent): `null` until first probed, then `true`/`false`. The hub hides its
|
|
20
|
+
// registries entry point when this is false.
|
|
21
|
+
const available = ref<boolean | null>(null)
|
|
22
|
+
let inFlight: Promise<void> | null = null
|
|
23
|
+
|
|
24
|
+
/** Force a refresh of the entry list (used after an add/remove). */
|
|
25
|
+
async function load() {
|
|
26
|
+
const ws = useWorkspaceStore()
|
|
27
|
+
loading.value = true
|
|
28
|
+
try {
|
|
29
|
+
entries.value = (await api.listPackageRegistries(ws.requireId())).entries
|
|
30
|
+
available.value = true
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (apiErrorStatus(err) === 503) {
|
|
33
|
+
// A definitive 503 means the integration is unconfigured (no encryption key on
|
|
34
|
+
// the backend): hide the UI entry points and stop probing.
|
|
35
|
+
available.value = false
|
|
36
|
+
entries.value = []
|
|
37
|
+
}
|
|
38
|
+
// Any other failure (transient 5xx / network) is left untouched: it must not hide
|
|
39
|
+
// an already-available panel nor cache a false "unavailable". `available` stays
|
|
40
|
+
// `null` when never probed, so `ensureLoaded` remains retryable on the next open.
|
|
41
|
+
} finally {
|
|
42
|
+
loading.value = false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Load once and share the result (coalescing concurrent callers); `load()` refreshes. */
|
|
47
|
+
async function ensureLoaded() {
|
|
48
|
+
if (available.value !== null) return
|
|
49
|
+
if (!inFlight) inFlight = load().finally(() => (inFlight = null))
|
|
50
|
+
return inFlight
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function add(input: AddPackageRegistryInput) {
|
|
54
|
+
const ws = useWorkspaceStore()
|
|
55
|
+
entries.value = (await api.addPackageRegistry(ws.requireId(), input)).entries
|
|
56
|
+
available.value = true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function remove(entryId: string) {
|
|
60
|
+
const ws = useWorkspaceStore()
|
|
61
|
+
await api.deletePackageRegistry(ws.requireId(), entryId)
|
|
62
|
+
entries.value = entries.value.filter((entry) => entry.id !== entryId)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { entries, loading, available, load, ensureLoaded, add, remove }
|
|
66
|
+
})
|
package/app/stores/ui.ts
CHANGED
|
@@ -153,6 +153,9 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
153
153
|
// today, pluggable). NB: distinct from `observabilityInstanceId` below, which is the
|
|
154
154
|
// LLM per-call observability panel.
|
|
155
155
|
const observabilityConnectionOpen = ref(false)
|
|
156
|
+
// Private package registries: the workspace's npm/GitHub-Packages entries agent
|
|
157
|
+
// containers install with. Opened from the Integrations hub.
|
|
158
|
+
const packageRegistriesOpen = ref(false)
|
|
156
159
|
// The single tabbed Infrastructure window — a TOP-LEVEL navbar destination (no longer
|
|
157
160
|
// reached via the Integrations hub). Two topical tabs: "Agent containers" (the execution
|
|
158
161
|
// backend + self-hosted runner pool, plus the local-mode warm pool/checkout) and "Test
|
|
@@ -561,6 +564,13 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
561
564
|
function closeObservabilityConnection() {
|
|
562
565
|
observabilityConnectionOpen.value = false
|
|
563
566
|
}
|
|
567
|
+
function openPackageRegistries() {
|
|
568
|
+
resetHubReturn()
|
|
569
|
+
packageRegistriesOpen.value = true
|
|
570
|
+
}
|
|
571
|
+
function closePackageRegistries() {
|
|
572
|
+
packageRegistriesOpen.value = false
|
|
573
|
+
}
|
|
564
574
|
// Top-level navbar entry into the Infrastructure window. No hub-return marker (it isn't
|
|
565
575
|
// reached from the Integrations hub), so the window shows no "Back to Integrations" control.
|
|
566
576
|
function openInfrastructure(tab: 'environment' | 'runner-pool' = 'runner-pool') {
|
|
@@ -791,6 +801,7 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
791
801
|
accountSettingsTab,
|
|
792
802
|
accountSettingsScrollTarget,
|
|
793
803
|
observabilityConnectionOpen,
|
|
804
|
+
packageRegistriesOpen,
|
|
794
805
|
infrastructureOpen,
|
|
795
806
|
infrastructureTab,
|
|
796
807
|
openInfrastructure,
|
|
@@ -880,6 +891,8 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
880
891
|
setAccountSettingsTab,
|
|
881
892
|
openObservabilityConnection,
|
|
882
893
|
closeObservabilityConnection,
|
|
894
|
+
openPackageRegistries,
|
|
895
|
+
closePackageRegistries,
|
|
883
896
|
openProviderConnection,
|
|
884
897
|
closeProviderConnection,
|
|
885
898
|
k3sSetupPrefill,
|
package/app/types/domain.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Private package-registry settings shapes. Per-workspace registry entries (npm
|
|
2
|
+
// private orgs, GitHub Packages) whose tokens are write-only — the list view only
|
|
3
|
+
// ever carries the non-secret summary (vendor + scopes + token tail).
|
|
4
|
+
//
|
|
5
|
+
// All wire shapes are sourced from @cat-factory/contracts (single source of truth).
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
PackageEcosystem,
|
|
9
|
+
PackageRegistryVendor,
|
|
10
|
+
AddPackageRegistryInput,
|
|
11
|
+
PackageRegistryEntryView,
|
|
12
|
+
PackageRegistryListView,
|
|
13
|
+
} from '@cat-factory/contracts'
|
package/i18n/locales/en.json
CHANGED
|
@@ -544,8 +544,22 @@
|
|
|
544
544
|
"failed": "Failed",
|
|
545
545
|
"stopped": "Not running"
|
|
546
546
|
}
|
|
547
|
+
},
|
|
548
|
+
"resolved": {
|
|
549
|
+
"title": "Resolves to",
|
|
550
|
+
"duplicateWarning": "Used on more than one binding, so only the last applies: {vars}.",
|
|
551
|
+
"serviceOffline": "{service}: no live environment (mocked)",
|
|
552
|
+
"mock": "Mock (WireMock)"
|
|
547
553
|
}
|
|
548
554
|
},
|
|
555
|
+
"serviceConnections": {
|
|
556
|
+
"title": "Service connections",
|
|
557
|
+
"hint": "The other services this one uses, e.g. a service that sends its emails. Connections draw edges on the board, and tasks can mark a connected service as directly involved.",
|
|
558
|
+
"descriptionPlaceholder": "How this service uses it, e.g. sends emails via it",
|
|
559
|
+
"remove": "Remove connection",
|
|
560
|
+
"empty": "No connections. Add one to link this service to another service it uses.",
|
|
561
|
+
"usedBy": "Used by"
|
|
562
|
+
},
|
|
549
563
|
"releaseHealth": {
|
|
550
564
|
"title": "Post-release health",
|
|
551
565
|
"clear": "Clear",
|
|
@@ -769,7 +783,11 @@
|
|
|
769
783
|
"responsibleProduct": "Responsible product",
|
|
770
784
|
"responsibleEmpty": "Unassigned. Set a product owner to notify them when requirement review flags this task.",
|
|
771
785
|
"autoStartDependents": "Auto-start dependents",
|
|
772
|
-
"autoStartHint": "When this task merges, automatically start the tasks that depend on it (once their other dependencies are also done)."
|
|
786
|
+
"autoStartHint": "When this task merges, automatically start the tasks that depend on it (once their other dependencies are also done).",
|
|
787
|
+
"involvedServices": "Involved services",
|
|
788
|
+
"involvedServicesHint": "Connected services directly involved in this task: each spins up as an ephemeral environment alongside this task's own service, and the coding agent may change their repositories too.",
|
|
789
|
+
"involvedServicesEmpty": "No connected services. Connect services on the service frame to select them here.",
|
|
790
|
+
"involvedServiceStale": "No longer connected to this task's service; it is dropped on the next change."
|
|
773
791
|
}
|
|
774
792
|
},
|
|
775
793
|
"panels": {
|
|
@@ -1479,6 +1497,7 @@
|
|
|
1479
1497
|
"documents": "Documents",
|
|
1480
1498
|
"taskTrackers": "Task trackers",
|
|
1481
1499
|
"observability": "Observability",
|
|
1500
|
+
"development": "Development",
|
|
1482
1501
|
"personal": "Personal (only you)"
|
|
1483
1502
|
},
|
|
1484
1503
|
"items": {
|
|
@@ -1516,6 +1535,10 @@
|
|
|
1516
1535
|
"label": "Post-release health",
|
|
1517
1536
|
"description": "Watch monitors and SLOs after a release ships (Datadog)."
|
|
1518
1537
|
},
|
|
1538
|
+
"packageRegistries": {
|
|
1539
|
+
"label": "Private package registries",
|
|
1540
|
+
"description": "npm and GitHub Packages tokens agents use to install private dependencies."
|
|
1541
|
+
},
|
|
1519
1542
|
"githubPat": {
|
|
1520
1543
|
"label": "My GitHub token",
|
|
1521
1544
|
"description": "A personal access token used for runs you start (pushes, PRs, CI, merge)."
|
|
@@ -2013,6 +2036,31 @@
|
|
|
2013
2036
|
"connectionNoun": "the observability connection",
|
|
2014
2037
|
"incidentNoun": "the incident provider"
|
|
2015
2038
|
},
|
|
2039
|
+
"packageRegistries": {
|
|
2040
|
+
"title": "Private package registries",
|
|
2041
|
+
"intro": "Connect the private registries your repositories install from. Agents receive them when they check out a repository, so private dependencies resolve during installs. Tokens are write-only: to change one, remove the entry and add it again.",
|
|
2042
|
+
"entryNoun": "registry entry",
|
|
2043
|
+
"list": {
|
|
2044
|
+
"heading": "Connected registries",
|
|
2045
|
+
"tokenTail": "token …{tail}",
|
|
2046
|
+
"remove": "Remove entry"
|
|
2047
|
+
},
|
|
2048
|
+
"add": {
|
|
2049
|
+
"heading": "Add a registry",
|
|
2050
|
+
"vendor": "Registry",
|
|
2051
|
+
"host": "The token is sent only to {host}.",
|
|
2052
|
+
"scopes": "Package scopes",
|
|
2053
|
+
"scopesHelp": "Comma-separated npm scopes",
|
|
2054
|
+
"token": "Access token",
|
|
2055
|
+
"save": "Add registry"
|
|
2056
|
+
},
|
|
2057
|
+
"toast": {
|
|
2058
|
+
"loadFailed": "Could not load package registries",
|
|
2059
|
+
"added": "Registry added",
|
|
2060
|
+
"addFailed": "Could not add the registry",
|
|
2061
|
+
"removeFailed": "Could not remove the registry entry"
|
|
2062
|
+
}
|
|
2063
|
+
},
|
|
2016
2064
|
"localMode": {
|
|
2017
2065
|
"title": "Local mode",
|
|
2018
2066
|
"intro": "Tuning for the local container runner, stored on this machine's deployment (it replaced the {poolVars} / {harnessVars} env vars). Saving resizes the warm pool live, no restart needed; in-flight runs keep the container they already hold.",
|
package/i18n/locales/es.json
CHANGED
|
@@ -501,8 +501,22 @@
|
|
|
501
501
|
"failed": "Error",
|
|
502
502
|
"stopped": "No iniciada"
|
|
503
503
|
}
|
|
504
|
+
},
|
|
505
|
+
"resolved": {
|
|
506
|
+
"title": "Se resuelve a",
|
|
507
|
+
"duplicateWarning": "Usado en más de un binding, por lo que solo se aplica el último: {vars}.",
|
|
508
|
+
"serviceOffline": "{service}: sin entorno activo (simulado)",
|
|
509
|
+
"mock": "Mock (WireMock)"
|
|
504
510
|
}
|
|
505
511
|
},
|
|
512
|
+
"serviceConnections": {
|
|
513
|
+
"title": "Conexiones de servicios",
|
|
514
|
+
"hint": "Los otros servicios que este utiliza, p. ej. un servicio que envía sus correos. Las conexiones dibujan aristas en el tablero, y las tareas pueden marcar un servicio conectado como directamente involucrado.",
|
|
515
|
+
"descriptionPlaceholder": "Cómo lo usa este servicio, p. ej. envía correos a través de él",
|
|
516
|
+
"remove": "Eliminar conexión",
|
|
517
|
+
"empty": "Sin conexiones. Añade una para vincular este servicio con otro servicio que utiliza.",
|
|
518
|
+
"usedBy": "Usado por"
|
|
519
|
+
},
|
|
506
520
|
"releaseHealth": {
|
|
507
521
|
"title": "Salud posterior al lanzamiento",
|
|
508
522
|
"clear": "Limpiar",
|
|
@@ -726,7 +740,11 @@
|
|
|
726
740
|
"responsibleProduct": "Producto responsable",
|
|
727
741
|
"responsibleEmpty": "Sin asignar. Define un responsable de producto para notificarle cuando la revisión de requisitos marque esta tarea.",
|
|
728
742
|
"autoStartDependents": "Iniciar dependientes automáticamente",
|
|
729
|
-
"autoStartHint": "Cuando esta tarea se fusione, inicia automáticamente las tareas que dependen de ella (una vez que sus otras dependencias también estén completas)."
|
|
743
|
+
"autoStartHint": "Cuando esta tarea se fusione, inicia automáticamente las tareas que dependen de ella (una vez que sus otras dependencias también estén completas).",
|
|
744
|
+
"involvedServices": "Servicios involucrados",
|
|
745
|
+
"involvedServicesHint": "Servicios conectados directamente involucrados en esta tarea: cada uno se levanta como un entorno efímero junto al servicio propio de la tarea, y el agente de código puede modificar también sus repositorios.",
|
|
746
|
+
"involvedServicesEmpty": "No hay servicios conectados. Conecta servicios en el marco del servicio para seleccionarlos aquí.",
|
|
747
|
+
"involvedServiceStale": "Ya no está conectado al servicio de esta tarea; se eliminará con el próximo cambio."
|
|
730
748
|
}
|
|
731
749
|
},
|
|
732
750
|
"panels": {
|
|
@@ -1426,6 +1444,7 @@
|
|
|
1426
1444
|
"documents": "Documentos",
|
|
1427
1445
|
"taskTrackers": "Rastreadores de tareas",
|
|
1428
1446
|
"observability": "Observabilidad",
|
|
1447
|
+
"development": "Desarrollo",
|
|
1429
1448
|
"personal": "Personal (solo tú)"
|
|
1430
1449
|
},
|
|
1431
1450
|
"items": {
|
|
@@ -1463,6 +1482,10 @@
|
|
|
1463
1482
|
"label": "Salud posterior al lanzamiento",
|
|
1464
1483
|
"description": "Vigila los monitores y SLO después de publicar una versión (Datadog)."
|
|
1465
1484
|
},
|
|
1485
|
+
"packageRegistries": {
|
|
1486
|
+
"label": "Registros privados de paquetes",
|
|
1487
|
+
"description": "Tokens de npm y GitHub Packages que los agentes usan para instalar dependencias privadas."
|
|
1488
|
+
},
|
|
1466
1489
|
"githubPat": {
|
|
1467
1490
|
"label": "Mi token de GitHub",
|
|
1468
1491
|
"description": "Un token de acceso personal usado para las ejecuciones que inicias (pushes, PR, CI, fusión)."
|
|
@@ -1842,6 +1865,31 @@
|
|
|
1842
1865
|
"connectionNoun": "la conexión de observabilidad",
|
|
1843
1866
|
"incidentNoun": "el proveedor de incidencias"
|
|
1844
1867
|
},
|
|
1868
|
+
"packageRegistries": {
|
|
1869
|
+
"title": "Registros privados de paquetes",
|
|
1870
|
+
"intro": "Conecta los registros privados desde los que instalan tus repositorios. Los agentes los reciben al hacer checkout de un repositorio, de modo que las dependencias privadas se resuelven durante la instalación. Los tokens son de solo escritura: para cambiar uno, elimina la entrada y vuelve a agregarla.",
|
|
1871
|
+
"entryNoun": "entrada de registro",
|
|
1872
|
+
"list": {
|
|
1873
|
+
"heading": "Registros conectados",
|
|
1874
|
+
"tokenTail": "token …{tail}",
|
|
1875
|
+
"remove": "Eliminar entrada"
|
|
1876
|
+
},
|
|
1877
|
+
"add": {
|
|
1878
|
+
"heading": "Agregar un registro",
|
|
1879
|
+
"vendor": "Registro",
|
|
1880
|
+
"host": "El token solo se envía a {host}.",
|
|
1881
|
+
"scopes": "Ámbitos de paquetes",
|
|
1882
|
+
"scopesHelp": "Ámbitos de npm separados por comas",
|
|
1883
|
+
"token": "Token de acceso",
|
|
1884
|
+
"save": "Agregar registro"
|
|
1885
|
+
},
|
|
1886
|
+
"toast": {
|
|
1887
|
+
"loadFailed": "No se pudieron cargar los registros de paquetes",
|
|
1888
|
+
"added": "Registro agregado",
|
|
1889
|
+
"addFailed": "No se pudo agregar el registro",
|
|
1890
|
+
"removeFailed": "No se pudo eliminar la entrada del registro"
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1845
1893
|
"localMode": {
|
|
1846
1894
|
"title": "Modo local",
|
|
1847
1895
|
"intro": "Ajustes del ejecutor de contenedores local, almacenados en el despliegue de esta máquina (reemplazó las variables de entorno {poolVars} / {harnessVars}). Guardar redimensiona el grupo en caliente en vivo, sin necesidad de reiniciar; las ejecuciones en curso conservan el contenedor que ya tienen.",
|