@cat-factory/app 0.49.2 → 0.50.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/layout/IntegrationsHub.vue +3 -66
- package/app/components/layout/SideBar.vue +39 -0
- package/app/components/panels/inspector/ServiceFragments.vue +31 -5
- package/app/components/panels/inspector/TaskStructure.vue +31 -5
- package/app/components/settings/ExecutionBackendSelector.vue +171 -0
- package/app/components/settings/InfrastructureWindow.vue +36 -126
- package/app/components/settings/LocalContainerPoolSettings.vue +181 -0
- package/app/components/settings/ProviderConnectionTab.vue +25 -12
- package/app/pages/index.vue +0 -4
- package/app/stores/auth.ts +10 -1
- package/app/stores/ui.ts +14 -18
- package/i18n/locales/en.json +24 -23
- package/i18n/locales/es.json +27 -26
- package/i18n/locales/fr.json +27 -26
- package/i18n/locales/pl.json +27 -26
- package/i18n/locales/uk.json +27 -26
- package/package.json +2 -2
- package/app/components/settings/LocalModeSettingsPanel.vue +0 -195
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// The single tabbed "Infrastructure" window
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
2
|
+
// The single tabbed "Infrastructure" window — now a TOP-LEVEL navbar destination (no longer
|
|
3
|
+
// reached through the Integrations hub). Two topical tabs:
|
|
4
|
+
// - "Agent containers" — where repo-operating agent containers run. Shows the execution
|
|
5
|
+
// backend selector, the runner-pool connection (ProviderConnectionTab), and — in local
|
|
6
|
+
// mode — the warm-container-pool + checkout-reuse settings (the local agent-container
|
|
7
|
+
// runtime, folded in from the former LocalModeSettingsPanel).
|
|
8
|
+
// - "Test environments" — where the Tester's ephemeral environments run. Shows the test-env
|
|
9
|
+
// backend selector and the environment-provider connection.
|
|
10
|
+
// Local-specific affordances render inline, gated on `auth.localMode?.enabled`. A tab whose
|
|
11
|
+
// backend integration is disabled (503) simply doesn't render.
|
|
12
12
|
import { computed, ref, watch } from 'vue'
|
|
13
13
|
import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
14
|
-
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
15
14
|
import ProviderConnectionTab from '~/components/settings/ProviderConnectionTab.vue'
|
|
15
|
+
import ExecutionBackendSelector from '~/components/settings/ExecutionBackendSelector.vue'
|
|
16
|
+
import LocalContainerPoolSettings from '~/components/settings/LocalContainerPoolSettings.vue'
|
|
16
17
|
|
|
17
18
|
const { t } = useI18n()
|
|
18
19
|
const ui = useUiStore()
|
|
19
20
|
const store = useProviderConnectionsStore()
|
|
20
21
|
const auth = useAuthStore()
|
|
21
|
-
const settings = useWorkspaceSettingsStore()
|
|
22
|
-
const toast = useToast()
|
|
23
22
|
|
|
24
23
|
const open = computed({
|
|
25
24
|
get: () => ui.infrastructureOpen,
|
|
@@ -27,7 +26,8 @@ const open = computed({
|
|
|
27
26
|
if (!v) ui.closeProviderConnection()
|
|
28
27
|
},
|
|
29
28
|
})
|
|
30
|
-
|
|
29
|
+
|
|
30
|
+
const isLocal = computed(() => auth.localMode?.enabled === true)
|
|
31
31
|
|
|
32
32
|
// Each concern gates on its own availability probe; an unavailable tab isn't offered.
|
|
33
33
|
const agentsAvailable = computed(() => store.isAvailable('runner-pool'))
|
|
@@ -38,7 +38,7 @@ const tabs = computed(() => {
|
|
|
38
38
|
if (agentsAvailable.value)
|
|
39
39
|
out.push({
|
|
40
40
|
value: 'runner-pool',
|
|
41
|
-
label: t('settings.providerConnection.tabs.
|
|
41
|
+
label: t('settings.providerConnection.tabs.agentContainers'),
|
|
42
42
|
icon: 'i-lucide-server-cog',
|
|
43
43
|
slot: 'runner-pool',
|
|
44
44
|
})
|
|
@@ -54,8 +54,8 @@ const tabs = computed(() => {
|
|
|
54
54
|
|
|
55
55
|
const activeTab = ref<ProviderConnectionKind>(ui.infrastructureTab)
|
|
56
56
|
|
|
57
|
-
// Honour the deep-linked tab each time the window opens
|
|
58
|
-
//
|
|
57
|
+
// Honour the deep-linked tab each time the window opens, falling back to the first available
|
|
58
|
+
// tab if the requested one is off.
|
|
59
59
|
watch(
|
|
60
60
|
open,
|
|
61
61
|
(isOpen) => {
|
|
@@ -69,9 +69,7 @@ watch(
|
|
|
69
69
|
)
|
|
70
70
|
// When availability resolves after open, re-pin onto a valid tab — but keep honouring the
|
|
71
71
|
// deep-linked request. The two availability probes resolve independently, so `tabs` can pass
|
|
72
|
-
// through a transient single-tab list
|
|
73
|
-
// environment one). We must NOT let that transient list steal focus from a still-loading
|
|
74
|
-
// requested tab, so only fall back to the first tab once loading has fully settled.
|
|
72
|
+
// through a transient single-tab list; only fall back to the first tab once loading settled.
|
|
75
73
|
watch([tabs, () => store.loaded], () => {
|
|
76
74
|
const list = tabs.value
|
|
77
75
|
if (list.some((x) => x.value === activeTab.value)) return
|
|
@@ -82,38 +80,6 @@ watch([tabs, () => store.loaded], () => {
|
|
|
82
80
|
activeTab.value = list[0]!.value
|
|
83
81
|
}
|
|
84
82
|
})
|
|
85
|
-
|
|
86
|
-
// --- Local-mode infrastructure delegation (cross-cutting; shown only in local mode) ---
|
|
87
|
-
// In local mode this is where a developer chooses, per workspace, whether to run on this
|
|
88
|
-
// machine (host Docker for agents, in-container docker-compose for the Tester) or delegate
|
|
89
|
-
// to an external service. Each toggle is enabled only once its provider is registered.
|
|
90
|
-
const isLocal = computed(() => auth.localMode?.enabled === true)
|
|
91
|
-
const runnerPoolRegistered = computed(() => !!store.connectionFor('runner-pool'))
|
|
92
|
-
const envRegistered = computed(() => !!store.connectionFor('environment'))
|
|
93
|
-
const savingDelegation = ref(false)
|
|
94
|
-
|
|
95
|
-
async function setDelegation(patch: {
|
|
96
|
-
delegateAgentsToRunnerPool?: boolean
|
|
97
|
-
delegateTestEnvToProvider?: boolean
|
|
98
|
-
}) {
|
|
99
|
-
savingDelegation.value = true
|
|
100
|
-
try {
|
|
101
|
-
await settings.update(patch)
|
|
102
|
-
} catch (e) {
|
|
103
|
-
toast.add({
|
|
104
|
-
title: t('settings.providerConnection.delegation.updateFailed'),
|
|
105
|
-
description: e instanceof Error ? e.message : String(e),
|
|
106
|
-
icon: 'i-lucide-triangle-alert',
|
|
107
|
-
color: 'error',
|
|
108
|
-
})
|
|
109
|
-
} finally {
|
|
110
|
-
savingDelegation.value = false
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function selectTab(kind: ProviderConnectionKind) {
|
|
115
|
-
activeTab.value = kind
|
|
116
|
-
}
|
|
117
83
|
</script>
|
|
118
84
|
|
|
119
85
|
<template>
|
|
@@ -122,80 +88,8 @@ function selectTab(kind: ProviderConnectionKind) {
|
|
|
122
88
|
:title="t('settings.providerConnection.windowTitle')"
|
|
123
89
|
:ui="{ content: 'max-w-xl' }"
|
|
124
90
|
>
|
|
125
|
-
<template #title>
|
|
126
|
-
<IntegrationBackTitle :title="t('settings.providerConnection.windowTitle')" @back="back" />
|
|
127
|
-
</template>
|
|
128
91
|
<template #body>
|
|
129
92
|
<div class="space-y-4">
|
|
130
|
-
<!-- Local-mode delegation: the local-vs-external choice for BOTH container agents
|
|
131
|
-
AND the Tester's ephemeral environments, made once here at the top. -->
|
|
132
|
-
<section
|
|
133
|
-
v-if="isLocal"
|
|
134
|
-
class="space-y-3 rounded-lg border border-slate-700 bg-slate-900/40 p-3"
|
|
135
|
-
>
|
|
136
|
-
<div>
|
|
137
|
-
<h3 class="text-sm font-semibold text-slate-200">
|
|
138
|
-
{{ t('settings.providerConnection.delegation.title') }}
|
|
139
|
-
</h3>
|
|
140
|
-
<p class="mt-1 text-[11px] text-slate-400">
|
|
141
|
-
{{ t('settings.providerConnection.delegation.intro') }}
|
|
142
|
-
</p>
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
<!-- Container agents → self-hosted runner pool -->
|
|
146
|
-
<div class="space-y-1">
|
|
147
|
-
<label class="flex items-center gap-2">
|
|
148
|
-
<USwitch
|
|
149
|
-
size="sm"
|
|
150
|
-
:model-value="settings.settings.delegateAgentsToRunnerPool"
|
|
151
|
-
:disabled="savingDelegation || !runnerPoolRegistered"
|
|
152
|
-
@update:model-value="(v) => setDelegation({ delegateAgentsToRunnerPool: v })"
|
|
153
|
-
/>
|
|
154
|
-
<span class="text-sm text-slate-200">
|
|
155
|
-
{{ t('settings.providerConnection.delegation.agentsToggle') }}
|
|
156
|
-
</span>
|
|
157
|
-
</label>
|
|
158
|
-
<p class="pl-9 text-[11px] text-slate-400">
|
|
159
|
-
{{ t('settings.providerConnection.delegation.agentsHint') }}
|
|
160
|
-
<template v-if="!runnerPoolRegistered">
|
|
161
|
-
<i18n-t
|
|
162
|
-
keypath="settings.providerConnection.delegation.registerPoolPrompt"
|
|
163
|
-
tag="span"
|
|
164
|
-
scope="global"
|
|
165
|
-
>
|
|
166
|
-
<template #link>
|
|
167
|
-
<button
|
|
168
|
-
type="button"
|
|
169
|
-
class="text-sky-400 underline underline-offset-2 hover:text-sky-300"
|
|
170
|
-
@click="selectTab('runner-pool')"
|
|
171
|
-
>
|
|
172
|
-
{{ t('settings.providerConnection.delegation.registerPoolLink') }}
|
|
173
|
-
</button>
|
|
174
|
-
</template>
|
|
175
|
-
</i18n-t>
|
|
176
|
-
</template>
|
|
177
|
-
</p>
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
<!-- Tester environments → environment provider -->
|
|
181
|
-
<div class="space-y-1">
|
|
182
|
-
<label class="flex items-center gap-2">
|
|
183
|
-
<USwitch
|
|
184
|
-
size="sm"
|
|
185
|
-
:model-value="settings.settings.delegateTestEnvToProvider"
|
|
186
|
-
:disabled="savingDelegation || !envRegistered"
|
|
187
|
-
@update:model-value="(v) => setDelegation({ delegateTestEnvToProvider: v })"
|
|
188
|
-
/>
|
|
189
|
-
<span class="text-sm text-slate-200">
|
|
190
|
-
{{ t('settings.providerConnection.delegation.envToggle') }}
|
|
191
|
-
</span>
|
|
192
|
-
</label>
|
|
193
|
-
<p class="pl-9 text-[11px] text-slate-400">
|
|
194
|
-
{{ t('settings.providerConnection.delegation.envHint') }}
|
|
195
|
-
</p>
|
|
196
|
-
</div>
|
|
197
|
-
</section>
|
|
198
|
-
|
|
199
93
|
<UTabs
|
|
200
94
|
v-if="tabs.length"
|
|
201
95
|
v-model="activeTab"
|
|
@@ -205,10 +99,26 @@ function selectTab(kind: ProviderConnectionKind) {
|
|
|
205
99
|
data-testid="infrastructure-tabs"
|
|
206
100
|
>
|
|
207
101
|
<template #runner-pool>
|
|
208
|
-
<
|
|
102
|
+
<div class="space-y-4">
|
|
103
|
+
<!-- Where agent containers run (writable in local mode; read-only elsewhere). -->
|
|
104
|
+
<ExecutionBackendSelector axis="execution" />
|
|
105
|
+
<ProviderConnectionTab kind="runner-pool" />
|
|
106
|
+
<!-- Local mode: the warm-pool + checkout reuse ARE the host agent-container
|
|
107
|
+
runtime, so they live here rather than in a separate menu. -->
|
|
108
|
+
<section v-if="isLocal" class="border-t border-slate-800 pt-4">
|
|
109
|
+
<h3 class="mb-3 text-sm font-semibold text-slate-200">
|
|
110
|
+
{{ t('settings.localMode.title') }}
|
|
111
|
+
</h3>
|
|
112
|
+
<LocalContainerPoolSettings />
|
|
113
|
+
</section>
|
|
114
|
+
</div>
|
|
209
115
|
</template>
|
|
210
116
|
<template #environment>
|
|
211
|
-
<
|
|
117
|
+
<div class="space-y-4">
|
|
118
|
+
<!-- Where Tester environments run (writable in local mode; read-only elsewhere). -->
|
|
119
|
+
<ExecutionBackendSelector axis="testEnv" />
|
|
120
|
+
<ProviderConnectionTab kind="environment" />
|
|
121
|
+
</div>
|
|
212
122
|
</template>
|
|
213
123
|
</UTabs>
|
|
214
124
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Local-mode-only: the warm-container pool + per-repo checkout reuse. These are a
|
|
3
|
+
// per-DEPLOYMENT singleton stored in the DB (they replaced the LOCAL_POOL_* / HARNESS_* env
|
|
4
|
+
// vars), so a developer tunes them here instead of editing .env. The warm pool keeps idle
|
|
5
|
+
// harness containers ready and re-leases one (preferring repo affinity) to each run — far
|
|
6
|
+
// faster startup than a cold container per run. Saving applies the new sizing to the running
|
|
7
|
+
// service immediately (live resize, no restart); in-flight runs keep the container they hold,
|
|
8
|
+
// and the checkout config applies to containers started after the save.
|
|
9
|
+
//
|
|
10
|
+
// Previously a standalone modal (LocalModeSettingsPanel); now folded into the Agent-containers
|
|
11
|
+
// tab of the Infrastructure window, since the warm pool IS the local agent-container runtime.
|
|
12
|
+
import { reactive, ref, watch } from 'vue'
|
|
13
|
+
|
|
14
|
+
const { t } = useI18n()
|
|
15
|
+
const store = useLocalSettingsStore()
|
|
16
|
+
const toast = useToast()
|
|
17
|
+
|
|
18
|
+
const saving = ref(false)
|
|
19
|
+
|
|
20
|
+
// Editable draft. `idleMinutes` and `cleanKeep` are friendlier renderings of the stored
|
|
21
|
+
// `pool.idleTtlMs` (ms) and `checkout.cleanKeep` (string[]).
|
|
22
|
+
const draft = reactive({
|
|
23
|
+
size: 0,
|
|
24
|
+
minWarm: 0,
|
|
25
|
+
max: null as number | null,
|
|
26
|
+
idleMinutes: 10,
|
|
27
|
+
workspaceRoot: '/workspace',
|
|
28
|
+
cleanKeep: 'node_modules,.venv,target,.gradle,.pnpm-store',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function syncDraft() {
|
|
32
|
+
const s = store.settings
|
|
33
|
+
if (!s) return
|
|
34
|
+
draft.size = s.pool.size
|
|
35
|
+
draft.minWarm = s.pool.minWarm
|
|
36
|
+
draft.max = s.pool.max
|
|
37
|
+
draft.idleMinutes = Math.round(s.pool.idleTtlMs / 60_000)
|
|
38
|
+
draft.workspaceRoot = s.checkout.workspaceRoot
|
|
39
|
+
draft.cleanKeep = s.checkout.cleanKeep.join(',')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Load + hydrate the draft on mount (the tab only mounts in local mode).
|
|
43
|
+
void store.load().then(syncDraft)
|
|
44
|
+
watch(() => store.settings, syncDraft)
|
|
45
|
+
|
|
46
|
+
async function save() {
|
|
47
|
+
const cleanKeep = draft.cleanKeep
|
|
48
|
+
.split(',')
|
|
49
|
+
.map((s) => s.trim())
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
saving.value = true
|
|
52
|
+
try {
|
|
53
|
+
await store.save({
|
|
54
|
+
pool: {
|
|
55
|
+
size: Math.max(0, Math.floor(draft.size)),
|
|
56
|
+
minWarm: Math.max(0, Math.floor(draft.minWarm)),
|
|
57
|
+
max: draft.max == null ? null : Math.max(0, Math.floor(draft.max)),
|
|
58
|
+
idleTtlMs: Math.max(0, Math.floor(draft.idleMinutes * 60_000)),
|
|
59
|
+
},
|
|
60
|
+
checkout: { workspaceRoot: draft.workspaceRoot.trim() || '/workspace', cleanKeep },
|
|
61
|
+
})
|
|
62
|
+
toast.add({
|
|
63
|
+
title: t('settings.localMode.toast.saved'),
|
|
64
|
+
icon: 'i-lucide-check',
|
|
65
|
+
color: 'success',
|
|
66
|
+
})
|
|
67
|
+
} catch (e) {
|
|
68
|
+
toast.add({
|
|
69
|
+
title: t('settings.localMode.toast.saveFailed'),
|
|
70
|
+
description: e instanceof Error ? e.message : String(e),
|
|
71
|
+
icon: 'i-lucide-triangle-alert',
|
|
72
|
+
color: 'error',
|
|
73
|
+
})
|
|
74
|
+
} finally {
|
|
75
|
+
saving.value = false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<div class="space-y-6" data-testid="local-container-pool-settings">
|
|
82
|
+
<i18n-t
|
|
83
|
+
keypath="settings.localMode.intro"
|
|
84
|
+
tag="p"
|
|
85
|
+
class="text-xs text-slate-400"
|
|
86
|
+
scope="global"
|
|
87
|
+
>
|
|
88
|
+
<template #poolVars>
|
|
89
|
+
<code>LOCAL_POOL_*</code>
|
|
90
|
+
</template>
|
|
91
|
+
<template #harnessVars>
|
|
92
|
+
<code>HARNESS_*</code>
|
|
93
|
+
</template>
|
|
94
|
+
</i18n-t>
|
|
95
|
+
|
|
96
|
+
<!-- Warm container pool -->
|
|
97
|
+
<section class="space-y-3">
|
|
98
|
+
<div>
|
|
99
|
+
<h4 class="text-sm font-semibold text-slate-200">
|
|
100
|
+
{{ t('settings.localMode.pool.heading') }}
|
|
101
|
+
</h4>
|
|
102
|
+
<i18n-t
|
|
103
|
+
keypath="settings.localMode.pool.description"
|
|
104
|
+
tag="p"
|
|
105
|
+
class="text-[11px] text-slate-400"
|
|
106
|
+
scope="global"
|
|
107
|
+
>
|
|
108
|
+
<template #appleContainer>
|
|
109
|
+
<code>container</code>
|
|
110
|
+
</template>
|
|
111
|
+
</i18n-t>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
114
|
+
<UFormField
|
|
115
|
+
:label="t('settings.localMode.pool.size.label')"
|
|
116
|
+
:help="t('settings.localMode.pool.size.help')"
|
|
117
|
+
>
|
|
118
|
+
<UInput v-model.number="draft.size" type="number" :min="0" size="sm" />
|
|
119
|
+
</UFormField>
|
|
120
|
+
<UFormField
|
|
121
|
+
:label="t('settings.localMode.pool.minWarm.label')"
|
|
122
|
+
:help="t('settings.localMode.pool.minWarm.help')"
|
|
123
|
+
>
|
|
124
|
+
<UInput v-model.number="draft.minWarm" type="number" :min="0" size="sm" />
|
|
125
|
+
</UFormField>
|
|
126
|
+
<UFormField
|
|
127
|
+
:label="t('settings.localMode.pool.max.label')"
|
|
128
|
+
:help="t('settings.localMode.pool.max.help')"
|
|
129
|
+
>
|
|
130
|
+
<UInput
|
|
131
|
+
v-model.number="draft.max"
|
|
132
|
+
type="number"
|
|
133
|
+
:min="0"
|
|
134
|
+
size="sm"
|
|
135
|
+
:placeholder="t('settings.localMode.pool.max.placeholder')"
|
|
136
|
+
/>
|
|
137
|
+
</UFormField>
|
|
138
|
+
<UFormField
|
|
139
|
+
:label="t('settings.localMode.pool.idleTimeout.label')"
|
|
140
|
+
:help="t('settings.localMode.pool.idleTimeout.help')"
|
|
141
|
+
>
|
|
142
|
+
<UInput v-model.number="draft.idleMinutes" type="number" :min="0" size="sm" />
|
|
143
|
+
</UFormField>
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
|
|
147
|
+
<!-- Checkout reuse -->
|
|
148
|
+
<section class="space-y-3 border-t border-slate-800 pt-6">
|
|
149
|
+
<div>
|
|
150
|
+
<h4 class="text-sm font-semibold text-slate-200">
|
|
151
|
+
{{ t('settings.localMode.checkout.heading') }}
|
|
152
|
+
</h4>
|
|
153
|
+
<p class="text-[11px] text-slate-400">
|
|
154
|
+
{{ t('settings.localMode.checkout.description') }}
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
<UFormField
|
|
158
|
+
:label="t('settings.localMode.checkout.workspaceRoot.label')"
|
|
159
|
+
:help="t('settings.localMode.checkout.workspaceRoot.help')"
|
|
160
|
+
>
|
|
161
|
+
<UInput v-model="draft.workspaceRoot" size="sm" placeholder="/workspace" />
|
|
162
|
+
</UFormField>
|
|
163
|
+
<UFormField
|
|
164
|
+
:label="t('settings.localMode.checkout.cleanKeep.label')"
|
|
165
|
+
:help="t('settings.localMode.checkout.cleanKeep.help')"
|
|
166
|
+
>
|
|
167
|
+
<UInput
|
|
168
|
+
v-model="draft.cleanKeep"
|
|
169
|
+
size="sm"
|
|
170
|
+
placeholder="node_modules,.venv,target,.gradle,.pnpm-store"
|
|
171
|
+
/>
|
|
172
|
+
</UFormField>
|
|
173
|
+
</section>
|
|
174
|
+
|
|
175
|
+
<div class="flex justify-end">
|
|
176
|
+
<UButton color="primary" icon="i-lucide-save" :loading="saving" @click="save">
|
|
177
|
+
{{ t('common.save') }}
|
|
178
|
+
</UButton>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</template>
|
|
@@ -440,18 +440,31 @@ function fieldHelp(key: string): string | undefined {
|
|
|
440
440
|
</div>
|
|
441
441
|
</div>
|
|
442
442
|
|
|
443
|
-
<!-- MANIFEST-driven provider: the
|
|
444
|
-
|
|
443
|
+
<!-- MANIFEST-driven provider: the raw JSON manifest editor. Collapsed by default — it's
|
|
444
|
+
the advanced path, needed ONLY to integrate a custom API-based scheduler. The common
|
|
445
|
+
backends (local Docker, Cloudflare Containers, Kubernetes) don't need it. -->
|
|
446
|
+
<details
|
|
445
447
|
v-else
|
|
446
|
-
|
|
447
|
-
:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
448
|
+
class="rounded-lg border border-slate-700 bg-slate-900/40 p-3"
|
|
449
|
+
:open="!!connection"
|
|
450
|
+
>
|
|
451
|
+
<summary class="cursor-pointer text-sm font-medium text-slate-200">
|
|
452
|
+
{{ t('settings.providerConnection.advancedManifest.summary') }}
|
|
453
|
+
</summary>
|
|
454
|
+
<p class="mt-2 mb-3 text-[11px] text-slate-400">
|
|
455
|
+
{{ t('settings.providerConnection.advancedManifest.intro') }}
|
|
456
|
+
</p>
|
|
457
|
+
<ProviderManifestEditor
|
|
458
|
+
:kind="kind"
|
|
459
|
+
:saved-manifest="descriptor.savedManifest"
|
|
460
|
+
:connected="!!connection"
|
|
461
|
+
:supports-test="descriptor.supportsTest"
|
|
462
|
+
:testing="testing"
|
|
463
|
+
:busy="busy"
|
|
464
|
+
:test-result="testResult"
|
|
465
|
+
@test="testManifest"
|
|
466
|
+
@save="saveManifest"
|
|
467
|
+
/>
|
|
468
|
+
</details>
|
|
456
469
|
</div>
|
|
457
470
|
</template>
|
package/app/pages/index.vue
CHANGED
|
@@ -79,9 +79,6 @@ const ModelConfigurationPanel = defineAsyncComponent(
|
|
|
79
79
|
const LocalModelEndpointsPanel = defineAsyncComponent(
|
|
80
80
|
() => import('~/components/settings/LocalModelEndpointsPanel.vue'),
|
|
81
81
|
)
|
|
82
|
-
const LocalModeSettingsPanel = defineAsyncComponent(
|
|
83
|
-
() => import('~/components/settings/LocalModeSettingsPanel.vue'),
|
|
84
|
-
)
|
|
85
82
|
const SandboxPanel = defineAsyncComponent(() => import('~/components/sandbox/SandboxPanel.vue'))
|
|
86
83
|
const UserSecretsSection = defineAsyncComponent(
|
|
87
84
|
() => import('~/components/settings/UserSecretsSection.vue'),
|
|
@@ -287,7 +284,6 @@ watch(
|
|
|
287
284
|
<InfrastructureWindow v-if="ui.infrastructureOpen" />
|
|
288
285
|
<ModelConfigurationPanel v-if="ui.modelConfigOpen" />
|
|
289
286
|
<LocalModelEndpointsPanel v-if="ui.localModelsOpen" />
|
|
290
|
-
<LocalModeSettingsPanel v-if="ui.localModeSettingsOpen" />
|
|
291
287
|
<SandboxPanel v-if="ui.sandboxOpen" />
|
|
292
288
|
<UserSecretsSection v-if="ui.userSecretsOpen" />
|
|
293
289
|
<OpenRouterCatalogPanel v-if="ui.openRouterOpen" />
|
package/app/stores/auth.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LocalModeConfig } from '@cat-factory/contracts'
|
|
1
|
+
import type { InfrastructureCapabilities, LocalModeConfig } from '@cat-factory/contracts'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { computed, ref } from 'vue'
|
|
4
4
|
import type { AuthUser } from '~/types/domain'
|
|
@@ -31,6 +31,13 @@ export const useAuthStore = defineStore(
|
|
|
31
31
|
* setup banner). Null on every other facade.
|
|
32
32
|
*/
|
|
33
33
|
const localMode = ref<LocalModeConfig | null>(null)
|
|
34
|
+
/**
|
|
35
|
+
* The deployment's infrastructure execution backends (which agent-container runtime + test
|
|
36
|
+
* environment options exist, and the deployment default active one). Drives the
|
|
37
|
+
* Infrastructure window's backend selector. Null until the auth handshake resolves / on a
|
|
38
|
+
* facade that doesn't report it.
|
|
39
|
+
*/
|
|
40
|
+
const infrastructure = ref<InfrastructureCapabilities | null>(null)
|
|
34
41
|
/**
|
|
35
42
|
* Local mode only: the source-control provider the user last chose to sign in with
|
|
36
43
|
* (its PAT lives server-side in env — this is just the non-secret choice). Persisted, so
|
|
@@ -72,6 +79,7 @@ export const useAuthStore = defineStore(
|
|
|
72
79
|
required.value = config.enabled
|
|
73
80
|
if (config.providers) providers.value = config.providers
|
|
74
81
|
localMode.value = config.localMode ?? null
|
|
82
|
+
infrastructure.value = config.infrastructure ?? null
|
|
75
83
|
} catch {
|
|
76
84
|
// Backend unreachable — let the board's own error UI handle it.
|
|
77
85
|
required.value = false
|
|
@@ -222,6 +230,7 @@ export const useAuthStore = defineStore(
|
|
|
222
230
|
required,
|
|
223
231
|
providers,
|
|
224
232
|
localMode,
|
|
233
|
+
infrastructure,
|
|
225
234
|
autoLoginProvider,
|
|
226
235
|
ready,
|
|
227
236
|
isAuthenticated,
|
package/app/stores/ui.ts
CHANGED
|
@@ -125,11 +125,12 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
125
125
|
// today, pluggable). NB: distinct from `observabilityInstanceId` below, which is the
|
|
126
126
|
// LLM per-call observability panel.
|
|
127
127
|
const observabilityConnectionOpen = ref(false)
|
|
128
|
-
// The single tabbed Infrastructure window
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
// selects the
|
|
128
|
+
// The single tabbed Infrastructure window — a TOP-LEVEL navbar destination (no longer
|
|
129
|
+
// reached via the Integrations hub). Two topical tabs: "Agent containers" (the execution
|
|
130
|
+
// backend + self-hosted runner pool, plus the local-mode warm pool/checkout) and "Test
|
|
131
|
+
// environments" (the ephemeral-environment provider). `infrastructureOpen` is the modal
|
|
132
|
+
// flag; `infrastructureTab` selects the tab. `openInfrastructure()` is the navbar entry;
|
|
133
|
+
// `openProviderConnection(kind)` remains for deep-links (a banner's "Configure…" button).
|
|
133
134
|
const infrastructureOpen = ref(false)
|
|
134
135
|
const infrastructureTab = ref<'environment' | 'runner-pool'>('runner-pool')
|
|
135
136
|
const modelConfigOpen = ref(false)
|
|
@@ -140,9 +141,6 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
140
141
|
const vendorCredentialsTab = ref('pool')
|
|
141
142
|
// Per-user settings panel: the signed-in user's own-machine local model runners.
|
|
142
143
|
const localModelsOpen = ref(false)
|
|
143
|
-
// Local-mode-only settings panel: the warm-container pool sizing + per-repo checkout reuse
|
|
144
|
-
// (a per-deployment singleton that replaced the LOCAL_POOL_* / HARNESS_* env vars).
|
|
145
|
-
const localModeSettingsOpen = ref(false)
|
|
146
144
|
// The Sandbox (parallel prompt/model testing) surface — an opt-in, on-demand window.
|
|
147
145
|
const sandboxOpen = ref(false)
|
|
148
146
|
const userSecretsOpen = ref(false)
|
|
@@ -479,6 +477,13 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
479
477
|
function closeObservabilityConnection() {
|
|
480
478
|
observabilityConnectionOpen.value = false
|
|
481
479
|
}
|
|
480
|
+
// Top-level navbar entry into the Infrastructure window. No hub-return marker (it isn't
|
|
481
|
+
// reached from the Integrations hub), so the window shows no "Back to Integrations" control.
|
|
482
|
+
function openInfrastructure(tab: 'environment' | 'runner-pool' = 'runner-pool') {
|
|
483
|
+
resetHubReturn()
|
|
484
|
+
infrastructureTab.value = tab
|
|
485
|
+
infrastructureOpen.value = true
|
|
486
|
+
}
|
|
482
487
|
function openProviderConnection(kind: 'environment' | 'runner-pool') {
|
|
483
488
|
resetHubReturn()
|
|
484
489
|
infrastructureTab.value = kind
|
|
@@ -511,13 +516,6 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
511
516
|
function closeLocalModels() {
|
|
512
517
|
localModelsOpen.value = false
|
|
513
518
|
}
|
|
514
|
-
function openLocalModeSettings() {
|
|
515
|
-
resetHubReturn()
|
|
516
|
-
localModeSettingsOpen.value = true
|
|
517
|
-
}
|
|
518
|
-
function closeLocalModeSettings() {
|
|
519
|
-
localModeSettingsOpen.value = false
|
|
520
|
-
}
|
|
521
519
|
function openSandbox() {
|
|
522
520
|
sandboxOpen.value = true
|
|
523
521
|
}
|
|
@@ -669,11 +667,11 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
669
667
|
observabilityConnectionOpen,
|
|
670
668
|
infrastructureOpen,
|
|
671
669
|
infrastructureTab,
|
|
670
|
+
openInfrastructure,
|
|
672
671
|
modelConfigOpen,
|
|
673
672
|
vendorCredentialsOpen,
|
|
674
673
|
vendorCredentialsTab,
|
|
675
674
|
localModelsOpen,
|
|
676
|
-
localModeSettingsOpen,
|
|
677
675
|
sandboxOpen,
|
|
678
676
|
userSecretsOpen,
|
|
679
677
|
openRouterOpen,
|
|
@@ -754,8 +752,6 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
754
752
|
closeVendorCredentials,
|
|
755
753
|
openLocalModels,
|
|
756
754
|
closeLocalModels,
|
|
757
|
-
openLocalModeSettings,
|
|
758
|
-
closeLocalModeSettings,
|
|
759
755
|
openSandbox,
|
|
760
756
|
closeSandbox,
|
|
761
757
|
openUserSecrets,
|